/* ============================================================================
   Phase 26 — Field Operations PWA (Port 8003) — consumer-grade mobile design.
   Phase 30 — Plum + Lavender + Off-White colour system (replaces #511877 maroon).

   Standalone stylesheet: the field vertical mounts its OWN /static (= static/field),
   so the admin chrome is intentionally never loaded here. Visual language translated
   1:1 from the design reference (EventCortex PWA.dc.html): Plus Jakarta Sans, the
   Plum (#6D28D9) gradient identity, soft white cards, and the sheet/toast/scan
   keyframes. Class names that JS or HTMX hook into are PRESERVED verbatim
   (fld-scan-result*, fld-flash, fld-scan-headline/msg, fld-queue-count, fld-sync-now,
   result-<rowId>, fld-ticket*, fld-sent*, fld-error).

   Design system doc: Code/FIELD_DESIGN.md (AR-48 — consult before any 8003 UI change).
   ========================================================================== */

/* Self-hosted webfonts (no CDN). The field PWA's CSP is default-src 'self' and the
   app must run fully offline at the gate — so fonts are vendored alongside htmx/alpine,
   not pulled from fonts.googleapis.com (which the CSP blocks and which fails offline).
   Plus Jakarta Sans ships as a single variable woff2 (400–800 axis); JetBrains Mono is
   the latin 400 cut used only by .fld-input--mono. Latin subset only. */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/static/fonts/plus-jakarta-sans-latin-var.woff2') format('woff2');
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/jetbrains-mono-latin-400.woff2') format('woff2');
}

:root {
  /* ── Plum + Lavender + Off-White identity (Phase 30) ────────────────────── */
  --fld-brand:  #6D28D9;  /* Plum — primary CTA, active tab, brand icon */
  --fld-brand2: #8B5CF6;  /* Violet — gradient partner, avatar gradient end */
  --fld-accent: #7C3AED;  /* Violet-600 — mid accent, icons, links */
  --fld-ink:    #1E1040;  /* Near-black with plum cast — body text */
  --fld-muted:  #7D6E9F;  /* Muted violet — secondary labels, kickers */
  --fld-muted2: #9181B3;  /* Lighter muted — timestamps, tertiary labels */
  --fld-faint:  #C4B5FD;  /* Lavender — disabled states, scanner corners */
  --fld-line:   #EDE9FE;  /* Violet-100 — hairline borders, dividers */
  --fld-line2:  #E5DFFE;  /* Slightly denser — card + chip borders */
  --fld-bg:     #FAF7FF;  /* Off-White — page background */
  --fld-tint:   #F5F3FF;  /* Violet-50 — hover/selected bg, ghost btn bg */
  --fld-card:   #ffffff;  /* Card surface — pure white */

  /* ── Semantic (unchanged — never migrate these) ──────────────────────────── */
  --fld-ok:     #15875a;
  --fld-bad:    #c0392b;
  --fld-warn:   #b87514;
  --fld-okbg:   #eafaf2;
  --fld-badbg:  #fbecea;

  /* ── Elevation ───────────────────────────────────────────────────────────── */
  --fld-shadow: 0 2px 10px -6px rgba(109,40,217,.10);
  --fld-shadow-lg: 0 24px 50px -18px rgba(109,40,217,.35);
}

* { box-sizing: border-box; }
[x-cloak] { display: none !important; }

/* ── Cross-document view transitions (Phase 32) ───────────────────────────────
   Each tab is a full server-rendered page (no SPA router), so without this every
   bottom-nav tap hard-cuts to a blank flash-of-white before the next page paints
   — the single biggest remaining "this is a website" tell. `navigation: auto`
   opts every field-app page into the browser's native cross-document transition
   (Chrome 126+, Safari 18.2+ iOS/macOS; Firefox ignores it — plain navigation,
   no regression). Zero JS, no library, CSP/AR-48-safe. `.fld-top` and
   `.fld-bottomnav` get a shared view-transition-name so the chrome stays put and
   only the page content cross-fades — exactly how a native app's persistent
   chrome behaves under a screen transition, instead of the whole page (including
   the tab bar) flashing. Each name may appear at most once per page — verified
   true here (top bar / bottom nav are each singular, conditionally rendered). */
@view-transition { navigation: auto; }
.fld-top { view-transition-name: fld-topbar; }
.fld-bottomnav { view-transition-name: fld-bottomnav; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) {
    animation: none !important;
  }
}

/* ── Native-app feel (Phase 32 — §9 FIELD_DESIGN.md) ──────────────────────────
   overscroll-behavior stops the iOS/Android rubber-band bounce + Chrome's
   pull-to-refresh from firing when a scroll view hits its top/bottom edge —
   without this, an accidental swipe reloads the whole app mid-shift, which
   reads as "a website", not "an app". Applied at the document root AND on
   every internal scroll container (sheets/menus) so scroll never chains out
   to the browser. */
html, body { height: 100%; overscroll-behavior-y: none; }
.fld-body {
  margin: 0;
  font-family: 'Plus Jakarta Sans', -apple-system, system-ui, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  color: var(--fld-ink);
  background: var(--fld-bg);
  overscroll-behavior-y: none;
}
::-webkit-scrollbar { width: 0; height: 0; }

/* touch-action: manipulation removes the ~300ms tap delay and disables
   double-tap-to-zoom on controls (that gesture is for content, not buttons).
   -webkit-touch-callout: none suppresses iOS's long-press callout menu
   ("Copy" / "Look Up") on rapid repeated taps against nav/buttons. Neither
   rule touches user-select, so ticket numbers/attendee names stay copyable —
   only interactive chrome opts out of the browser's text-selection gesture. */
button, a, .fld-tab, .fld-btn, .fld-evchip__row, .fld-evchip__option,
.fld-acct__greet, .fld-acct__action, .fld-seg__btn, .fld-install__cta, .fld-install__close, .fld-dist-stepper__btn {
  touch-action: manipulation;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

/* ── Keyframes (lifted from the design reference) ─────────────────────────── */
/* Travel distance tuned to the .fld-vf__box size (230px, minus its 16px laser inset
   on each side = ~198px) — keep this in sync if the viewfinder box size changes. */
@keyframes fld-scan  { 0%{transform:translateY(0);opacity:0} 12%{opacity:1} 88%{opacity:1} 100%{transform:translateY(198px);opacity:0} }
@keyframes fld-flash { 0%{opacity:.55;transform:scale(.96)} 100%{opacity:1;transform:scale(1)} }
@keyframes fld-sheet { 0%{transform:translateY(102%)} 100%{transform:translateY(0)} }
@keyframes fld-toast { 0%{opacity:0;transform:translateY(10px)} 100%{opacity:1;transform:translateY(0)} }
@keyframes fld-pop   { 0%{opacity:0;transform:translateY(6px) scale(.98)} 100%{opacity:1;transform:translateY(0) scale(1)} }
@keyframes fld-popc  { 0%{opacity:0;transform:translate(-50%,-50%) scale(.96)} 100%{opacity:1;transform:translate(-50%,-50%) scale(1)} }
@keyframes fld-pulse { 0%,100%{opacity:.5} 50%{opacity:1} }
@keyframes fld-spin  { to{transform:rotate(360deg)} }

/* ── Top app bar (white, sticky) ──────────────────────────────────────────── */
/* Two stacked rows as of Phase 32 part 2: the masthead (.fld-top__mast, what
   .fld-top's own flex row used to be, before the event switcher moved in
   here from each page body) and the event chip beneath it. */
.fld-top {
  display: flex; flex-direction: column;
  background: #fff; border-bottom: 1px solid var(--fld-line);
  padding: calc(14px + env(safe-area-inset-top)) 16px 12px;
  position: sticky; top: 0; z-index: 30;
}
/* Masthead gets its OWN full-bleed tinted strip (--fld-line, violet-100),
   distinct from the event-switch row below it, which stays on .fld-top's
   plain white — this is what "top bar different bg from event switch"
   actually means; colouring the whole header as one block (tried first,
   rejected) just made the header different from the PAGE, not the two
   zones different from EACH OTHER. Negative margin cancels .fld-top's own
   padding so the tint reaches the true left/right/top edges, then the same
   amount is re-added as this element's own padding.
   --fld-tint (near-white, #F5F3FF) was tried before --fld-line too — too
   close to the page's own --fld-bg to read as distinct at this size.
   Greeting chip flips to white (--fld-card) against this tint so it still
   reads as its own control, not blended into the strip. */
.fld-top__mast {
  display: flex; align-items: center; gap: 9px;
  background: var(--fld-line);
  margin: calc(-1 * (14px + env(safe-area-inset-top))) -16px 10px;
  padding: calc(14px + env(safe-area-inset-top)) 16px 10px;
}
/* Signature rule — a short Plum→Violet gradient bar between the masthead
   and the event heading below it; the one deliberately colourful flourish
   in an otherwise quiet header, echoing the same gradient already used on
   progress bars / the active nav tab elsewhere in the app. */
.fld-top__rule {
  display: block; width: 28px; height: 3px; border-radius: 99px; margin: 0 0 10px;
  background: linear-gradient(90deg, var(--fld-brand), var(--fld-brand2));
}
.fld-top__logo {
  width: 40px; height: 40px; border-radius: 11px; flex: none;
  background: var(--fld-brand); display: flex; align-items: center; justify-content: center;
  box-shadow: 0 6px 16px -6px rgba(109,40,217,.55);
}
.fld-top__logo::after { content: ""; width: 10px; height: 10px; border: 2px solid #fff; border-radius: 3px; }
img.fld-top__logo { background: none; box-shadow: none; object-fit: contain; padding: 0; }
.fld-top__brand { font-size: 17px; font-weight: 800; letter-spacing: -.01em; color: var(--fld-ink); }
.fld-top__brand .np { color: var(--fld-accent); }
.fld-top__id { display: flex; align-items: center; gap: 9px; min-width: 0; }
.fld-top__tenant {
  margin-left: auto; font-size: 11.5px; font-weight: 700; color: var(--fld-accent);
  background: #EDE9FE; border: 1px solid #E5DFFE; padding: 5px 11px; border-radius: 999px;
}

/* Event selector (legacy header dropdown — kept for reference; now replaced by fld-evchip) */
.fld-evsel { position: relative; min-width: 0; }
.fld-evsel__btn { font-family: inherit; cursor: pointer; background: none; border: none; padding: 0;
  text-align: right; max-width: 60vw; }
.fld-evsel__kicker { display: block; font-size: 10px; font-weight: 700; color: var(--fld-muted2); letter-spacing: .04em; }
.fld-evsel__name { display: flex; align-items: center; justify-content: flex-end; gap: 5px;
  font-size: 14px; font-weight: 800; color: var(--fld-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fld-evsel__caret { color: var(--fld-faint); font-size: 11px; }
.fld-evsel__menu { position: absolute; top: calc(100% + 9px); right: 0; z-index: 50;
  width: max(220px, 60vw); max-height: 60vh; overflow-y: auto;
  background: #fff; border: 1px solid var(--fld-line2); border-radius: 16px;
  box-shadow: var(--fld-shadow-lg); padding: 6px; animation: fld-pop .16s ease; }
.fld-evsel__item { display: flex; align-items: center; justify-content: space-between; gap: 8px;
  text-decoration: none; color: var(--fld-ink); font-size: 14px; font-weight: 600;
  border-radius: 11px; padding: 11px 12px; }
.fld-evsel__item:active { background: var(--fld-tint); }
.fld-evsel__item--active { background: var(--fld-tint); color: var(--fld-brand); font-weight: 800; }
.fld-evsel__tick { color: var(--fld-brand); font-weight: 800; }

/* ── Page header (title + subtitle row) ──────────────────────────────────── */
.fld-page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; margin-bottom: 4px; }
/* Variant with an inline action button (e.g. allocations "+ New") — the title
   block sits in its own wrapper div, so its .fld-h1/.fld-sub need tighter,
   button-row-appropriate spacing than the plain title+subtitle page head. */
.fld-page-head--tight { margin-bottom: 0; }
.fld-page-head--tight .fld-h1  { margin: 0; }
.fld-page-head--tight .fld-sub { margin: 2px 0 0; }

/* ── Event context chip (per-tab page-level event switcher) ──────────────── */
/* Phase 32 part 2: relocated from each page body into the persistent header
   (base_field.html) — margin-bottom dropped to 0 since .fld-top's own 12px
   bottom padding now plays that role; the 16px page-body spacing value is
   dead now that there's exactly one instance, not four. */
/* Flat heading style (Phase 32 part 2 — matches the confirmed design
   proposal: no card/border/icon, a plain event-name heading with a "Switch"
   text affordance, not a bordered dropdown button). */
.fld-evchip { margin-bottom: 0; position: relative; }
/* Scanner (active scan) and Wallet (non-list distribute flow) each toggle
   this on <body> — via a $watch in their own Alpine scope, since the header
   is now outside that scope — to reclaim the chip's vertical space during
   their full-screen "live" views, exactly as each did before the chip moved
   into the shared header (Phase 32 part 2). */
body.fld-evchip-hidden .fld-evchip,
body.fld-evchip-hidden .fld-top__rule { display: none; }
/* Hiding the chip+rule above removes their own height, but .fld-top's
   trailing 12px bottom padding and .fld-top__mast's 10px margin-bottom
   (both there to space the mast away from the rule/chip that no longer
   exist) were left behind as a bare white gap under the masthead. Collapse
   both so the dist-bar sits directly under the mast. */
body.fld-evchip-hidden .fld-top { padding-bottom: 0; }
body.fld-evchip-hidden .fld-top__mast { margin-bottom: 0; }
/* Wallet's distribute form/review flow hides the bottom nav — matches the
   proposal mockup's intent (every distribute-flow screen was built with
   showNav:false) so the sticky action footer above isn't fighting the
   floating nav pill for the same bottom-of-screen real estate. */
body.fld-bottomnav-hidden .fld-bottomnav { display: none; }
body.fld-bottomnav-hidden .fld-main--hasnav { padding-bottom: calc(24px + env(safe-area-inset-bottom)); }
.fld-evchip__row {
  width: 100%; background: none; border: none; padding: 0; margin: 0;
  display: flex; align-items: center; gap: 10px; min-height: 32px;
  font-family: inherit; text-align: left;
}
button.fld-evchip__row { cursor: pointer; }
.fld-evchip__name { flex: 1; min-width: 0; font-size: 15px; font-weight: 800; color: var(--fld-ink); }
.fld-evchip__name .t { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fld-evchip__name--empty { color: var(--fld-muted); font-weight: 600; }
.fld-evchip__switch { flex-shrink: 0; font-size: 12px; font-weight: 800; color: var(--fld-accent); }

.fld-evchip__panel {
  position: absolute; top: calc(100% + 8px); left: 0; right: 0; z-index: 20;
  background: var(--fld-card);
  border: 1.5px solid var(--fld-line2); border-radius: 16px;
  overflow: hidden; box-shadow: 0 8px 24px -8px rgba(109,40,217,.12);
}
.fld-evchip__panel--enter { transition: opacity .18s ease, transform .18s ease; }
.fld-evchip__panel--enter-start { opacity: 0; transform: translateY(-6px); }
.fld-evchip__panel--enter-end   { opacity: 1; transform: translateY(0); }

.fld-evchip__option {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 14px; min-height: 52px;
  text-decoration: none; color: var(--fld-ink);
  font-size: 14px; font-weight: 600;
  border-bottom: 1px solid var(--fld-line);
  -webkit-tap-highlight-color: transparent;
  transition: background .1s;
}
.fld-evchip__option:last-child { border-bottom: none; }
.fld-evchip__option:active { background: var(--fld-tint); }
.fld-evchip__option--active { color: var(--fld-brand); font-weight: 800; background: var(--fld-tint); }
.fld-evchip__opt-check { width: 20px; flex: none; display: flex; align-items: center; justify-content: center; color: var(--fld-brand); }
.fld-evchip__opt-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (prefers-reduced-motion: reduce) {
  .fld-evchip__panel--enter { transition: none; }
}

/* ── Account menu trigger (greeting + name chip) + dropdown ───────────────── */
/* Replaces the old two-initial avatar circle (Phase 32 shell redesign) — a
   name reads faster than initials, especially "who's signed in right now" on
   a shared gate device. Tinted pill (same --fld-tint used by the segmented
   control's track) gives it clear tap affordance — bare text on the header
   didn't read as a control. Two stacked lines inside the chip (greeting
   caption + name), each with its own independent max-width, so if anything
   has to truncate under a wide brand name it's the small greeting caption,
   never the name. Server-rendered via the existing `greeting_period()` Jinja
   global (app/core/templates.py, already powers the Admin dashboards) —
   NOT client-side `Date()`, which read the operator's device timezone
   instead of the business's NZ time (DB-19/app/core/timezone.py). Real
   min-height:44px tap target (not the negative-margin trick — no fixed
   circle to preserve here). */
.fld-acct { position: relative; flex: none; margin-left: auto; min-width: 0; }
.fld-acct__greet {
  display: flex; align-items: center; gap: 6px; min-height: 44px; min-width: 0;
  background: var(--fld-card); border: none; font-family: inherit; cursor: pointer;
  padding: 0 12px 0 14px; border-radius: 999px;
  box-shadow: 0 1px 4px -2px rgba(109,40,217,.12);
}
.fld-acct__greet:active { background: var(--fld-line); }
.fld-acct__greet-t { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; line-height: 1.2; }
.fld-acct__greet-hi {
  font-size: 9.5px; font-weight: 700; color: var(--fld-muted2); text-transform: uppercase;
  letter-spacing: .03em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 30vw;
}
.fld-acct__greet-name {
  font-size: 13.5px; font-weight: 800; color: var(--fld-ink); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 30vw;
}
.fld-acct__greet-caret { flex-shrink: 0; color: var(--fld-accent); }
.fld-acct__menu {
  position: absolute; top: calc(100% + 12px); right: 0; z-index: 60;
  width: 220px; background: #fff; border: 1px solid var(--fld-line2);
  border-radius: 18px; box-shadow: 0 20px 50px -12px rgba(0,0,0,.22), 0 4px 16px -6px rgba(109,40,217,.10);
  padding: 6px; overflow: hidden;
}
.fld-acct__menu--in { animation: fld-pop .15s ease; }
.fld-acct__identity { padding: 10px 12px 8px; }
.fld-acct__name { font-size: 13.5px; font-weight: 800; color: var(--fld-ink); margin-bottom: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fld-acct__email { font-size: 11.5px; color: var(--fld-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fld-acct__sep { margin: 0 8px 4px; border: 0; border-top: 1px solid var(--fld-line); }
.fld-acct__action {
  display: flex; align-items: center; gap: 9px;
  padding: 10px 12px; border-radius: 12px;
  font-family: inherit; font-size: 13.5px; font-weight: 700; color: var(--fld-ink);
  text-decoration: none; cursor: pointer; background: none; border: none;
  width: 100%; text-align: left; transition: background .12s;
}
.fld-acct__action:active { background: var(--fld-tint); }
.fld-acct__action--danger { color: var(--fld-bad); }
.fld-acct__action--danger:active { background: #fff5f5; }
.fld-acct__logout-form { margin: 0; }

/* ── Main scroll area ─────────────────────────────────────────────────────── */
.fld-main { max-width: 640px; margin: 0 auto; padding: 16px 14px 24px; }
/* 100px reserves clearance for the floating .fld-bottomnav pill (Phase 32):
   its own 16px bottom offset + ~66px rendered pill height, rounded up for a
   safety margin — an over-estimate here just adds a little extra scroll
   space, never clips content, so rounding up is the safe direction. */
.fld-main--hasnav { padding-bottom: calc(100px + env(safe-area-inset-bottom)); }
/* Full-bleed screens (login, change-password) supply their own edge-to-edge
   background + safe-area padding via .fld-login — the default .fld-main
   padding/max-width was leaving a ring of the wrong (--fld-bg off-white)
   background visible around all four sides of the "full-bleed" gradient
   card, the exact "bg color is not fully used" symptom. This modifier zeroes
   .fld-main out completely so .fld-login owns 100% of the viewport itself. */
.fld-main--bleed { padding: 0; max-width: none; margin: 0; }
/* Full-page single-state screen (no_access.html today; reusable for any future
   whole-page state) — vertically centers its content instead of the default
   top-anchored flow, which otherwise leaves a dead void beneath a short block
   (the "doesn't look like an app" tell). min-height subtracts .fld-top's real
   rendered height (14px top padding + 44px greeting-trigger tap target [the
   tallest masthead child, Phase 32 shell redesign] + 12px bottom padding + 1px
   border = 71px) plus its own safe-area inset, since .fld-top is sticky (takes
   normal-flow space) rather than fixed. */
.fld-main--center {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 18px;
  min-height: calc(100dvh - 71px - env(safe-area-inset-top));
}
.fld-h1  { font-size: 20px; font-weight: 800; margin: 4px 0 3px; letter-spacing: -.01em; }
.fld-sub { color: var(--fld-muted2); font-size: 12.5px; line-height: 1.5; margin: 0 0 14px; }
.fld-section-label { font-size: 11px; font-weight: 700; color: #9181B3; letter-spacing: .04em; margin: 4px 2px 8px;
  display: flex; align-items: center; gap: 6px; }
.fld-section-label svg { flex: none; }

.fld-empty {
  text-align: center; padding: 46px 20px; color: var(--fld-faint);
  border: 1px dashed var(--fld-line2); border-radius: 16px; background: #fff;
}
.fld-empty b { display: block; color: var(--fld-muted); font-weight: 700; font-size: 14px; }
.fld-empty span { display: block; font-size: 12.5px; }
.fld-empty--compact { padding: 30px 20px; }

/* A block-width submit button directly under a single sheet input. */
.fld-input + .fld-btn--block { margin-top: 12px; }

/* Empty-state / no-access icon chip — Violet-100 fill + Violet-150 border
   (FIELD_DESIGN §3). Geometry (size/radius/margin) stays inline per template;
   colour lives here so templates carry no hex in style="" (AR-14). */
.fld-eico { background: var(--fld-line); border: 1px solid var(--fld-line2); }
/* Small "all caught up" variant (allocations empty state) — 48px, green-themed,
   text glyph instead of an SVG. Geometry stays inline per the .fld-eico
   convention above only where it's genuinely per-instance; this size/colour
   pairing repeats nowhere else so it gets its own modifiers instead. */
.fld-eico--sm { width: 48px; height: 48px; border-radius: 14px; margin: 0 auto 14px;
  display: flex; align-items: center; justify-content: center; }
.fld-eico--ok { background: var(--fld-okbg); border-color: transparent; color: var(--fld-ok); font-size: 20px; }

/* ── Buttons ──────────────────────────────────────────────────────────────── */
.fld-btn {
  font-family: inherit; cursor: pointer; appearance: none;
  border: 1px solid var(--fld-line2); background: #fff; color: var(--fld-ink);
  padding: 12px 15px; border-radius: 12px; font-weight: 700; font-size: 13.5px;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  transition: transform .06s ease, filter .15s ease;
  text-decoration: none; -webkit-tap-highlight-color: transparent;
}
.fld-btn:active { transform: translateY(1px) scale(.995); }
.fld-btn--primary {
  background: var(--fld-brand); color: #fff; border-color: var(--fld-brand);
  box-shadow: 0 10px 22px -12px rgba(109,40,217,.65);
}
.fld-btn--ghost  { background: var(--fld-tint); color: var(--fld-brand); border-color: #E5DFFE; }
.fld-btn--danger { background: #fff; color: var(--fld-bad); border-color: #f0d8d4; }
.fld-btn--block  { width: 100%; }

/* ── Cards ────────────────────────────────────────────────────────────────── */
.fld-card {
  background: var(--fld-card); border: 1px solid var(--fld-line); border-radius: 18px;
  padding: 15px 16px; margin-bottom: 12px; box-shadow: var(--fld-shadow);
}
.fld-card--link { display: block; text-decoration: none; color: inherit; }
.fld-card--link:active { background: #fafafa; }
.fld-card__head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; }
.fld-card__title { font-size: 14.5px; font-weight: 800; color: var(--fld-ink); }
.fld-card__tier  { color: var(--fld-muted); font-size: 12.5px; margin-top: 2px; }

.fld-counts { display: flex; align-items: baseline; gap: 6px; margin: 10px 0 2px; }
.fld-counts .big { font-size: 22px; font-weight: 800; color: var(--fld-brand); }
.fld-counts .of  { font-size: 12px; font-weight: 700; color: #9181B3; }
.fld-count { font-size: 13px; }
.fld-count b { font-size: 18px; }
.fld-count--muted { color: var(--fld-muted); }

.fld-progress { margin-top: 9px; height: 6px; background: var(--fld-line); border-radius: 99px; overflow: hidden; }
.fld-progress__bar { height: 100%; background: linear-gradient(90deg,#6D28D9,#8B5CF6); border-radius: 99px; transition: width .3s ease; }

/* Wallet (distributor hand-out list) */
.fld-wallet__title { font-size: 14.5px; font-weight: 800; color: var(--fld-ink); }
.fld-wallet__sub { font-size: 11.5px; color: var(--fld-muted2); margin-top: 2px; }
.fld-wallet__line { border-top: 1px solid var(--fld-line); padding-top: 11px; margin-top: 11px; }
.fld-wallet__line:first-of-type { border-top: none; padding-top: 12px; }
.fld-wallet__name { font-size: 14.5px; font-weight: 800; color: var(--fld-ink); }
.fld-wallet__rem { font-size: 20px; font-weight: 800; color: var(--fld-brand); }
.fld-wallet__of { font-size: 12px; color: #9181B3; font-weight: 700; }
.fld-wallet__noperm { margin-top: 11px; text-align: center; font-size: 12px; font-weight: 700;
  color: var(--fld-muted); background: var(--fld-tint); border-radius: 12px; padding: 11px; }
.fld-wallet__done { margin-top: 11px; text-align: center; }
/* "Pool remaining" caption — same weight as .fld-wallet__name, smaller + muted */
.fld-wallet__caption { font-size: 13px; font-weight: 800; color: var(--fld-muted); }
/* Contextual spacing overrides scoped to the wallet card, matching the existing
   .fld-tiercard .fld-progress / .fld-scan-hero .fld-progress precedent instead
   of ad-hoc inline style="margin-top:…". */
.fld-wallet__line .fld-card__head { margin-top: 8px; }
.fld-wallet__line .fld-card__tier { margin-top: 5px; }
.fld-wallet__line .fld-progress { margin-top: 7px; }
.fld-wallet__line .fld-btn--block { margin-top: 11px; }

/* Reserves layout space for the validation hint before any text appears, so the
   field below doesn't jump when it does. */
.fld-req__hint--reserve { min-height: 15px; }
/* Repeated 12px top-gap between stacked form fields (distribute form, etc). */
.fld-label--gap { margin-top: 12px; }
/* Native :disabled already reflects Alpine's :disabled="!canSend" binding — no
   need for a duplicate :style="!x && 'opacity:...'" expression per button. */
.fld-btn:disabled { opacity: .5; cursor: not-allowed; }

/* ── Badges ───────────────────────────────────────────────────────────────── */
.fld-badge { font-size: 10.5px; font-weight: 700; padding: 4px 9px; border-radius: 999px;
  text-transform: uppercase; letter-spacing: .03em; white-space: nowrap; }
.fld-badge--requested { background: #ede9fe; color: #6d28d9; }
.fld-badge--pending   { background: #fbf4e7; color: #b87514; }
.fld-badge--approved  { background: #dcfce7; color: #15803d; }
.fld-badge--distributed { background: #ede9fe; color: #6d28d9; }
.fld-badge--exhausted { background: #f1f5f9; color: #475569; }
.fld-badge--rejected, .fld-badge--cancelled { background: #fee2e2; color: #b91c1c; }

/* ── Segmented control (pill toggle) ──────────────────────────────────────── */
.fld-seg { display: flex; background: var(--fld-tint); border-radius: 13px; padding: 3px; margin-bottom: 14px; }
.fld-seg__btn {
  font-family: inherit; cursor: pointer; flex: 1; border: none; background: none;
  border-radius: 10px; padding: 10px; font-weight: 700; font-size: 13px; color: var(--fld-muted);
  min-height: 44px; display: flex; align-items: center; justify-content: center;
  transition: background .18s ease, color .18s ease, box-shadow .18s ease;
}
.fld-seg__btn--active { background: #fff; color: var(--fld-brand); box-shadow: 0 2px 8px -4px rgba(109,40,217,.20); }
.fld-seg__count { font-size: 11px; font-weight: 800; margin-left: 5px; color: var(--fld-warn);
  background: #fbf4e7; padding: 1px 7px; border-radius: 99px; }

/* ── Two-up field row (e.g. Sold At amount + Payment method) ─────────────────
   Pairs a short numeric input with a segmented control on one row instead of
   stacking two full-width rows — halves the vertical space the pair costs in
   the distribute-sheet form. Margins on .fld-field-row replace the label's
   own top-gap + .fld-seg's own bottom margin for whatever sits directly
   above/below the row, so the row-to-row rhythm matches the rest of the form
   exactly.
   Columns use a px flex-basis + flex-grow ratio, NOT a % split — a % split
   looks fine at one width and wrong at every other: on a narrow phone it
   crushes both columns together, on a wider phone/tablet it leaves the short
   amount input looking stranded next to an oversized gap before the payment
   control. flex-basis anchors each column to its actual content size
   (a currency figure vs. a 2-button pill); flex-grow then hands any leftover
   width to the payment column at 1.6x the rate of the amount column, so the
   segmented control comfortably fills extra space instead of the row looking
   lopsided. flex-wrap lets the payment column drop to its own full-width row
   below the amount input as a fallback on genuinely tiny widths (a folded
   foldable, or large-text accessibility zoom) rather than crushing either
   column below a usable size. */
.fld-field-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-start; margin: 12px 0 14px; }
.fld-field-row .fld-label { margin-top: 0; }
.fld-field-row .fld-seg { margin-bottom: 0; }
.fld-field-row__col { min-width: 0; }
.fld-field-row__col--amt { flex: 1 1 110px; max-width: 150px; }
.fld-field-row__col--pay { flex: 1.6 1 160px; }
/* Inline unit pill inside the Sold Amt field itself — "ea" (retail-tag
   shorthand for "each", e.g. a shelf price tag) rather than plain caption
   text underneath, which read flat/non-native. The label stays generic
   ("Sold Amt") since that's the natural way an operator thinks about cash
   just handed over, but the field is still bound to a per-ticket value
   underneath (unchanged storage semantics) — this pill clarifies the unit
   without a second computed number restating the same figure. */
.fld-dist-amt-wrap { position: relative; }
.fld-dist-amt-wrap .fld-input { padding-right: 46px; }
.fld-dist-amt-unit {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  font-size: 10.5px; font-weight: 800; color: var(--fld-brand);
  background: var(--fld-tint); padding: 4px 8px; border-radius: 999px;
  pointer-events: none;
}

/* ── Approval card extras ─────────────────────────────────────────────────── */
.fld-req__head { display: flex; align-items: center; justify-content: space-between; }
.fld-avatar { width: 30px; height: 30px; border-radius: 50%; background: #EDE9FE; color: var(--fld-accent);
  display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 11px; flex: none; }
.fld-req__who { display: flex; align-items: center; gap: 9px; }
.fld-req__name { font-size: 13.5px; font-weight: 800; }
.fld-req__when { font-size: 11px; color: var(--fld-muted2); }
.fld-req__lines { background: var(--fld-bg); border: 1px solid var(--fld-line); border-radius: 12px;
  padding: 10px 12px; margin-top: 11px; display: flex; flex-direction: column; gap: 7px; }
.fld-req__line { display: flex; align-items: center; justify-content: space-between; font-size: 13px;
  font-weight: 700; color: #3D2B6E; }
.fld-req__reason-label { font-size: 10.5px; font-weight: 800; color: #9181B3; letter-spacing: .03em;
  display: block; margin-top: 11px; }
.fld-req__reason-label + .fld-input,
.fld-req__reason-label + .fld-req__reason { margin-top: 5px; }
/* Quoted-reason callout (Phase 32 redesign) — used both for a maker's own
   reason ("My requests") and the requester's reason on the approval card.
   Previously a bare line of grey text with no container, so a terse answer
   like "ok" read as a stray fragment with no context. Now a tinted card with
   a quote-accent left border, always paired with a .fld-req__reason-label
   ("REASON" / "REQUESTER'S REASON") immediately above it. */
.fld-req__reason {
  font-size: 12.5px; color: var(--fld-ink); line-height: 1.45;
  background: var(--fld-tint); border-left: 2.5px solid var(--fld-line2);
  border-radius: 0 10px 10px 0; padding: 8px 10px 8px 12px;
}
/* Renamed from .fld-field-row (2026-08-10) — it was a same-name duplicate of the
   wallet distribute-sheet's two-up amount+payment row above (line ~447), and being
   later in the cascade it silently won for BOTH usages, dropping that row's
   flex-wrap on narrow phones. This is a distinct component: a generic two-button
   action row (Reject/Approve), used only by allocations.html. */
.fld-btn-row { display: flex; gap: 8px; margin-top: 12px; }
.fld-btn-row .fld-btn { flex: 1; }
.fld-btn-row .fld-btn--primary { flex: 2; }

/* Request-card bullets + meta (maker's "My requests" list) */
.fld-req__bullets { display: flex; flex-direction: column; gap: 4px; margin-top: 9px; }
.fld-req__bullet { display: flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 600; color: #3D2B6E; }
.fld-dot { width: 5px; height: 5px; border-radius: 50%; background: #C4B5FD; flex: none; }
/* Footer metadata row — pushed further from the reason card above (7→11px) and
   given an icon so it reads as a distinct "submission facts" footer rather than
   a continuation of the reason text above it. */
.fld-card__meta { font-size: 11.5px; color: var(--fld-faint); margin-top: 11px;
  display: flex; align-items: center; gap: 5px; }
.fld-card__meta svg { flex: none; opacity: .8; }

/* Approval-card right-hand ticket count + per-line availability */
.fld-req__total { font-size: 18px; font-weight: 800; color: var(--fld-ink); line-height: 1; }
.fld-req__total-label { font-size: 10.5px; color: var(--fld-muted2); font-weight: 600; }
.fld-req__total-wrap { text-align: right; }
/* Cancel button on a pending "My requests" card. */
.fld-req__cancel-btn { margin-top: 12px; }
.fld-avail { font-size: 11.5px; font-weight: 700; display: inline-flex; align-items: center; gap: 5px; }
.fld-avail--ok    { color: var(--fld-ok); }
.fld-avail--short { color: var(--fld-bad); }

/* Mandatory-reason validation hint (request + approval) */
.fld-req__hint { font-size: 11.5px; font-weight: 600; color: var(--fld-bad); margin-top: 6px; }

/* Inputs (shared) */
.fld-input {
  font-family: inherit; width: 100%; border: 1.5px solid #E5DFFE; border-radius: 12px;
  padding: 12px 13px; font-size: 15px; color: var(--fld-ink); outline: none; background: #fff;
}
.fld-input:focus { border-color: var(--fld-brand2); }
.fld-input--mono { font-family: 'JetBrains Mono', monospace; letter-spacing: 2px; text-transform: uppercase; }
.fld-label { font-size: 12px; font-weight: 700; color: #6B5E8A; margin: 6px 0; display: block; }
.fld-label .req { color: var(--fld-bad); }

/* ── Bottom sheet + modal (Alpine-driven) ─────────────────────────────────── */
.fld-backdrop { position: fixed; inset: 0; z-index: 80; background: rgba(30,16,64,.45); }
.fld-sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 86;
  background: #fff; border-radius: 26px 26px 0 0; max-height: 92%; overflow-y: auto;
  padding: 16px 18px calc(30px + env(safe-area-inset-bottom));
  animation: fld-sheet .28s cubic-bezier(.2,.8,.2,1);
  /* Prevents an over-scroll at the top/bottom of the sheet's own content from
     chaining into the page behind it (rubber-band / pull-to-refresh). */
  overscroll-behavior: contain;
}
.fld-sheet__grab { width: 38px; height: 4px; background: #EDE9FE; border-radius: 99px; margin: 0 auto 14px; }
.fld-sheet__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 3px; }
.fld-sheet__title { font-size: 18px; font-weight: 800; color: var(--fld-ink); }
/* 44×44 minimum touch target (Apple HIG / WCAG 2.5.5). Visual circle stays 32px;
   negative margin offsets the extra hit-area so layout spacing is unchanged. */
.fld-sheet__close { font-family: inherit; cursor: pointer; background: var(--fld-tint); border: none;
  width: 44px; height: 44px; border-radius: 50%; color: #6B5E8A; font-size: 15px;
  display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin: -7px -7px -7px 0; }
.fld-sheet__sub { font-size: 13px; color: var(--fld-muted); margin-bottom: 14px; }
/* Live running-total recap ("3 tickets across 2 tiers") above the submit button
   in the new-request sheet — centred, with its own top/bottom rhythm. */
.fld-sheet__sub--summary { text-align: center; margin: 12px 0 10px; }

/* Centered modal card (confirm / error popup) */
.fld-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%,-50%); z-index: 97;
  width: min(320px, 86vw); background: #fff; border-radius: 20px; padding: 22px;
  box-shadow: 0 30px 70px -20px rgba(30,16,64,.60); animation: fld-popc .18s ease; }

/* Confirmation dialog (replaces the native confirm() for every hx-confirm; design
   lifted from the reference's confirmation-dialog component). */
.fld-confirm { position: fixed; inset: 0; z-index: 96; }
.fld-confirm[hidden] { display: none; }
.fld-confirm__backdrop { position: fixed; inset: 0; background: rgba(30,16,64,.50); animation: fld-flash .16s ease; }
.fld-modal__title { font-size: 17px; font-weight: 800; color: var(--fld-ink); }
.fld-modal__body  { font-size: 13.5px; color: #6B5E8A; line-height: 1.5; margin-top: 8px; }
.fld-modal__actions { display: flex; gap: 9px; margin-top: 20px; }
.fld-modal__actions .fld-btn { flex: 1; }
.fld-btn--soft { background: var(--fld-tint); color: #6B5E8A; border-color: transparent; }
.fld-btn--danger-solid { background: var(--fld-bad); color: #fff; border-color: var(--fld-bad); }

/* Method chooser tiles (distribute) */
.fld-method { font-family: inherit; cursor: pointer; text-align: left; width: 100%;
  background: var(--fld-bg); border: 1px solid var(--fld-line2); border-radius: 15px;
  padding: 14px 15px; display: flex; align-items: center; gap: 13px; margin-bottom: 9px; }
.fld-method__icon { width: 42px; height: 42px; border-radius: 12px; display: flex; align-items: center;
  justify-content: center; font-size: 19px; flex: none; }
.fld-method__icon--mail { background: var(--fld-okbg); }
.fld-method__icon--qr   { background: #EDE9FE; }
.fld-method__t { font-size: 14.5px; font-weight: 800; color: var(--fld-ink); }
.fld-method__d { font-size: 12px; color: var(--fld-muted); }

/* Qty stepper (request form) */
.fld-stepper { display: flex; align-items: center; gap: 11px; padding: 11px 0; border-bottom: 1px solid var(--fld-line); }
.fld-stepper__main { flex: 1; }
.fld-stepper__btn { font-family: inherit; cursor: pointer; width: 36px; height: 36px; border-radius: 10px;
  border: 1.5px solid #E5DFFE; background: var(--fld-bg); color: var(--fld-brand); font-size: 18px; font-weight: 700; }
.fld-stepper__qty { font-family: inherit; font-size: 16px; font-weight: 800; color: var(--fld-ink);
  width: 54px; text-align: center; border: 1.5px solid #E5DFFE; border-radius: 10px; background: #fff; padding: 8px 2px; outline: none; }

/* .fld-error — generic inline error banner, still used by login.html /
   change_password.html. NOT part of the old distribution-result fragment below
   (that whole family was removed 2026-08-10 — see Distribution result below). */
.fld-error { background: var(--fld-badbg); border: 1px solid #f3d3ce; color: #9a2d20;
  border-radius: 12px; padding: 12px; font-size: 13px; font-weight: 600; }

/* ── Distribution full-screen view (2026-08-10 redesign) ──────────────────────
   Replaces the old HTMX-fragment result (.fld-result/.fld-ticket*/.fld-sent,
   removed — no template references them anymore) with an in-page review→result
   flow, no reload. Nav-bar pattern cloned from .fld-scan-bar (own prefix per
   FIELD_DESIGN.md convention — distribution is not scanner-specific).

   195px (Phase 32 shell redesign, was 175px) = surrounding chrome: sticky
   .fld-top (~71px, up from ~60px — the greeting trigger's 44px real tap
   target is now the tallest masthead child) + .fld-main's 16px top padding +
   its 100px --hasnav bottom reservation (up from 96px — the floating nav's
   own 16px offset from the viewport edge), rounded up. Doesn't need to be
   exact — over-estimating just stops shrinking early, never causes overflow. */
.fld-dist-live { display: flex; flex-direction: column;
  min-height: calc(100vh - 195px);
  min-height: calc(100dvh - 195px - env(safe-area-inset-top) - env(safe-area-inset-bottom)); }

.fld-dist-bar {
  position: relative; display: flex; align-items: center; justify-content: center;
  min-height: 44px; padding-bottom: 12px;
}
.fld-dist-bar__back {
  position: absolute; left: 0; top: 0; bottom: 12px;
  font-family: inherit; cursor: pointer; background: none; border: none;
  padding: 0 10px 0 0; color: var(--fld-brand);
  min-width: 44px; /* §9 tap target (height from the bar) */
  display: flex; align-items: center; justify-content: flex-start;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
.fld-dist-bar__back svg { display: block; }
.fld-dist-bar__titles {
  display: flex; flex-direction: column; align-items: center;
  max-width: calc(100% - 88px); min-width: 0;
}
.fld-dist-bar__evt {
  font-size: 14.5px; font-weight: 800; color: var(--fld-ink);
  line-height: 1.2; text-align: center; letter-spacing: -.01em;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fld-dist-bar__sub {
  font-size: 11.5px; font-weight: 700; color: var(--fld-muted);
  margin-top: 2px; white-space: nowrap;
}

/* 96px reserves clearance for the now-fixed .fld-dist-foot below (16px
   viewport offset + ~70px card: 24px padding + 44px button — rounded up,
   same over-estimate-is-safe convention as .fld-main--hasnav above). */
.fld-dist-body { flex: 1 1 auto; min-height: 0; padding-bottom: calc(96px + env(safe-area-inset-bottom)); }

/* Fixed action footer — same proven pattern as .fld-bottomnav (position:
   fixed, pinned to the viewport, not to the content column) rather than
   position:sticky. sticky depends on its container's own height reaching
   the viewport edge, which meant hand-tuning .fld-dist-live's min-height
   pixel-for-pixel against the header/nav's real rendered height — two
   rounds of that still left a dead gap under the footer. Fixed sidesteps
   the whole calculation: it's pinned to the viewport unconditionally, like
   the bottom nav it replaces during this flow. .fld-dist-body below carries
   matching bottom padding so the last content row isn't hidden under it. */
.fld-dist-foot {
  position: fixed; left: 14px; right: 14px;
  bottom: calc(16px + env(safe-area-inset-bottom)); z-index: 40;
  background: var(--fld-card); border: 1px solid var(--fld-line2); border-radius: 16px;
  padding: 12px 14px; box-shadow: var(--fld-shadow-lg);
}

/* Distribute form — grouped cards (Recipient/Tickets/Payment/Reason), icon-
   labeled sections instead of one flat list of inputs. New, wallet-distribute-
   only classes — .fld-input/.fld-label themselves are shared with
   login/change-password/allocations/scanner and are deliberately NOT touched. */
.fld-dist-group {
  background: var(--fld-card); border: 1px solid var(--fld-line2); border-radius: 16px;
  padding: 12px; margin-bottom: 8px; box-shadow: var(--fld-shadow);
}
.fld-dist-group__head { display: flex; align-items: center; gap: 7px; margin-bottom: 8px; }
.fld-dist-group__icon {
  width: 22px; height: 22px; border-radius: 7px; background: var(--fld-tint); color: var(--fld-accent);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.fld-dist-group__title { font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: var(--fld-muted2); }
.fld-dist-group .fld-label:first-of-type { margin-top: 0; }
.fld-dist-group .fld-field-row { margin: 0; }
/* Live-computed total (qty × per-ticket price) — only shown once qty > 1,
   since at qty 1 it would just repeat the price field above it. Derived
   from Alpine's totalCollected getter, never its own state. */
.fld-dist-total {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--fld-line);
  font-size: 12.5px;
}
.fld-dist-total span:first-child { color: var(--fld-muted); font-weight: 600; }
.fld-dist-total span:last-child { color: var(--fld-ink); font-weight: 800; }

/* Tier + quantity — one row, tier info left / stepper right (was two
   stacked rows — merged per request). Its own component, NOT the shared
   .fld-stepper (allocations.html's request-form sheet uses that one,
   unchanged). */
.fld-dist-tier-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  background: var(--fld-tint); border-radius: 12px; padding: 5px 10px 5px 13px;
}
.fld-dist-tier-row__info { min-width: 0; }
.fld-dist-tier-row__name { font-size: 13.5px; font-weight: 800; color: var(--fld-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fld-dist-tier-row__avail { font-size: 10.5px; font-weight: 700; color: var(--fld-ok); margin-top: 2px; }
.fld-dist-stepper { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
/* Visual box shrunk to 32×32 (was an honestly-sized 44×44, which read as
   bulky sitting next to a compact qty number). §9's 44px tap-target floor
   is still met via the invisible-padding technique instead — same
   sanctioned route FIELD_DESIGN.md documents for .fld-login__eye: a
   position:relative host with a ::before expanding the actual hit area to
   44×44 (inset -6px each side = 32+12=44) while the rendered button stays
   visually compact. ::before has no separate DOM node, so a tap anywhere
   in that invisible expanded box still fires the button's own @click. */
.fld-dist-stepper__btn {
  position: relative;
  font-family: inherit; cursor: pointer; width: 32px; height: 32px; border-radius: 10px;
  border: 1.5px solid var(--fld-line2); background: #fff; color: var(--fld-brand);
  font-size: 15px; font-weight: 800; display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.fld-dist-stepper__btn::before { content: ''; position: absolute; inset: -6px; }
/* Widened 22→30→36px (was tuned for 1, then 2 digits — now supports a
   3-digit quantity like "100" without clipping). */
.fld-dist-stepper__qty { font-family: inherit; font-size: 16px; font-weight: 800; color: var(--fld-ink); width: 36px; text-align: center; background: none; border: none; flex-shrink: 0; }

/* Review step (redesigned 2026-08-10, revised 2026-08-10.2 per user feedback
   to a plain table — every fact its own row, nothing merged or turned into a
   hero stat) + a short consequence note. Elevated to match .fld-dist-group's
   card treatment (white bg + shadow — it previously had neither), plus a
   method badge so it's unambiguous which flow (Email/Manual) produced this
   recap when both share the identical review shape. */
.fld-dist-badge-method {
  display: inline-flex; align-items: center; gap: 6px; background: var(--fld-tint); color: var(--fld-accent);
  font-size: 11px; font-weight: 800; padding: 6px 12px; border-radius: 99px; margin-bottom: 12px;
}
/* Ticket-stub hero — perforated-card motif (gradient ID half + dashed
   perforation + punched notches + plain receipt half), echoing the actual
   object being handed over instead of another flat data row. Notches sit
   on .fld-dist-stub__top's own fixed min-height boundary (deterministic,
   not derived from the perforation's rendered position) and are full
   circles colored to match the page bg, positioned straddling the card's
   edge — the classic ticket-punch illusion, no overflow-clip trick needed. */
.fld-dist-stub {
  position: relative; border-radius: 16px;
  background: var(--fld-card); box-shadow: var(--fld-shadow-lg); margin-bottom: 12px;
}
.fld-dist-stub__top {
  min-height: 80px; border-radius: 16px 16px 0 0; padding: 16px;
  background: linear-gradient(135deg, var(--fld-brand), var(--fld-brand2)); color: #fff;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.fld-dist-stub__id { min-width: 0; }
.fld-dist-stub__tier {
  font-size: 15.5px; font-weight: 800; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fld-dist-stub__evt { font-size: 11.5px; font-weight: 600; opacity: .82; margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fld-dist-stub__qty { text-align: right; flex-shrink: 0; }
.fld-dist-stub__qty span { font-size: 24px; font-weight: 800; line-height: 1; display: block; }
.fld-dist-stub__qty small {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; opacity: .85;
}
.fld-dist-stub__notch {
  position: absolute; top: 80px; margin-top: -9px; width: 18px; height: 18px;
  border-radius: 50%; background: var(--fld-bg); z-index: 2;
}
.fld-dist-stub__notch--l { left: -9px; }
.fld-dist-stub__notch--r { right: -9px; }
.fld-dist-stub__perf { border-top: 2px dashed var(--fld-line2); margin: 0 18px; }
.fld-dist-stub__bottom { padding: 12px 16px 14px; }
.fld-dist-stub__row {
  display: flex; justify-content: space-between; align-items: center;
  font-size: 12.5px; font-weight: 600; color: var(--fld-muted); padding: 5px 0;
}
.fld-dist-stub__row--total {
  border-top: 1px solid var(--fld-line); margin-top: 4px; padding-top: 9px;
}
.fld-dist-stub__row--total span:first-child { color: var(--fld-ink); font-weight: 700; }
.fld-dist-stub__row--total span:last-child { color: var(--fld-ink); font-weight: 800; font-size: 15px; }

.fld-dist-review__row {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 7px 0; font-size: 13px;
}
.fld-dist-review__row + .fld-dist-review__row { border-top: 1px solid var(--fld-line); margin-top: 2px; padding-top: 9px; }
.fld-dist-review__k { color: var(--fld-muted); font-weight: 600; flex: none; }
.fld-dist-review__hint {
  font-size: 9.5px; font-weight: 800; color: var(--fld-brand);
  background: var(--fld-tint); padding: 2px 6px; border-radius: 999px;
  margin-left: 2px; vertical-align: middle;
}
.fld-dist-review__v { color: var(--fld-ink); font-weight: 700; text-align: right; overflow-wrap: anywhere; }
.fld-dist-review__reason { font-size: 13px; font-weight: 600; color: var(--fld-ink); line-height: 1.5; }

.fld-dist-review__note {
  display: flex; align-items: flex-start; gap: 9px;
  background: var(--fld-tint); border-radius: 12px; padding: 11px 13px;
  font-size: 12px; font-weight: 600; color: var(--fld-ink); line-height: 1.5;
}
.fld-dist-review__note svg { flex: none; color: var(--fld-brand); margin-top: 1px; }

.fld-dist-confirm__acts { display: flex; gap: 10px; }
.fld-dist-confirm__acts .fld-btn:first-child { flex: 1; }
.fld-dist-confirm__acts .fld-btn:last-child { flex: 1.6; }

/* Result step — pending / ok / error, in-page (no reload) */
.fld-dist-result { text-align: center; padding: 48px 10px 8px; }
.fld-dist-result__icon {
  width: 64px; height: 64px; border-radius: 99px;
  display: flex; align-items: center; justify-content: center; margin: 0 auto 16px;
}
.fld-dist-result--pending .fld-dist-result__icon { background: var(--fld-tint); color: var(--fld-brand); }
.fld-dist-result--ok .fld-dist-result__icon { background: var(--fld-okbg); color: var(--fld-ok); }
.fld-dist-result--error .fld-dist-result__icon { background: var(--fld-badbg); color: var(--fld-bad); }
.fld-dist-result__head { font-size: 19px; font-weight: 800; margin-bottom: 4px; color: var(--fld-ink); }
.fld-dist-result--ok .fld-dist-result__head { color: var(--fld-ok); }
.fld-dist-result--error .fld-dist-result__head { color: var(--fld-bad); }
.fld-dist-result__msg {
  font-size: 13px; color: var(--fld-muted); max-width: 280px; margin: 0 auto 8px; line-height: 1.5;
}
.fld-dist-result__queued {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11.5px; font-weight: 700; color: var(--fld-warn);
  background: #FDF4E6; border: 1px solid #F0DDBC; border-radius: 99px; padding: 6px 12px; margin-top: 8px;
}
.fld-dist-result__queued::before {
  content: ''; width: 6px; height: 6px; border-radius: 99px; background: var(--fld-warn);
}
.fld-dist-spin {
  width: 26px; height: 26px; border: 3px solid var(--fld-line2);
  border-top-color: var(--fld-brand); border-radius: 99px; animation: fld-spin .7s linear infinite;
}

/* ── Scanner ──────────────────────────────────────────────────────────────── */
.fld-offline-banner { background: var(--fld-badbg); border: 1px solid #f3d3ce; border-radius: 14px;
  padding: 11px 13px; margin-bottom: 12px; display: flex; align-items: center; gap: 11px; }
.fld-offline-banner::before { content: ""; width: 9px; height: 9px; border-radius: 50%;
  background: var(--fld-bad); animation: fld-pulse 1.4s infinite; flex: none; }
.fld-offline-banner b { display: block; font-size: 13px; color: #9a2d20; }
.fld-offline-banner span { font-size: 11.5px; color: #bb5e52; }

/* .fld-scan-device fuses the camera (.fld-cam) and the result banner
   (#fld-scan-result) into ONE continuous instrument: one outer radius, one
   card shadow, no visible seam — instead of two independently-styled stacked
   cards. The two children keep their own backgrounds/state colours; only the
   shared outer corners + elevation move up to this wrapper. See
   FIELD_DESIGN.md §7 "Scan device". */
.fld-scan-device { border-radius: 22px; overflow: hidden; box-shadow: var(--fld-shadow); margin: 8px 0 14px;
  /* Flex column so .fld-cam can absorb the leftover height while the result banner
     keeps its natural size — see .fld-scan-live below. */
  display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }

/* ── Full-height live scan view (Phase 33) ────────────────────────────────────
   The active scan view is a flex column that fills the viewport, so the camera
   grows into whatever the chrome leaves instead of stopping at a fixed 340px and
   stranding ~140px of empty white above the tab bar — the strongest "this is a web
   page, not an app" tell on the whole surface.

   100dvh (not 100vh) is deliberate: on iOS Safari the URL bar collapses on scroll,
   and 100vh refers to the TALLER pre-collapse viewport, which would push the action
   strip under the tab bar. dvh tracks the live viewport. 100vh is kept as the
   fallback line for engines without dvh.

   The subtraction is the surrounding chrome: sticky .fld-top (~71px, up from
   ~60px in Phase 32's shell redesign — see .fld-dist-live above for the same
   195px derivation) + .fld-main's 16px top padding + its 100px --hasnav
   bottom reservation, plus the safe-area insets those already add. It does
   not need to be exact — .fld-cam's min-height is the floor, so an
   over-estimate simply stops the camera shrinking further. */
.fld-scan-live { display: flex; flex-direction: column;
  min-height: calc(100vh - 195px);
  min-height: calc(100dvh - 195px - env(safe-area-inset-top) - env(safe-area-inset-bottom)); }
.fld-scan-live #fld-scanner { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }

/* Camera stack: #fld-scan-reader holds the qr-scanner <video> on the bottom layer,
   the pulsing viewfinder floats on top (pointer-events:none so the camera-
   permission button underneath stays tappable). Radius/margin live on the
   .fld-scan-device wrapper now — .fld-cam is a flush top segment of it.
   .fld-cam and the <video> are both a FIXED height (340px, Phase 32 — was 260px;
   raised so the camera reads as a substantial full-bleed native viewfinder
   instead of a thin strip) — height:auto (intrinsic camera aspect ratio)
   previously made the video a different size than its container on any stream
   that wasn't exactly the fixed height at 100% width, showing as a gap (video
   shorter) or a silent crop (video taller). Fixed height + object-fit:cover
   makes the two boxes identical always, cropping the stream instead of
   letterboxing it — standard for camera viewfinders. */
.fld-cam { position: relative; height: 340px;
  background: radial-gradient(120% 90% at 50% 30%, #2D1060 0%, #0F0820 100%); }
/* Inside the full-height live view the camera FLEXES instead of sitting at 340px.
   This does NOT reintroduce the §14.9 bug that the fixed height was protecting
   against: that bug was `height: auto`, which let the VIDEO's aspect ratio drive the
   container. Here the container is still driven externally (by flex), and the video
   remains height:100% + object-fit:cover — so the two boxes stay identical and the
   stream is cropped, never letterboxed. The invariant is "container-driven height",
   not "literally 340px". min-height keeps it usable on short/landscape viewports. */
.fld-scan-live .fld-cam { height: auto; flex: 1 1 auto; min-height: 300px; }
/* …and the video layer must FILL that flexed box. `.fld-reader` uses `height: 100%`,
   which is a PERCENTAGE — and a percentage height only resolves against a parent with
   a *definite* height. Once `.fld-cam` became `height: auto` (flex-sized), that
   percentage had nothing to resolve against, so the reader collapsed to the video's
   intrinsic size and `.fld-cam`'s plum gradient showed through underneath as a hard
   black/purple seam across the middle of the viewfinder.
   Absolute inset:0 sidesteps percentage resolution entirely — it fills the padding box
   however the height was computed. This is the same technique `.fld-vf` already uses
   for the viewfinder overlay, so the camera, overlay and permission layers now share
   one positioning model. */
.fld-scan-live .fld-reader,
.fld-scan-live #fld-scan-reader { position: absolute; inset: 0; height: auto; width: auto; }
.fld-reader, #fld-scan-reader { position: relative; z-index: 1; width: 100%; height: 100%; }
#fld-scan-reader video { width: 100% !important; height: 100% !important; object-fit: cover; display: block; }
#fld-scan-reader { color: #C4B5FD; }                    /* permission prompt text on dark bg */

.fld-vf { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
/* Phase 32: the faint tech-grid overlay was removed — no mainstream native QR
   scanner (Apple/Google/WeChat/Pay apps) draws a grid across the live camera;
   it read as an AR/wireframe effect rather than a clean camera view and was
   flagged as a "doesn't feel native" contributor. The corner-bracket frame
   alone is the correct amount of camera chrome. */
/* Frame enlarged 200→230px and corners lengthened/thickened (Phase 32) so it
   reads as a confident, obvious scan target — matching how native scanners
   size their viewfinder relative to the camera view, rather than a small
   frame floating in a lot of empty background. */
.fld-vf__box { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
  width: 230px; height: 230px; }
.fld-vf__c { position: absolute; width: 42px; height: 42px; }
.fld-vf__c--tl { top: 0; left: 0; border-top: 3.5px solid #C4B5FD; border-left: 3.5px solid #C4B5FD; border-radius: 10px 0 0 0; }
.fld-vf__c--tr { top: 0; right: 0; border-top: 3.5px solid #C4B5FD; border-right: 3.5px solid #C4B5FD; border-radius: 0 10px 0 0; }
.fld-vf__c--bl { bottom: 0; left: 0; border-bottom: 3.5px solid #C4B5FD; border-left: 3.5px solid #C4B5FD; border-radius: 0 0 0 10px; }
.fld-vf__c--br { bottom: 0; right: 0; border-bottom: 3.5px solid #C4B5FD; border-right: 3.5px solid #C4B5FD; border-radius: 0 0 10px 0; }
.fld-vf__laser { position: absolute; top: 16px; left: 16px; right: 16px; height: 2px;
  background: linear-gradient(90deg,transparent,#A78BFA,transparent); box-shadow: 0 0 12px #A78BFA;
  animation: fld-scan 2.4s ease-in-out infinite; }

/* Scan result banner — scanner.js swaps the --ok/--dup/--invalid/--idle modifier and
   re-adds .fld-flash on every scan. Colours = the design's green-admit / red-reject.
   Radius lives on the .fld-scan-device wrapper — this is the flush bottom segment. */
.fld-scan-result { padding: 15px 16px; text-align: center; color: #fff; }
.fld-scan-result__head { display: flex; align-items: center; justify-content: center; gap: 7px; }
.fld-scan-result__headline { font-size: 20px; font-weight: 800; }
.fld-scan-result__msg { font-size: 13.5px; margin-top: 4px; opacity: .95; }
.fld-scan-result--idle { background: radial-gradient(120% 90% at 50% 30%, #3D2B6E 0%, #1E1040 100%); }
.fld-scan-result--ok   { background: linear-gradient(90deg,#15875a,#1aa06c); }   /* GREEN — admitted */
.fld-scan-result--dup,
.fld-scan-result--invalid { background: linear-gradient(90deg,#c0392b,#d8503f); } /* RED — dup / off-manifest */
.fld-flash { animation: fld-flash .25s ease; }

/* Phase 32: the resting "Ready to scan" state is a passive hint, not feedback
   about something that happened — it should read as a quiet caption under the
   viewfinder, not a shouted headline+paragraph competing with the camera for
   attention (the exact contributor to the "too bulky" complaint). The mandated
   copy strings (FIELD_DESIGN.md §14.4) are unchanged — only their weight here.
   Every OTHER state (pending/ok/dup/invalid) is real-time feedback the operator
   must act on or notice immediately and KEEPS the full bold treatment above. */
.fld-scan-result--idle { padding: 9px 16px; }
.fld-scan-result--idle .fld-scan-result__headline { font-size: 12.5px; font-weight: 700; }
.fld-scan-result--idle .fld-scan-result__msg { font-size: 11px; opacity: .6; margin-top: 1px; }
.fld-scan-result--idle .fld-scan-result__icon,
.fld-scan-result--idle .fld-scan-result__icon svg { width: 14px; height: 14px; }

/* State icon — WCAG 1.4.1 (don't convey state by colour alone) + faster at-a-glance
   read in bright outdoor gate conditions. Sits INLINE beside the headline (not
   stacked above it) so it stays a compact one-line row instead of adding a whole
   icon-badge row of height to the banner. Pure CSS: all four SVGs are always in the
   DOM: scanner.js only ever toggles the parent's --idle/--pending/--ok/--dup/--invalid
   class (unchanged); this block keys icon visibility off that same class so scanner.js
   never gains an innerHTML/DOM write. Dup + invalid share one icon (both rejections,
   already share one red background above) per §14's "don't split dup into gate/server
   visual variants" rule — extended here to icon choice for the same reason. */
.fld-scan-result__icon { display: flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; flex: none; }
.fld-scan-result__icon svg { display: none; width: 18px; height: 18px; }
.fld-scan-result--idle .i-idle,
.fld-scan-result--pending .i-pending,
.fld-scan-result--ok .i-ok,
.fld-scan-result--dup .i-bad,
.fld-scan-result--invalid .i-bad { display: block; }

.fld-scan-meta { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 14px; font-size: 13px; color: var(--fld-muted); }
.fld-scan-meta b { color: var(--fld-ink); }

/* Scanner dashboard (pre-scan by-tier check-in view) */
.fld-backlink { font-family: inherit; cursor: pointer; background: none; border: none; padding: 0;
  color: var(--fld-brand); font-weight: 700; font-size: 13.5px; margin-bottom: 11px; }
.fld-overall { background: #fff; border: 1px solid var(--fld-line); border-radius: 18px;
  padding: 14px 16px; margin-bottom: 13px; box-shadow: var(--fld-shadow); }
.fld-overall__head { display: flex; align-items: baseline; justify-content: space-between; }
.fld-overall__n { font-size: 26px; font-weight: 800; color: var(--fld-ink); }
.fld-overall__of { font-size: 14px; color: #9181B3; font-weight: 700; }
.fld-overall__pct { font-size: 12px; font-weight: 700; color: var(--fld-ok); }
.fld-tiercard { background: #fff; border: 1px solid var(--fld-line); border-radius: 15px;
  padding: 13px 14px; margin-bottom: 9px; }
.fld-tiercard__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.fld-tiercard__name { font-size: 14.5px; font-weight: 800; color: var(--fld-ink); }
.fld-tiercard__pill { font-size: 11.5px; font-weight: 700; color: var(--fld-brand);
  background: var(--fld-tint); padding: 3px 9px; border-radius: 99px; white-space: nowrap; }
.fld-tiercard__sub { font-size: 12px; color: var(--fld-muted); margin-top: 5px; }
.fld-tiercard .fld-progress { margin-top: 7px; height: 5px; }
.fld-scanbig { width: 100%; margin-top: 6px; padding: 15px; font-size: 15px; }
.fld-syncrow { display: flex; gap: 8px; margin-top: 11px; }
.fld-syncrow .fld-btn { flex: 1; }
.fld-syncrow__note { text-align: center; font-size: 11px; color: var(--fld-faint); margin-top: 9px; }
.fld-torch { font-family: inherit; cursor: pointer; position: absolute; top: 12px; right: 12px; z-index: 3;
  width: 38px; height: 38px; border-radius: 12px; border: 1px solid rgba(255,255,255,.16);
  background: rgba(255,255,255,.12); color: #fff; font-size: 16px; }
.fld-torch--on { background: #A78BFA; }

/* ── Phase 31 Scanner UX redesign ─────────────────────────────────────────── */

/* Pending scan — Plum gradient while the confirm sheet is open.
   Signals "brand interaction moment — the operator must decide now."
   NOT amber (that's for low-stock warnings; it must never appear here). */
.fld-scan-result--pending { background: linear-gradient(135deg, var(--fld-brand) 0%, var(--fld-brand2) 100%); }

/* Admit button — the ONE legitimate exception where --fld-ok green overrides
   --fld-brand Plum. Used ONLY on the "Admit ✓" gate action inside .fld-sheet.
   The green matches the --ok scan-result colour so the action and outcome agree. */
.fld-btn--admit {
  background: var(--fld-ok);
  color: #fff;
  border-color: var(--fld-ok);
  box-shadow: 0 8px 20px -10px rgba(21,135,90,.55);
}
.fld-btn--admit:active { background: #0f6847; }

/* Camera permission overlay — position: absolute inside .fld-cam (which is
   position: relative), so it fills the camera area before access is granted.
   .fld-perm--denied = red treatment for OS-blocked camera. */
.fld-perm {
  position: absolute; inset: 0; z-index: 10; border-radius: 22px;
  background: var(--fld-tint);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 28px 20px; gap: 14px; text-align: center;
}
.fld-perm--denied { background: var(--fld-badbg); }
.fld-perm__icon {
  width: 64px; height: 64px; border-radius: 20px;
  background: var(--fld-brand);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 10px 24px -8px rgba(109,40,217,.45);
  color: #fff; flex-shrink: 0;
}
.fld-perm--denied .fld-perm__icon { background: var(--fld-bad); box-shadow: 0 10px 24px -8px rgba(192,57,43,.40); }
.fld-perm__title { font-size: 16px; font-weight: 800; color: var(--fld-ink); }
.fld-perm--denied .fld-perm__title { color: #9a2d20; }
.fld-perm__body { font-size: 13px; color: var(--fld-muted); line-height: 1.5; max-width: 240px; }
.fld-perm--denied .fld-perm__body { color: #bb5e52; }
.fld-perm__btn { margin-top: 4px; }

/* Dashboard hero card — replaces the bare .fld-overall div.
   White card (NOT Plum gradient — that's the command dashboard only). */
.fld-scan-hero {
  background: var(--fld-card); border: 1px solid var(--fld-line);
  border-radius: 20px; padding: 20px 18px; margin-bottom: 14px;
  box-shadow: var(--fld-shadow);
}
.fld-scan-hero__top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.fld-scan-hero__n  { font-size: 40px; font-weight: 800; color: var(--fld-ink); letter-spacing: -.02em; line-height: 1; }
.fld-scan-hero__of { font-size: 15px; font-weight: 700; color: var(--fld-muted2); }
.fld-scan-hero__pct {
  font-size: 11.5px; font-weight: 800; color: var(--fld-ok);
  background: var(--fld-okbg); border-radius: 99px; padding: 3px 10px; flex-shrink: 0; align-self: center;
}
.fld-scan-hero__evt {
  font-size: 12px; font-weight: 600; color: var(--fld-muted); margin-top: 4px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fld-scan-hero .fld-progress { margin-top: 14px; height: 8px; }

/* Scan-stat bar — compact header for the active-scan view.
   Replaces .fld-backlink + .fld-h1 + .fld-sub in the old layout. */
/* Native nav-bar pattern (Phase 33): the title is TRUE-centred in the full bar width,
   with the back control and count chip absolutely positioned in the side zones. A
   plain flex row with `flex: 1` on the title (the previous form) centres it in the
   LEFTOVER space instead, so it drifts left/right as the chip's digits grow — the
   giveaway that a header was laid out as a web page rather than an app nav bar.
   Both side controls are out of flow, so the title is unaffected by their width. */
.fld-scan-bar {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  min-height: 44px; padding-bottom: 12px;
}
.fld-scan-bar__back {
  /* top/bottom pinned (bottom matches the bar's padding-bottom) so the chevron stays
     optically centred against the two-line title stack instead of hugging the first
     line. The bar's own min-height:44px guarantees the §9 tap target even though the
     text stack is only ~35px tall. */
  position: absolute; left: 0; top: 0; bottom: 12px;
  font-family: inherit; cursor: pointer; background: none; border: none;
  padding: 0 10px 0 0; color: var(--fld-brand);
  min-width: 44px;                            /* §9 tap target (height from the bar) */
  display: flex; align-items: center; justify-content: flex-start;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
.fld-scan-bar__back svg { display: block; }
/* Title + subtitle stack. Only the 44px back zone needs clearing, and the 88px is
   SYMMETRIC (44 each side) so the stack stays optically centred on the bar rather than
   centred in the space left over beside the button. That buys the title 274px on a
   390px phone versus 212px under the old title+chip row — the difference between
   "MUKTI N REVIVAL – LIVE IN AU…" and the whole thing. */
.fld-scan-bar__titles {
  display: flex; flex-direction: column; align-items: center;
  max-width: calc(100% - 88px); min-width: 0;
}
.fld-scan-bar__evt {
  /* 14.5px/800 — matches .fld-card__title. Deliberately a touch below the 15px used
     when this was a single row: the subtitle underneath now carries the secondary
     information, so the title no longer has to carry the whole bar on its own, and the
     smaller size fits ~34 characters instead of ~30. */
  font-size: 14.5px; font-weight: 800; color: var(--fld-ink);
  line-height: 1.2; text-align: center;
  /* Slight negative tracking, per §4's convention for heavy weights. At 800 the
     default spacing is loose enough to cost ~3% of the line — which is the margin
     between a 34-character event title fitting and clipping on a 390px phone. */
  letter-spacing: -.01em;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fld-scan-bar__count {
  /* Semantic green (§10 — admitted/success), as TEXT not a pill: a pill here competed
     with the title for width and read as a status badge rather than live progress. */
  font-size: 11.5px; font-weight: 700; color: var(--fld-ok);
  margin-top: 2px; white-space: nowrap;
}
/* LEGACY (Phase 33) — the count is a subtitle now, not a right-hand pill. Rule kept
   per §14.8's deprecated-class convention; no template references it. */
.fld-scan-bar__chip {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%); margin-top: -6px;
  font-size: 11px; font-weight: 800; color: var(--fld-ok);
  background: var(--fld-okbg); border-radius: 99px; padding: 3px 10px;
}

/* Action strip below the result banner — torch + enter-code + sync.
   Replaces the old .fld-scan-meta row. */
.fld-scan-actions {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-top: 10px; padding: 0 2px; font-size: 12px; color: var(--fld-muted);
}
.fld-scan-actions__right { display: flex; gap: 8px; align-items: center; }
.fld-scan-actions .fld-btn { padding: 9px 12px; font-size: 12.5px; }

/* Ticket info card inside the scan confirm sheet.
   Plum left-border gives a "ticket stub" cue without full skeuomorphism. */
.fld-ticket-info {
  background: var(--fld-tint); border: 1px solid var(--fld-line2);
  border-left: 4px solid var(--fld-brand);
  border-radius: 16px; padding: 16px 16px 16px 14px; margin-bottom: 6px;
}
.fld-ticket-info__name {
  font-size: 18px; font-weight: 800; color: var(--fld-ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fld-ticket-info__tier {
  font-size: 11px; font-weight: 700; color: var(--fld-brand);
  text-transform: uppercase; letter-spacing: .07em; margin-top: 5px;
}
.fld-ticket-info__num {
  font-family: 'JetBrains Mono', monospace;
  font-size: 12.5px; color: var(--fld-muted2); letter-spacing: 1.5px; margin-top: 8px;
  display: block;
}

/* Error sheet title modifiers — invalid (red) vs dup/already (amber) */
.fld-sheet__title--invalid { color: var(--fld-bad); }
.fld-sheet__title--dup     { color: #b87514; }

/* Dup ticket info card — amber left-border instead of Plum to signal "already used" */
.fld-ticket-info--dup {
  background: #fffbeb; border-left-color: #f59e0b;
}
.fld-ticket-info--dup .fld-ticket-info__tier { color: #b87514; }

/* Confirm sheet action row — 48 px tap-height (Apple HIG / Android material touch spec). */
.fld-scan-confirm__acts { display: flex; gap: 10px; margin-top: 20px; }
.fld-scan-confirm__acts .fld-btn { min-height: 48px; }
.fld-scan-confirm__acts .fld-btn:first-child { flex: 1; }
.fld-scan-confirm__acts .fld-btn--admit { flex: 1.6; min-width: 0; font-size: 15px; }

/* ── Bottom navigation (RBAC-gated tabs) ──────────────────────────────────── */
/* Floating light pill (Phase 32 shell redesign) — replaces the old edge-to-edge
   white bar. Inset from both side edges and lifted off the true viewport
   bottom via `bottom`, so its own background never needs the old bar's large
   bottom padding to clear the home-indicator/safe-area — the offset does
   that instead. Still `position: fixed` (not `absolute` — this must stay
   pinned during scroll; only a static single-screen mockup could get away
   with `absolute`). Active-tab gradient fill is the one deliberate colour
   moment; inactive icons use --fld-muted2 (a real token — an earlier pass
   used a hardcoded #B29CE0 on a near-black bar, corrected). */
.fld-bottomnav { position: fixed; left: 14px; right: 14px;
  bottom: calc(16px + env(safe-area-inset-bottom)); z-index: 40;
  display: flex; background: var(--fld-card); border: 1px solid var(--fld-line2);
  border-radius: 22px; padding: 6px; box-shadow: var(--fld-shadow-lg); }
.fld-tab { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 8px 4px 7px; border-radius: 16px; text-decoration: none; color: var(--fld-muted2);
  font-size: 10px; font-weight: 700; position: relative; transition: background .15s, color .15s; }
.fld-tab svg { display: block; width: 23px; height: 23px; }
.fld-tab--active { background: linear-gradient(135deg, var(--fld-brand), var(--fld-brand2));
  color: #fff; box-shadow: 0 6px 14px -6px rgba(109,40,217,.45); }
.fld-tab__badge { position: absolute; top: -3px; left: 50%; margin-left: 5px; min-width: 15px; height: 15px;
  background: var(--fld-bad); color: #fff; border-radius: 99px; font-size: 9.5px; font-weight: 800;
  display: flex; align-items: center; justify-content: center; padding: 0 3px; }

/* ── Toasts (driven by the `toast` HX-Trigger event) ──────────────────────── */
/* 88px clears the floating .fld-bottomnav pill's top edge (Phase 32). */
.fld-toasts { position: fixed; left: 16px; right: 16px; bottom: calc(88px + env(safe-area-inset-bottom));
  z-index: 101; display: flex; flex-direction: column; gap: 8px; pointer-events: none; }
.fld-toast { background: var(--fld-ink); color: #fff; border-radius: 14px; padding: 13px 16px;
  font-size: 13.5px; font-weight: 600; box-shadow: 0 16px 36px -12px rgba(0,0,0,.5);
  animation: fld-toast .22s ease; display: flex; align-items: center; gap: 10px; }
.fld-toast__icon { font-weight: 800; }
.fld-toast--good  .fld-toast__icon { color: #5ee3a4; }
.fld-toast--error .fld-toast__icon { color: #ff8d7a; }

/* ── Loader overlay ───────────────────────────────────────────────────────── */
.fld-loader { position: fixed; inset: 0; z-index: 100; background: rgba(250,247,255,.82);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px;
  backdrop-filter: blur(2px); }
.fld-loader[hidden] { display: none; }   /* author rule beats the UA [hidden] default */
.fld-loader__spin { width: 42px; height: 42px; border-radius: 50%; border: 3.5px solid #EDE9FE;
  border-top-color: var(--fld-brand); animation: fld-spin .7s linear infinite; }
.fld-loader__t { font-size: 13.5px; font-weight: 700; color: #6B5E8A; }
@media (prefers-reduced-motion: reduce) {
  /* Every other animated component in this file already respects this —
     the loader spinner was the one gap. Static ring + the "Working…" text
     still communicate busy state without the spin. */
  .fld-loader__spin { animation: none; }
}

/* ── Install banner (Phase 32 — §9.1 FIELD_DESIGN.md) ─────────────────────────
   Snackbar-style "Add to Home Screen" prompt. Android gets a real "Install"
   button wired to the captured beforeinstallprompt event; iOS Safari (no such
   event exists there) gets manual Share-sheet instructions instead. Hidden
   entirely once running standalone — see the script in base_field.html. */
.fld-install {
  position: fixed; left: 12px; right: 12px; z-index: 90;
  bottom: calc(12px + env(safe-area-inset-bottom));
  background: var(--fld-card); border: 1px solid var(--fld-line2);
  border-radius: 18px; box-shadow: var(--fld-shadow-lg);
  padding: 14px 34px 14px 14px; display: flex; align-items: flex-start; gap: 10px;
  animation: fld-toast .22s ease;
}
.fld-install[hidden] { display: none; }
/* Sits above the bottom nav on authenticated pages so it never overlaps the tabs. */
/* 84px clears the floating .fld-bottomnav pill's top edge (Phase 32). */
.fld-install--hasnav { bottom: calc(84px + env(safe-area-inset-bottom)); }
.fld-install__icon {
  flex: none; width: 38px; height: 38px; border-radius: 11px;
  background: var(--fld-brand); display: flex; align-items: center; justify-content: center;
  box-shadow: 0 6px 16px -6px rgba(109,40,217,.55);
}
.fld-install__text { flex: 1; min-width: 0; }
.fld-install__title { font-size: 13.5px; font-weight: 800; color: var(--fld-ink); }
.fld-install__body { font-size: 12px; color: var(--fld-muted); margin-top: 2px; line-height: 1.45; }
.fld-install__cta {
  margin-top: 9px; padding: 8px 16px; min-height: 36px; font-size: 12.5px;
}
.fld-install__close {
  position: absolute; top: 8px; right: 8px; width: 28px; height: 28px;
  border-radius: 50%; border: none; background: var(--fld-tint); color: var(--fld-muted);
  font-size: 13px; line-height: 1; cursor: pointer;
}

/* ── Login (full-screen Plum gradient) ────────────────────────────────────── */
.fld-login { min-height: 100vh; min-height: 100dvh;
  background: radial-gradient(130% 80% at 50% 0%, #7C3AED 0%, #5B21B6 48%, #3B0764 100%);
  display: flex; flex-direction: column;
  /* Top inset was a flat 78px with no env(safe-area-inset-top) — inconsistent
     with .fld-top's calc(14px + env(safe-area-inset-top)) pattern, and the one
     remaining place on this screen a large Dynamic-Island-class inset could
     crowd the brand mark under the notch. Bottom already did this correctly. */
  padding: calc(78px + env(safe-area-inset-top)) 26px calc(30px + env(safe-area-inset-bottom));
  overflow-y: auto;
  /* This div (not <body>) is the actual scroll container on short viewports
     (e.g. the 3-field change-password form) — needs its own overscroll-behavior
     so a bounce at its top/bottom doesn't chain into the page (Phase 32 §9). */
  overscroll-behavior: contain; }
.fld-login__brandwrap { display: flex; flex-direction: column; align-items: center; text-align: center; margin-bottom: 30px; }
.fld-login__mark { width: 88px; height: 88px; border-radius: 24px; background: rgba(255,255,255,.12);
  border: 1px solid rgba(255,255,255,.25); display: flex; align-items: center; justify-content: center; margin-bottom: 16px; }
.fld-login__mark::after { content: ""; width: 24px; height: 24px; border: 3px solid #fff; border-radius: 7px; }
img.fld-login__mark { background: none; border: none; box-shadow: none; object-fit: contain; }
.fld-login__brand { font-size: 26px; font-weight: 800; color: #fff; letter-spacing: -.02em; }
.fld-login__brand .np { color: #DDD6FE; }
.fld-login__kicker { font-size: 13px; font-weight: 600; color: rgba(255,255,255,.62); margin-top: 5px; letter-spacing: .02em; }
.fld-login__card { background: #fff; border-radius: 22px; padding: 22px 20px; box-shadow: 0 30px 60px -24px rgba(0,0,0,.5); }
.fld-login__title { font-size: 18px; font-weight: 800; color: var(--fld-ink); margin-bottom: 2px; }
.fld-login__sub { font-size: 12.5px; color: var(--fld-muted); margin-bottom: 16px; }
.fld-login__form { display: flex; flex-direction: column; }
.fld-login__form .fld-input { margin-bottom: 13px; }
.fld-login__pwd { position: relative; }
.fld-login__pwd .fld-input { padding-right: 58px; }
/* min-height:44px meets the Apple HIG / WCAG 2.5.5 tap-target rule (§9, HARD) —
   was 6px padding (~24px tall). translateY(-50%) keeps it vertically centred on
   the input regardless of its own box height, so this doesn't shift the visible
   "Show"/"Hide" text position. */
.fld-login__eye { font-family: inherit; cursor: pointer; position: absolute; right: 4px; top: 21px;
  transform: translateY(-50%); background: none; border: none; color: var(--fld-accent); font-size: 12px; font-weight: 700;
  padding: 6px 10px; min-height: 44px; min-width: 44px; display: flex; align-items: center; justify-content: center; }
.fld-login__submit { margin-top: 5px; width: 100%; padding: 15px; font-size: 15px; }
.fld-powered {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  margin-top: auto; padding-top: 24px;
}
.fld-powered__row {
  display: flex; align-items: center; justify-content: center; gap: 9px;
}
.fld-powered__label {
  font-size: 11px; font-weight: 700; color: rgba(255,255,255,.7); letter-spacing: .08em; text-transform: uppercase;
}
.fld-powered__logo { height: 26px; width: auto; display: block; }
.fld-login__foot { text-align: center; font-size: 10px; color: rgba(255,255,255,.38); margin-top: 4px; padding-bottom: calc(10px + env(safe-area-inset-bottom)); }
.fld-login .fld-error { margin-bottom: 14px; }
.fld-input--err { border-color: var(--fld-bad) !important; }
.fld-field-err { font-size: 11.5px; color: var(--fld-bad); font-weight: 600; margin: 3px 0 6px; }
.fld-chpw-banner { background: #fef3c7; border: 1px solid #fcd34d; color: #92400e; border-radius: 10px;
  font-size: 12.5px; font-weight: 600; padding: 10px 13px; margin-bottom: 14px; line-height: 1.4; }
/* padding (not just margin) gives this a real ≥44px tap height — was bare text
   with only its own line-height (~18px) clickable, failing the §9 tap-target
   rule. margin-top trimmed 16→3px so the padding addition keeps the same
   ~16px visual gap from the button above. */
.fld-chpw-back { display: block; text-align: center; margin-top: 3px; padding: 13px 0; font-size: 13px; font-weight: 600;
  color: var(--fld-accent); text-decoration: none; }
.fld-chpw-back:hover { text-decoration: underline; }
.fld-login__form .fld-label { margin-top: 0; }
.fld-login__form--busy { opacity: .7; pointer-events: none; }
.fld-label--tight { margin-top: 4px; }
.fld-login__submit--gap { margin-top: 18px; }

/* ── Overview dashboard (first tab — live ticketing operations overview,
   renamed from "Command" Phase 32 part 3) ────────────────────────────────
   Mobile-first single column within .fld-main (640px cap). Redesigned from
   the v1 gradient-hero/stacked-cards layout to: a single glanceable ring
   (issued/capacity) with a permission-gated breakdown beside it, a
   horizontal tier carousel, and one segmented Field Ops/Check In panel —
   see docs/architecture/platform/field-ops/ for the full rationale. */

/* Refresh affordance — replaces the removed page-title row (the header's
   event switcher already answers "what am I looking at"). Overlays the
   hero card's own top-right corner via `position: absolute`, which by
   definition doesn't add to the parent's box size — no extra row, no
   taller hero, regardless of the button's own 44px tap target. */
.fld-cmd-refresh__btn {
  position: absolute; top: 6px; right: 6px; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 12px;
  color: var(--fld-accent); text-decoration: none;
}
.fld-cmd-refresh__btn:active { background: var(--fld-tint); }

/* Attention card — the one thing worth interrupting the scroll for. Only
   rendered when something actually needs a look (sync-stuck batches or
   quiet gates); a permanently-visible "all clear" panel trains people to
   ignore it. Reuses the amber .fld-syncwarn palette, not a new colour. */
.fld-attn {
  background: #FDF4E6; border: 1px solid #F0DDBC; /* same amber pair as .fld-syncwarn */
  border-radius: 14px; padding: 10px 11px; margin-bottom: 14px;
}
.fld-attn__head { display: flex; align-items: center; gap: 6px; margin-bottom: 7px; }
.fld-attn__icon {
  width: 18px; height: 18px; border-radius: 6px; background: var(--fld-warn); color: #fff;
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.fld-attn__icon svg { width: 10px; height: 10px; }
.fld-attn__title { font-size: 10.5px; font-weight: 800; color: #9A7433; letter-spacing: .02em; }
.fld-attn__list { display: flex; flex-direction: column; gap: 5px; }
.fld-attn__row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  background: rgba(255,255,255,.55); border-radius: 9px; padding: 7px 9px;
}
.fld-attn__row-t { font-size: 11px; font-weight: 700; color: var(--fld-ink); }
.fld-attn__row-s { font-size: 9.5px; color: var(--fld-muted); margin-top: 1px; }
.fld-attn__row-r { font-family: 'JetBrains Mono', monospace; font-size: 10px; font-weight: 600; color: #9A7433; flex-shrink: 0; }

/* Hero — a single glanceable ring (issued/capacity), not three numbers in a
   row. CSS conic-gradient donut, no SVG/JS needed. */
.fld-cmd-hero {
  position: relative;
  background: var(--fld-card); border: 1px solid var(--fld-line2); border-radius: 20px;
  padding: 18px 16px; margin: 2px 0 16px; box-shadow: var(--fld-shadow);
  display: flex; align-items: center; gap: 16px;
}
.fld-cmd-hero__ring {
  --pct: 0; width: 100px; height: 100px; border-radius: 50%; flex-shrink: 0; position: relative;
  background: conic-gradient(from -90deg, var(--fld-brand), var(--fld-brand2) calc(var(--pct)*1%), var(--fld-line) 0);
}
.fld-cmd-hero__ring::before { content: ""; position: absolute; inset: 10px; background: var(--fld-card); border-radius: 50%; }
.fld-cmd-hero__ring-c { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.fld-cmd-hero__ring-n { font-size: 20px; font-weight: 800; color: var(--fld-ink); letter-spacing: -.01em; line-height: 1; }
.fld-cmd-hero__ring-lbl { font-size: 8.5px; font-weight: 700; color: var(--fld-muted2); text-transform: uppercase; letter-spacing: .05em; margin-top: 2px; }
.fld-cmd-hero__side { flex: 1; min-width: 0; }
.fld-cmd-hero__of { font-size: 12.5px; font-weight: 700; color: var(--fld-muted); margin-bottom: 6px; }
.fld-cmd-hero__of b { color: var(--fld-ink); font-weight: 800; }
.fld-cmd-hero__row { display: flex; align-items: center; gap: 7px; font-size: 11px; padding: 5px 0; border-top: 1px solid var(--fld-line); }
.fld-cmd-hero__row:first-of-type { border-top: none; }
.fld-cmd-hero__row svg { flex-shrink: 0; color: var(--fld-faint); }
.fld-cmd-hero__row span { flex: 1; min-width: 0; color: var(--fld-muted); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fld-cmd-hero__row b { flex-shrink: 0; color: var(--fld-ink); font-weight: 800; font-family: 'JetBrains Mono', monospace; font-size: 11.5px; }
.fld-cmd-hero__row--warn svg { color: var(--fld-warn); }
.fld-cmd-hero__row--warn b { color: var(--fld-warn); }

/* One card, a flowing stat row (hairline dividers between columns, not
   per-cell borders — the outer .fld-panel already supplies the frame) plus
   a progress bar. Field Ops / Check In share this shape. */
.fld-panel {
  background: var(--fld-card); border: 1px solid var(--fld-line2); border-radius: 16px;
  padding: 10px; box-shadow: var(--fld-shadow); margin-bottom: 14px;
}
.fld-stats { display: flex; margin-bottom: 10px; }
.fld-stats__col { flex: 1; min-width: 0; padding: 0 10px; }
.fld-stats__col:first-child { padding-left: 0; }
.fld-stats__col:last-child { padding-right: 0; }
.fld-stats__col + .fld-stats__col { border-left: 1px solid var(--fld-line2); }
.fld-stats__top { display: flex; align-items: center; gap: 5px; }
.fld-stats__top svg { flex-shrink: 0; color: var(--fld-faint); width: 13px; height: 13px; }
.fld-stats__n { font-size: 18px; font-weight: 800; color: var(--fld-ink); letter-spacing: -.01em; line-height: 1.1; }
.fld-stats__lbl { font-size: 9px; font-weight: 800; letter-spacing: .03em; text-transform: uppercase; color: var(--fld-muted2); margin-top: 4px; }
.fld-stats__lbl--ok { color: var(--fld-ok); }
.fld-stats__lbl--warn { color: var(--fld-warn); }
.fld-stats__lbl--bad { color: var(--fld-bad); }
.fld-stats__sub { font-size: 9.5px; color: var(--fld-muted); margin-top: 2px; }
.fld-panel__bar { height: 5px; border-radius: 99px; background: var(--fld-line); overflow: hidden; margin-bottom: 6px; }
.fld-panel__fill { height: 100%; background: linear-gradient(90deg, var(--fld-brand), var(--fld-brand2)); border-radius: 99px; }
.fld-panel__cap { font-size: 10px; font-weight: 700; color: var(--fld-muted); }

/* Tiers — horizontal scroll-snap carousel, not a vertical stacked list;
   each tier's own inner content (name/price/bar/meta/tag) is unchanged. */
.fld-cmd-tiers {
  display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x mandatory;
  margin: 0 0 16px; padding: 0 0 4px; scrollbar-width: none;
}
.fld-cmd-tiers::-webkit-scrollbar { display: none; }
/* Single tier — nothing to scroll to, so let the card fill the row instead
   of sitting at its 156px carousel width with dead space beside it. */
.fld-cmd-tiers--single { overflow-x: visible; scroll-snap-type: none; }
.fld-cmd-tier {
  scroll-snap-align: start; flex: 0 0 156px;
  background: var(--fld-card); border: 1px solid var(--fld-line2); border-radius: 14px;
  padding: 12px; box-shadow: var(--fld-shadow);
}
.fld-cmd-tiers--single .fld-cmd-tier { flex: 1 1 auto; }
.fld-cmd-tier__top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.fld-cmd-tier__name { font-size: 14px; font-weight: 800; color: var(--fld-ink); letter-spacing: -.01em;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fld-cmd-tier__price { font-size: 12.5px; font-weight: 700; color: var(--fld-accent); flex-shrink: 0; }
.fld-cmd-tier__bar { height: 5px; border-radius: 99px; background: var(--fld-tint); overflow: hidden; margin-bottom: 7px; }
.fld-cmd-tier__fill { height: 100%; border-radius: 99px; background: linear-gradient(90deg, #6D28D9, #8B5CF6); }
.fld-cmd-tier__fill.is-low  { background: var(--fld-warn); }
.fld-cmd-tier__fill.is-full { background: var(--fld-bad); }
.fld-cmd-tier__meta { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.fld-cmd-tier__sold  { font-size: 11.5px; color: var(--fld-muted2); }
.fld-cmd-tier__avail { font-size: 11.5px; font-weight: 700; color: var(--fld-ok); flex-shrink: 0; }
.fld-cmd-tier__tag {
  font-size: 10.5px; font-weight: 800; letter-spacing: .02em; flex-shrink: 0;
  padding: 2px 9px; border-radius: 99px; background: var(--fld-tint); color: var(--fld-accent);
}
.fld-cmd-tier__tag--warn { background: #fdf2e3; color: var(--fld-warn); }
.fld-cmd-tier__tag--bad  { background: var(--fld-badbg); color: var(--fld-bad); }

/* ── Offline fallback page (offline.html) ── */
.off-wrap {
  min-height: 100dvh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 20px;
  padding: 32px 24px calc(32px + env(safe-area-inset-bottom));
  background: var(--fld-bg); text-align: center;
}
.off-mark {
  width: 72px; height: 72px; border-radius: 20px; background: var(--fld-brand);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.off-mark svg { color: #fff; }
.off-h { font-size: 20px; font-weight: 800; color: var(--fld-ink); letter-spacing: -.01em; margin: 0; }
.off-sub {
  font-size: 14px; font-weight: 600; color: var(--fld-muted);
  margin: 0; max-width: 280px; line-height: 1.5;
}
.off-pill {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--fld-tint); border: 1.5px solid var(--fld-line2);
  border-radius: 100px; padding: 6px 14px;
  font-size: 12px; font-weight: 700; color: var(--fld-muted2);
  letter-spacing: .04em; text-transform: uppercase;
}
.off-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--fld-faint); animation: fld-pulse 1.4s ease-in-out infinite;
}
.off-retry { margin-top: 8px; }

/* ── Scanner audit (Phase 33, 2026-08-08) ────────────────────────────────────
   Three additions, all on --fld-* tokens per §11 (no inline colour, no new
   namespace). See FIELD_DESIGN.md §7 Component Inventory. */

/* Squares off the identity card's bottom corners so it fuses with the panel below.
   Applied via an Alpine class binding, NOT `:has(+ .fld-prior)` — x-show hides the
   panel with display:none while leaving it in the DOM, so a sibling selector would
   round-off the wrong corners whenever there is no prior admission to report. */
.fld-ticket-info--joined { border-radius: 16px 16px 0 0; margin-bottom: 0; }

/* S15 — "what already happened" panel in the duplicate sheet, directly beneath the
   .fld-ticket-info identity card. A tinted second ZONE rather than another line inside
   that card: the two answer different questions (whose ticket is this / why am I being
   stopped), and an operator reads this in seconds with a queue behind them.
   Label/value rows scan vertically; the previous single run-on line did not. */
.fld-prior {
  margin-top: -1px;                      /* sit flush against the card's bottom border */
  border: 1px solid var(--fld-line2);
  border-radius: 0 0 16px 16px;
  /* WHITE against the tinted .fld-ticket-info above it. The identity card is already
     --fld-tint, so tinting this too would erase the boundary between the two zones —
     and dark values on white are the more legible pairing for the time, which is the
     number the operator is actually comparing. */
  background: var(--fld-card);
  padding: 2px 14px;
}
.fld-prior__row { display: flex; align-items: center; gap: 9px; padding: 9px 0; }
.fld-prior__row + .fld-prior__row { border-top: 1px solid var(--fld-line); }
.fld-prior__row svg { flex: 0 0 auto; color: var(--fld-muted2); }
.fld-prior__k { flex: 1; font-size: 11.5px; font-weight: 700; color: var(--fld-muted); }
.fld-prior__v {
  font-size: 14.5px; font-weight: 800; letter-spacing: -.01em;
  color: var(--fld-ink); text-align: right; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Tabular figures so the digits do not shift width between scans — the time is the
   value the operator is actually comparing. */
.fld-prior__v--time { font-variant-numeric: tabular-nums; }
/* Absence is information, not an error: pre-audit rows genuinely have no operator. */
.fld-prior__v--none { font-size: 12.5px; font-weight: 700; font-style: italic; color: var(--fld-muted2); }

/* S10 — sync-failure notice on the scanner dashboard. Amber, not red: the scans
   are SAFE on the device and will drain on reconnect, so this is a warning
   (§10 "pending / warning"), never an error. Mirrors .fld-offline-banner's shape
   so the two read as the same family of status strip. */
.fld-syncwarn {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  background: #FDF4E6;
  border: 1px solid #F0DDBC;
  border-radius: 14px;
  padding: 11px 13px;
  margin-top: 11px;
}
.fld-syncwarn::before {
  content: "";
  flex: 0 0 auto;
  width: 9px; height: 9px;
  margin-top: 4px;
  border-radius: 50%;
  background: var(--fld-warn);
}
.fld-syncwarn b { display: block; font-size: 13px; color: var(--fld-warn); }
.fld-syncwarn span { font-size: 11.5px; color: #9A7433; }
.fld-syncwarn[hidden] { display: none; }

/* ── .fld-queuebar — the ONE queue notice, on every authenticated tab (audit N3/N4) ──
   Sits directly under the top bar, above <main>, so it is impossible to scroll past.
   field-queue.js decides which single message renders (held-back > not-syncing >
   waiting) — never more than one, because stacked warnings become wallpaper.

   Severity is driven by queue AGE, not count. A count alone is unreadable: at a gate
   doing 500/hour, "12 waiting" is completely routine, while "12 waiting · oldest 45 min
   ago" is unmistakably wrong. So the resting state is deliberately QUIET (neutral,
   informational — the queue draining is normal operation, not a problem), and it
   escalates to amber past 15 minutes and red past an hour.

   Colours follow §10: amber = warning, red = error, never brand Plum for either. */
.fld-queuebar {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 8px;
  padding: 9px 16px;
  background: var(--fld-tint);
  border-bottom: 1px solid var(--fld-line2);
}
.fld-queuebar b    { font-size: 12.5px; font-weight: 800; color: var(--fld-ink); }
.fld-queuebar span { font-size: 11.5px; color: var(--fld-muted); }
.fld-queuebar[hidden] { display: none; }

/* Amber — the queue has aged past AGE_WARN_MIN, or a flush is failing. Same tinted
   surface + darkened body copy as .fld-syncwarn so the two read as one family. */
.fld-queuebar--warn,
.fld-queuebar--held { background: #FDF4E6; border-bottom-color: #F0DDBC; }
.fld-queuebar--warn b,
.fld-queuebar--held b    { color: var(--fld-warn); }
.fld-queuebar--warn span,
.fld-queuebar--held span { color: #9A7433; }

/* Red — past AGE_BAD_MIN. Reserved for "this needs acting on now": an hour-old queue at
   a live gate means something is genuinely wrong, not merely slow. */
.fld-queuebar--bad      { background: var(--fld-badbg); border-bottom-color: #F0C8C2; }
.fld-queuebar--bad b    { color: var(--fld-bad); }
.fld-queuebar--bad span { color: #A4463A; }

/* S6 — spacing for the manual-entry reason field, which sits directly under the
   ticket-code input in the manual sheet. Geometry only. */
.fld-label--mt { margin-top: 14px; }

/* Sync-warning variants (scanner audit P1) ────────────────────────────────────
   --inline: the same notice inside the ACTIVE scan view. An operator mid-shift never
   returns to the dashboard, so a dashboard-only warning is invisible precisely when
   the queue is stuck. Tighter than the dashboard copy — it sits under the camera.
   --static: the "you cannot scan at all" notice (no field.scanner.sync grant). Excluded
   from the JS `hidden` toggle by selector, because it is a permanent state of the
   account, not a transient sync failure. */
.fld-syncwarn--inline { margin-top: 9px; padding: 9px 12px; }
.fld-syncwarn--inline b { font-size: 12.5px; }
.fld-syncwarn--inline span { font-size: 11px; }
.fld-syncwarn--static { margin-top: 11px; }
.fld-syncwarn__age { font-weight: 800; }

