/* ITZA Tab - the app shell and components.
 *
 * Loaded AFTER tab.css (tokens, themes, base) and receipt.css (the
 * harvested receipt-line columns and the + fan menu). tab.css stays the
 * authority on colour and type; this file is the machinery that makes the
 * product read as an app - the pane stack, the dock, sheets, the capture
 * surface - plus Tab's own components. Where files disagree, this one is
 * later in the cascade on purpose.
 *
 * EVERY COLOUR RIDES THE TOKENS. No literal colour appears here outside
 * var() fallbacks and the sign-in card's own scoped --ob-* palette, so a
 * theme change re-tints the room without touching what anything means.
 *
 * Z-INDEX CONVENTIONS (shared with receipt.css - do not drift):
 *   panes stack by DOM order; sticky bars 20; dock 30; #scrim/#modal 60;
 *   scrim2 80; TAPPABLE-above-scrim 81 (sheet, drawer, fab-pop); toast 90;
 *   lockgate 300. Anything meant to be tapped above a scrim must clear it.
 *
 * The iOS field floor lives at the BOTTOM OF THIS FILE and must stay the
 * last font-size rule in the whole cascade. Do not add font-size rules
 * after it, in this file or any stylesheet loaded later.
 */

/* ---------------------------------------------------------------------
   The shell: screens, view, panes
   --------------------------------------------------------------------- */

.screen { position: fixed; inset: 0; display: none; }
.screen.on { display: block; }
.screen:focus { outline: none; }

#screen-app { display: none; }
#screen-app.on { display: flex; flex-direction: column; }

#view { position: relative; flex: 1; min-height: 0; }

.pane {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  background-color: var(--paper);
  padding-bottom: var(--dock);
  /* EVERY PANE IS ITS OWN STACKING CONTEXT. Without this, a covered
     pane's positioned children (sticky bars, relative cards) paint
     straight through the pane on top of them - an earlier ITZA app's
     timeline floated over its open editor. With isolation, DOM order alone decides which
     pane wins, which is exactly the stack. */
  isolation: isolate;
}

/* A covered pane keeps its DOM (scroll memory, decoded thumbnails) but
   cannot be reached through the pane above it. */
.pane-under { pointer-events: none; }

/* Push/pop: a pane that MOVES reads as an app; one that swaps reads as a
   web page. The moving pane carries an edge shadow so the motion has
   depth - the same shadow an edge-swipe drag should show. */
@keyframes paneIn {
  from { transform: translate3d(100%, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}
@keyframes paneOut {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(100%, 0, 0); }
}
@keyframes paneFade {
  from { opacity: 0; transform: translate3d(0, 4px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}
.pane-enter {
  animation: paneIn var(--m-enter) var(--ease-enter);
  box-shadow: -24px 0 48px rgba(0, 0, 0, 0.35);
}
.pane-exit {
  animation: paneOut var(--m-exit) var(--ease-exit) forwards;
  z-index: 5;
  box-shadow: -24px 0 48px rgba(0, 0, 0, 0.35);
}
.pane-fade { animation: paneFade 180ms ease-out; }

/* ---------------------------------------------------------------------
   Top bars
   --------------------------------------------------------------------- */

.tbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 4px;
  min-height: 54px;
  padding: calc(var(--safe-t) + 4px) calc(8px + var(--safe-r)) 4px calc(8px + var(--safe-l));
  background: var(--frost);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  backdrop-filter: blur(18px) saturate(1.3);
  border-bottom: 1px solid var(--rule);
  font-family: var(--sans);
}

/* A bar laid over a photo header: transparent, and absolute so the image
   runs to the top edge behind it. */
.tbar.overhero {
  background: transparent;
  border-bottom-color: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  position: absolute;
  left: 0; right: 0;
}

.tbar-title {
  flex: 1;
  text-align: center;
  font-family: var(--serif-display);
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tbar .wm { font-size: 1.02rem; }

.tbtn {
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  background: transparent;
  border-radius: var(--r-full);
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex: none;
}
.tbtn svg { width: 22px; height: 22px; }
.tbtn:active { transform: scale(0.94); }

/* A control a thumb does not need and a pointer cannot do without: the
   phone has a system back, the desktop has none. VISIBILITY, not
   display, so the button keeps its 44px and the title stays centred. */
.tbtn-pointer { visibility: hidden; }
@media (hover: hover) and (pointer: fine) {
  .tbtn-pointer { visibility: visible; }
}

/* ---------------------------------------------------------------------
   The dock - floating bottom bar with the raised capture action
   --------------------------------------------------------------------- */

.dock {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  /* Clears the home-indicator GLYPH, not the whole gesture region -
     safe-b + air read as hovering, measured on real phones. */
  bottom: max(8px, calc(var(--safe-b) - 8px));
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 6px;
  height: var(--bar-h);
  padding: 0 14px;
  border-radius: var(--r-full);
  background: var(--frost);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  backdrop-filter: blur(18px) saturate(1.3);
  border: 1px solid var(--rule);
  box-shadow: var(--shadow-float);
}

.dock-btn {
  width: 58px;
  height: 48px;
  border: none;
  background: transparent;
  border-radius: var(--r-md);
  color: var(--muted);
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  cursor: pointer;
  font-family: var(--sans);
  font-size: 0.62rem;
  padding: 0;
}
.dock-btn svg { width: 23px; height: 23px; }
.dock-btn.on { color: var(--accent); }
.dock-btn:active { transform: scale(0.94); }

/* The raised centre action: the camera. Accent, never gold - capture is
   chrome; the SEAL is the gold moment. */
.fab {
  width: 56px;
  height: 56px;
  margin: 0 8px;
  border: none;
  border-radius: var(--r-full);
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: var(--shadow-float);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transform: translateY(-14px);
  transition: transform var(--m-tap) ease-out;
}
.fab svg { width: 26px; height: 26px; }
.fab:active { transform: translateY(-14px) scale(0.93); }

/* The dock leaves while capturing, reviewing, or standing at a gate -
   nobody needs Settings mid-shutter, and the review screen's own actions
   must not sit under it. */
html[data-route='capture'] .dock,
html[data-route='review'] .dock,
html[data-route='write'] .dock,
html[data-route='import'] .dock,
html[data-route='gate'] .dock,
html[data-route='shared'] .dock { display: none; }
/* THE HIDDEN ATTRIBUTE MUST ALWAYS WIN. `.dock { display:flex }` would
   beat the UA's [hidden] rule and a guest would watch the owner's dock
   sit there - a shipped bug upstream, twice. */
.dock[hidden] { display: none !important; }
html[data-route='capture'] .pane[data-route='capture'] { padding-bottom: 0; }

/* NOTE: .fab-pop / .fab-pill (the + fan menu) live in receipt.css - the
   harvested block. Do not restyle them here; they already ride the
   tokens and the z-81 convention. */

/* ---------------------------------------------------------------------
   Skeletons - never spinners for content (spec 8.3). A skeleton is the
   shape of what is coming; a spinner is an apology for it.
   --------------------------------------------------------------------- */

.skel {
  border-radius: var(--r-md);
  border: 1px solid var(--rule);
  height: 74px;
  margin: 10px 0;
  position: relative;
  overflow: hidden;
  background: color-mix(in srgb, var(--rule) 30%, var(--paper));
}
@keyframes skel { to { transform: translateX(100%); } }

/* The shapes Tab actually loads: a square photo, a text line, a whole
   receipt row (thumb + two lines + amount, drawn as one plate). */
.skel-thumb { width: 56px; height: 56px; margin: 0; flex: none; border-radius: var(--r-sm); }
.skel-line.short { width: 55%; }

/* An image on its way: same shimmer language, image-sized. */
.imgskel {
  min-height: 200px;
  border-radius: var(--r-md);
  border: 1px solid var(--rule);
  background: color-mix(in srgb, var(--rule) 30%, var(--paper));
  position: relative;
  overflow: hidden;
}

/* The one legal spinner: a BUTTON at work for under a second - never a
   stand-in for content. */
.spinner {
  display: inline-block;
  width: 13px; height: 13px;
  border: 2px solid var(--rule);
  border-top-color: var(--blue);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  vertical-align: -2px;
  margin-right: 4px;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* The waiting line for chain reads: small, calm, in motion so the app
   never looks stuck. */
.loading {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--sans);
  font-size: 0.85rem;
  color: var(--muted);
  padding: 1.5rem 0;
}
.loading-dots { display: inline-flex; gap: 4px; }
.loading-dots i {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--muted);
  opacity: 0.35;
  animation: loadingPulse 1.1s ease-in-out infinite;
}
.loading-dots i:nth-child(2) { animation-delay: 0.16s; }
.loading-dots i:nth-child(3) { animation-delay: 0.32s; }
@keyframes loadingPulse {
  0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
  40% { opacity: 1; transform: translateY(-3px); }
}

/* ---------------------------------------------------------------------
   Toast
   --------------------------------------------------------------------- */

#toast {
  position: fixed;
  left: 50%;
  /* Above the dock, always - feedback about the thing you just did must
     never be buried under the bar you did it with. */
  bottom: calc(var(--bar-h) + var(--safe-b) + 28px);
  transform: translateX(-50%) translateY(1rem);
  background: var(--navy);
  color: var(--paper);
  font-family: var(--sans);
  font-size: 0.82rem;
  line-height: 1.4;
  padding: 0.7rem 1rem;
  border-radius: var(--r-sm);
  max-width: min(34rem, calc(100vw - 2rem));
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
  z-index: 90;
}
#toast.on { opacity: 1; transform: translateX(-50%) translateY(0); }
/* A toast CARRYING AN ACTION must accept the tap - the base rule lets
   touches pass through pure feedback, which once turned an Undo
   button into decoration on a real phone, upstream. */
#toast.has-act { pointer-events: auto; }

/* The undo action: the one gold-adjacent control outside the seal - it
   is the moment of KEEPING something, which is the product's promise. */
.toast-act {
  margin-left: 10px;
  border: none;
  cursor: pointer;
  font-family: var(--sans);
  font-size: 0.8rem;
  font-weight: 700;
  padding: 4px 14px;
  border-radius: var(--r-full);
  background: var(--gold);
  color: var(--on-gold);
}

/* ---------------------------------------------------------------------
   Modal (#scrim / #modal) and dialog furniture
   --------------------------------------------------------------------- */

#scrim {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  /* ABOVE #screen-onboard.on (z 50): the modal layer must beat every
     screen, onboarding included, or "Create account" opens its
     confirmation underneath an opaque card. */
  z-index: 60;
  padding: 1rem;
  overflow-y: auto;
}
/* Dialogs sit CENTRED in the room; `safe center` keeps one taller than
   the screen reachable from its top instead of clipping both ends. */
#scrim.on { display: flex; align-items: safe center; justify-content: center; }

#modal {
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-float);
  width: 100%;
  max-width: 32rem;
  margin: 3vh auto;
  padding: 1.25rem;
  font-family: var(--sans);
  /* THE APP'S READING SIZE, not a smaller one - a popup that ignores the
     person's text size reads as exactly that. Same rem, same scale. */
  font-size: 1rem;
  /* Nothing may widen the dialog: anything that overflows wraps or
     scrolls inside its own row, never as a dialog-wide scrollbar. */
  overflow-x: hidden;
  /* A dialog taller than the screen scrolls INSIDE itself - it must
     never push its own buttons off the bottom. */
  max-height: calc(100vh - 6vh - 2rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.mhead {
  font-family: var(--serif-display);
  font-size: 1.4rem;
  color: var(--navy);
  margin-bottom: 1rem;
}

/* The button row has a LAYOUT: wide screens right-aligned with the
   primary rightmost; narrow screens stacked full width with the primary
   on top (dialogs write ghost-then-primary in the DOM). */
.mbtns,
.mbtns-foot {
  margin-top: 1.25rem;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}
/* The DIALOG's footer is its own row: when a section above carries its
   own Save, two touching right-aligned rows read as one confused one
   (owner's second-look rule caught it live). A hairline says whose
   button is whose. */
.mbtns-foot {
  border-top: 1px solid var(--rule);
  padding-top: 0.9rem;
}
/* Short pills equalize (owner: Close ran a letter wider than Save, so
   the stacked rows' pills did not column). A minimum width lifts only
   the short labels - Save, Close, Cancel - into one size; long labels
   keep their natural width. */
.mbtns .btn, .mbtns-foot .btn { min-width: 6rem; }
@media (max-width: 480px) {
  .mbtns, .mbtns-foot { flex-direction: column-reverse; align-items: stretch; }
}
.mbtns-stack { flex-direction: column; align-items: stretch; }
.mbtns-stack .btn.wide + .btn.wide { margin-top: 0; }

/* Body copy inside a dialog: UI voice, dialog size. */
.mtext {
  font-family: var(--sans);
  font-size: 0.92rem;
  line-height: 1.55;
  color: var(--ink);
  margin: 0 0 0.8rem;
  max-width: none;
}
.mtext b { color: var(--navy); }
/* A dialog-body figure carries its own hair of air: the space after a
   bold digit measures equal to every word gap yet reads glued, because
   the digit's ink runs to its advance edge. Inline-end so RTL packs
   get the air on the correct side of the number. */
.mtext .num { padding-inline-end: 0.15em; }

/* The quiet third choice under a dialog's buttons. */
.mlink {
  display: block;
  margin: 0.95rem auto 0;
  padding: 0;
  background: none;
  border: none;
  color: var(--muted);
  font-family: var(--sans);
  font-size: 0.78rem;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.mlink:hover { color: var(--ink); }

#modal textarea {
  width: 100%;
  font-family: var(--sans);
  font-size: 0.88rem;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 0.55rem 0.7rem;
  resize: vertical;
}

/* ---------------------------------------------------------------------
   Sheets - the bottom drawer for actions and pickers
   --------------------------------------------------------------------- */

.scrim2 {
  position: fixed;
  inset: 0;
  z-index: 80;
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity var(--m-quick);
}
.scrim2.on { opacity: 1; }

.sheet {
  position: fixed;
  left: 50%;
  transform: translate(-50%, 105%);
  bottom: 0;
  z-index: 81;             /* tappable above the scrim, per the register */
  width: min(100%, 30rem);
  max-height: 86vh;
  overflow-y: auto;
  background: var(--surface);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  box-shadow: var(--shadow-float);
  padding: 8px 18px calc(var(--safe-b) + 18px);
  transition: transform var(--m-enter) var(--ease-enter);
  font-family: var(--sans);
}
.sheet.on { transform: translate(-50%, 0); }

/* The grab handle IS the swipe-down affordance; touch-action none there
   so the drag-to-dismiss gesture never fights the sheet's own scroll.
   Desktop gets Esc, the scrim click and a visible close instead. */
.sheet-grab {
  width: 38px; height: 4px;
  border-radius: var(--r-full);
  background: var(--rule);
  margin: 6px auto 12px;
  touch-action: none;
}
.sheet h3 {
  font-family: var(--serif-display);
  font-size: 1.1rem;
  margin: 0 0 10px;
  color: var(--ink);
}
.sheet-row {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  border: none;
  background: transparent;
  text-align: left;
  padding: 13px 2px;
  font-family: var(--sans);
  font-size: 0.95rem;
  color: var(--ink);
  cursor: pointer;
  border-bottom: 1px solid var(--rule);
}
.sheet-row:last-child { border-bottom: none; }
/* Category picker + trade picker + guide rows (NEXT-BUILD A1/A4). */
.sheet-list { max-height: 52vh; overflow-y: auto; margin-top: 8px; }
.rv-catbtn { text-align: left; cursor: pointer; }
.cat-lim { color: var(--muted); font-size: .85em; margin-left: 6px; }
.qmark {
  margin-left: auto; width: 22px; height: 22px; border-radius: 50%;
  border: 1px solid var(--rule); color: var(--muted);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: .8em; flex: none;
}
.cat-rule { padding: 0 14px 8px; }
.cat-src { opacity: .7; font-style: italic; }
.fr-dots { display: flex; gap: 6px; justify-content: center; margin: 12px 0 0; }
.fr-dots span { width: 6px; height: 6px; border-radius: 50%; background: var(--muted); opacity: .35; }
.fr-dots .on { opacity: 1; }
/* Reports rows (NEXT-BUILD C): a front door each for the analyses. */
.rp-row {
  display: flex; align-items: center; gap: 14px; width: 100%;
  padding: 14px 4px; background: none; border: 0;
  border-bottom: 1px solid var(--rule); text-align: left;
  font: inherit; color: inherit; cursor: pointer;
}
.rp-row svg { flex: none; color: var(--muted); }
.rp-main { display: flex; flex-direction: column; gap: 2px; }
.rp-name { font-weight: 600; }
.rp-note { color: var(--muted); font-size: .85em; }
.rp-net { display: flex; gap: 22px; margin: 6px 0 2px; }
.rp-cell { display: flex; flex-direction: column; }
.rp-cell-l { color: var(--muted); font-size: .8em; }
.rp-neg { color: #b3261e; }
/* The envelope detail's pace (NEXT-BUILD B). */
.bg-pace { width: 100%; height: 96px; display: block; margin: 10px 0 4px; }
.bg-pace-ideal { stroke: var(--muted); stroke-width: 1.5; stroke-dasharray: 5 5; fill: none; }
.bg-pace-actual { stroke: currentColor; stroke-width: 2; fill: none; }
.bg-detail-left { font-size: 1.7rem; font-weight: 700; margin: 2px 0 0; }
#bgCats .bg-name { cursor: pointer; }
/* Who is cheapest (owner, 2026-08-31). */
.px-row { display: flex; align-items: center; gap: 12px; padding: 12px 2px; border-bottom: 1px solid var(--rule); }
.px-row .px-name { flex: 1; display: flex; flex-direction: column; gap: 2px; text-transform: capitalize; }
.px-sub { color: var(--muted); font-size: .8em; }
.px-best { color: var(--muted); font-size: .75em; border: 1px solid var(--rule); border-radius: 999px; padding: 1px 8px; margin-left: 6px; }
.px-prod { padding: 12px 2px; border-bottom: 1px solid var(--rule); }
.px-prod .px-name { display: block; font-weight: 600; margin-bottom: 4px; }
/* Head to head as a SCOREBOARD (owner, 2026-08-31: one flat gray run
 * gave the eye nothing) - each shop is its own card: name small on
 * top, the price the loudest thing in it, the pack note under, the
 * flag pinned to the corner. The winner's card wears the blue. */
.px-cell { position: relative; display: inline-flex; flex-direction: column; align-items: flex-start; gap: 3px; margin: 6px 10px 4px 0; padding: 10px 14px; background: var(--surface); border: 1px solid var(--rule); border-radius: 12px; color: var(--muted); text-transform: capitalize; vertical-align: top; }
.px-shop { font-size: .8em; letter-spacing: .02em; }
.px-cell .num { color: var(--ink); font-weight: 700; font-size: 1.2em; }
.px-cell .px-flag { position: absolute; top: 8px; right: 8px; }
.px-cell:has(.px-flag) { padding-right: 36px; }
.px-cheap { border-color: var(--blue); }
.px-cheap { color: inherit; font-weight: 600; }
.px-pack { font-size: .8em; opacity: .8; }
.px-flag { background: none; border: 0; padding: 2px; color: var(--muted); cursor: pointer; }
/* Mailed-receipt question sheet: six decisions in two honest rows. */
.ml-btns { display: flex; flex-direction: column; gap: 8px; margin: 8px 0; }
.ml-btn { width: 100%; }
/* A circle member: the user name carries the row, the joined date
   rides beneath, and Remove is a visible word on the right (owner,
   2026-09-01: the quiet X read as no way out at all). */
.shc-p { display: flex; flex-direction: column; gap: 2px; text-align: start; flex: 1; }
.shc-drop { color: var(--danger, #c62828); background: none; border: 0; cursor: pointer; font: inherit; font-size: 0.85em; }
/* THE ROW SURVIVES A NAME IT DID NOT CHOOSE (owner, 2026-09-15). A member
   the chain knows but this device has never named has no name to show, and
   an untruncated wallet address pushed the row into a sideways scroll and
   wrapped the action onto three lines. The name takes what is left and
   ellipsises; the action keeps its line. */
.check-row .shc-p { min-width: 0; }
.check-row .shc-p > b {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.check-row .shc-drop { flex: none; white-space: nowrap; }
.px-sizerow { display: flex; align-items: center; gap: 8px; margin: 6px 0 4px; }
.px-sizerow .rv-in { width: 28%; min-width: 64px; text-align: center; }
.px-sizerow select.rv-in { flex: 1; text-align: left; }
.px-x { color: var(--muted); }
/* The unit pills - the house rv-seg, allowed to wrap. */
.rv-seg.px-units { display: flex; flex-wrap: wrap; margin: 2px 0 12px; }
/* The two lenses: your shops, or you against the area's pool. */
.px-scope { margin: 0 0 14px; }
/* The quiet fork between the two tools. */
.px-or { display: flex; align-items: center; gap: 12px; margin: 14px 0 10px; color: var(--muted); font-size: .85em; text-transform: uppercase; letter-spacing: .08em; }
.px-or::before, .px-or::after { content: ''; flex: 1; border-top: 1px solid var(--rule); }
.px-prod[data-hist] { cursor: pointer; }
.px-prod .px-name svg { vertical-align: -2px; color: var(--muted); }
.px-save { font-weight: 600; margin: 4px 0 2px; }
.px-hist { width: 100%; height: 110px; display: block; margin: 8px 0 4px; }
.px-hist-line { stroke: currentColor; stroke-width: 1.8; fill: none; }
.px-legend { display: flex; flex-wrap: wrap; gap: 8px 18px; }
.px-leg { display: inline-flex; align-items: center; gap: 6px; text-transform: capitalize; }
.px-leg svg { width: 26px; height: 8px; }
/* Colour where the eye needs it (owner: too black and white). */
.px-cheap { color: var(--blue); font-weight: 700; }
.px-cheap .num { color: var(--blue); }
.px-best { color: var(--blue); border-color: var(--blue); }
.px-bar { display: block; height: 4px; margin-top: 5px; border-radius: 99px; background: var(--rule); overflow: hidden; max-width: 260px; }
.px-bar i { display: block; height: 100%; background: var(--muted); border-radius: 99px; }
.px-bar .px-bar-best, .px-bar i.px-bar-best { background: var(--blue); }
.px-spark { width: 64px; height: 18px; vertical-align: -4px; margin: 0 4px; }
.px-spark polyline { stroke: var(--blue); stroke-width: 1.6; fill: none; }
.px-solo .px-name { font-weight: 600; }
.px-rest { margin-top: 10px; }
.px-rest summary { color: var(--muted); font-size: .9em; cursor: pointer; padding: 6px 0; }
/* The update bar: quiet, fixed, one tap (owner, 2026-08-31). */
.up-bar {
  position: fixed; left: 12px; right: 12px; bottom: 84px; z-index: 60;
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 10px 14px; border-radius: 14px;
  background: var(--surface); border: 1px solid var(--rule);
  box-shadow: 0 6px 24px rgba(0, 0, 0, .12); font-size: .9rem;
}
.sheet-row svg { width: 20px; height: 20px; color: var(--muted); flex: none; }
.sheet-row.danger, .sheet-row.danger svg { color: var(--danger); }
.sheet-row:active { opacity: 0.6; }

/* ---------------------------------------------------------------------
   DRAWER - the left slide-in panel reached from the home top bar's menu
   button. The house pattern, sideways: fixed left instead of fixed
   bottom, translateX instead of translateY, no swipe-to-dismiss (a
   sideways sheet has no natural drag axis) - scrim2 and the z-81
   register are shared with the sheet above. Every colour rides the
   tokens so a theme change re-tints it like everything else; the
   wordmark's own dark-context colours (tab.css's .wm-itza/.wm-prod
   overrides) already keep the head legible on deep and dark.
   --------------------------------------------------------------------- */

.drawer {
  position: fixed;
  top: 0; bottom: 0; left: 0;
  z-index: 81;              /* tappable above the scrim, per the register */
  width: min(20rem, 86vw);
  background: var(--surface);
  box-shadow: var(--shadow-float);
  transform: translateX(-100%);
  transition: transform var(--m-enter) var(--ease-enter);
  overflow-y: auto;
  font-family: var(--sans);
  padding-bottom: calc(var(--safe-b) + 12px);
}
.drawer.on { transform: translateX(0); }

.drawer-head {
  padding: calc(var(--safe-t) + 22px) 18px 18px;
  border-bottom: 1px solid var(--rule);
}

.drawer-group { padding: 8px 8px 2px; }
.drawer-group + .drawer-group { border-top: 1px solid var(--rule); }

.drawer-row {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  border: none;
  background: transparent;
  text-align: left;
  padding: 12px;
  border-radius: var(--r-md);
  font-family: var(--sans);
  font-size: 0.95rem;
  color: var(--ink);
  cursor: pointer;
}
.drawer-row svg { width: 21px; height: 21px; color: var(--muted); flex: none; }
.drawer-row:active { background: color-mix(in srgb, var(--rule) 40%, transparent); }

/* ---------------------------------------------------------------------
   Buttons - real depth, and a pressed state that MOVES (spec 8.3: hover
   colour changes do not exist on touch; press displaces, release settles)
   --------------------------------------------------------------------- */

.btn {
  display: inline-block;
  font-family: var(--sans);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--accent-ink);
  background: var(--accent);
  border: 1px solid transparent;
  border-radius: var(--r-full);
  padding: 0.55rem 1.15rem;
  cursor: pointer;
  text-align: center;
  /* The subtle inner highlight that says "raised". */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 1px 2px rgba(0, 0, 0, 0.2);
  transition: transform var(--m-tap) ease-out, box-shadow var(--m-tap) ease-out;
}
.btn:active {
  transform: translateY(1px) scale(0.97);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.22);
}

.btn.wide { display: block; width: 100%; }
.btn.ghost {
  background: var(--frost);
  /* INK, not accent: only --ink is DEFINED by contrast against the
     theme's own surfaces. */
  color: var(--ink);
  border-color: var(--rule);
  font-weight: 500;
  box-shadow: none;
}
.btn.ghost:active {
  transform: translateY(1px) scale(0.97);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.14);
}
/* The named PRIMARY of a screen: same accent, floating a little. (The
   class name is the house pattern's; the fill is the accent - gold itself stays
   reserved for the seal.) */
.btn.gold {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), var(--shadow-float);
  font-weight: 700;
}
.btn.gold:active {
  transform: translateY(1px) scale(0.97);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.22);
}
.btn.sm { font-size: 0.76rem; padding: 0.35rem 0.7rem; }
.btn[disabled] { opacity: 0.5; cursor: default; }
.btn[disabled]:active { transform: none; }
/* The danger variant exists for exactly one class of act: the
   irreversible. If it appears anywhere reversible, that is a bug. */
.btn.danger { background: var(--danger-fill); color: var(--danger-ink); }
.btn.danger[disabled] { background: var(--danger-fill); }

button,
.button {
  font-family: var(--sans);
  font-size: 0.85rem;
  color: var(--accent-ink);
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--r-sm);
  padding: 0.5rem 0.95rem;
  cursor: pointer;
}
button.quiet,
.button.quiet {
  background: transparent;
  color: var(--blue);
  border-color: var(--rule);
}
button[disabled] { opacity: 0.5; cursor: default; }

/* The quiet link-shaped button, anywhere (the ob card carries its own
   scoped copy). */
.linklike {
  display: block;
  margin: 14px auto 0;
  background: none;
  border: none;
  color: var(--blue);
  font-family: var(--sans);
  font-size: 0.82rem;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ---------------------------------------------------------------------
   Forms
   --------------------------------------------------------------------- */

/* Any dialog form gets the stacked treatment by wrapping its fields in
   .mform - bare labels and inputs flow INLINE in HTML, which reads as a
   ransom note. */
.mform label {
  display: block;
  font-family: var(--sans);
  font-size: 0.82rem;
  color: var(--ink);
  margin: 0.95rem 0 0.3rem;
}
.mform input,
.mform textarea {
  display: block;
  width: 100%;
  box-sizing: border-box;
  /* checkboxes are exempted just below - a full-width block checkbox
     strands its own label on the next line (owner, the org KYC toggle) */
  font-family: var(--sans);
  font-size: 0.92rem;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 0.55rem 0.7rem;
}
/* The category picker behind a button reads as the FIELD it stands in
   for - the same box the inputs beside it wear, left-aligned, never a
   navy action chip (owner, on the light-edit sheet live). */
.mform button.rv-catbtn {
  display: block;
  width: 100%;
  box-sizing: border-box;
  font-family: var(--sans);
  font-size: 0.92rem;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 0.55rem 0.7rem;
  text-align: left;
  cursor: pointer;
}
.mform input[type="checkbox"],
.mform input[type="radio"] {
  display: inline-block;
  width: 1.1rem;
  height: 1.1rem;
  flex: none;
  margin: 0;
}
.mform .setNote { margin-top: 0.9rem; }

/* A mismatch is a STOP, not a whisper: stated in place, the moment it is
   true, not only on submit. */
.fieldErr {
  margin-top: 8px;
  padding: 9px 12px;
  border-radius: 9px;
  border: 2px solid var(--danger);
  color: var(--danger);
  font-size: 0.84rem;
  font-weight: 700;
  line-height: 1.4;
}
input.err, .pwwrap input.err {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 18%, transparent);
}

/* The account warning at the moment of no return - bigger than an inline
   note by design; this is the last point a forgotten password is fixable. */
.obWarnBig {
  border: 2px solid var(--danger);
  border-radius: var(--r-md);
  padding: 14px 16px;
  margin: 4px 0 6px;
  font-size: 0.94rem;
  line-height: 1.55;
}
.obWarnBig b.lead { display: block; font-size: 1rem; margin-bottom: 8px; }
.obWarnBig .hl {
  font-weight: 800;
  background: color-mix(in srgb, var(--gold) 24%, transparent);
  padding: 1px 5px;
  border-radius: 5px;
}

/* The wrote-it-down checkbox at account creation. */
.aw-wrote {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-family: var(--sans);
  font-size: 0.85rem;
  line-height: 1.5;
  margin: 14px 2px;
  cursor: pointer;
}
.aw-wrote input { width: 1.1em; height: 1.1em; margin-top: 0.15em; accent-color: var(--gold); }

/* =====================================================================
   SIGN IN - PORTED 1:1 FROM AN EARLIER ITZA APP, WHICH PORTED IT FROM CHESS/CHAT.
   The same account, the same screen, in every ITZA app on purpose.
   SELF-CONTAINED: the whole palette is scoped to #screen-onboard so the
   card cannot be disturbed by the app's theme and cannot disturb it.
   Light is the base; the DARK variant follows the app's theme attribute,
   and because Tab's DEFAULT theme is deep, the dark card also applies
   when no data-theme is set and when it is 'deep' - that mapping is the
   only change made to any of it.
   ===================================================================== */

#screen-onboard{
  --ob-blue:#0c7ffb; --ob-blue-soft:#1d6fd0; --ob-gold:#f5c711;
  --ob-acc:#0c7ffb; --ob-acc-ink:#ffffff;
  --ob-bg:#f3f4f6; --ob-bg2:#ffffff; --ob-bg3:#f7f7f8;
  --ob-ink:#0b2a4a; --ob-ink2:#1e416b; --ob-ink3:#41618c;
  --ob-line:#d7dbe2; --ob-line2:#c5ccd6; --ob-hover:rgba(12,127,251,.06);
  --ob-card-shadow:0 18px 50px rgba(24,59,103,.12);
  --ob-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  --ob-r-sm:8px;
  --ob-danger:#c62828;
}
html[data-theme="dark"] #screen-onboard,
html[data-theme="deep"] #screen-onboard{
  --ob-bg:#0b1f3a; --ob-bg2:#10294a; --ob-bg3:#173058;
  --ob-ink:#e8edf4; --ob-ink2:#9db0c7; --ob-ink3:#5b7089;
  --ob-line:#1e3a63; --ob-line2:#28466f; --ob-hover:rgba(255,255,255,.05);
  --ob-card-shadow:0 18px 50px rgba(0,0,0,.45);
}
/* The light card comes back for the light-room themes. */
html[data-theme="paper"] #screen-onboard,
html[data-theme="hc"] #screen-onboard{
  --ob-bg:#f3f4f6; --ob-bg2:#ffffff; --ob-bg3:#f7f7f8;
  --ob-ink:#0b2a4a; --ob-ink2:#1e416b; --ob-ink3:#41618c;
  --ob-line:#d7dbe2; --ob-line2:#c5ccd6; --ob-hover:rgba(12,127,251,.06);
  --ob-card-shadow:0 18px 50px rgba(24,59,103,.12);
}
#screen-onboard{padding:0}
#screen-onboard.on{
  position:fixed; inset:0; z-index:50; display:flex; align-items:center; justify-content:center;
  padding:16px; overflow-y:auto; overscroll-behavior:contain;
  padding-top:max(16px, var(--safe-t)); padding-bottom:max(16px, var(--safe-b));
  background:linear-gradient(to bottom,#ffffff 0,#ffffff 200px,var(--ob-blue) 200px,var(--ob-blue) 224px,var(--ob-bg) 224px);
}
html[data-theme="dark"] #screen-onboard.on,
html[data-theme="deep"] #screen-onboard.on{
  background:linear-gradient(to bottom,#10294a 0,#10294a 200px,var(--ob-blue) 200px,var(--ob-blue) 224px,var(--ob-bg) 224px);
}
html[data-theme="paper"] #screen-onboard.on,
html[data-theme="hc"] #screen-onboard.on{
  background:linear-gradient(to bottom,#ffffff 0,#ffffff 200px,var(--ob-blue) 200px,var(--ob-blue) 224px,var(--ob-bg) 224px);
}
#screen-onboard .obcard{
  width:100%; max-width:430px; margin:0; text-align:left;
  background:var(--ob-bg2); border:1px solid var(--ob-line); border-radius:22px;
  padding:24px 22px; box-shadow:var(--ob-card-shadow); color:var(--ob-ink);
  /* The FAMILY face, not the platform default: same tokens as every pane.
     13px system-stack shipped once and read as a novice app (owner,
     2026-08-26); the card is the first thing anyone sees. */
  font:15px/1.5 var(--sans);
}
#screen-onboard .hd{display:flex; align-items:center; gap:10px; margin-bottom:10px}
/* The wordmark at card scale: Fraunces, blue-into-gold, the pyramid at its
   side - the same mark every ITZA app wears (spec 8.1). */
#screen-onboard .brand{font-size:clamp(1.55rem, 6vw, 1.9rem); gap:0.55rem; margin:0}
#screen-onboard .brand-mark{width:32px; height:32px}
#screen-onboard .tag{font-size:0.95rem; line-height:1.5; color:var(--ob-ink2); margin-bottom:16px}
#screen-onboard .tag b{color:#8d6f00}
html[data-theme="dark"] #screen-onboard .tag b,
html[data-theme="deep"] #screen-onboard .tag b{color:var(--ob-gold)}
html[data-theme="paper"] #screen-onboard .tag b,
html[data-theme="hc"] #screen-onboard .tag b{color:#8d6f00}
#screen-onboard .pill{font-size:0.6rem; padding:1px 7px; border-radius:99px; border:1px solid var(--ob-line2); color:var(--ob-ink3); text-transform:uppercase; letter-spacing:.05em; font-weight:400}
#screen-onboard label{display:block; margin:14px 0 5px; font-size:0.8rem; color:var(--ob-ink2); font-weight:600; text-align:left}
#screen-onboard input,#screen-onboard textarea{
  width:100%; font:inherit; font-size:max(1rem, 16px); color:var(--ob-ink); background:var(--ob-bg3);
  border:1px solid var(--ob-line2); border-radius:var(--ob-r-sm); padding:10px 12px; outline:none;
}
#screen-onboard input:focus,#screen-onboard textarea:focus{border-color:var(--ob-acc)}
#screen-onboard ::placeholder{color:var(--ob-ink3)}
#screen-onboard .obtoggle{display:flex; gap:4px; margin-bottom:12px}
#screen-onboard .obtoggle button{flex:1; font-size:0.72rem; padding:6px; border-radius:var(--ob-r-sm); border:1px solid var(--ob-line2); color:var(--ob-ink2); background:none; cursor:pointer}
#screen-onboard .obtoggle button.on{border-color:var(--ob-acc); color:var(--ob-ink); background:var(--ob-hover); font-weight:700}
#screen-onboard .row2{display:flex; gap:8px}
#screen-onboard .row2 .btn,#screen-onboard .row2 input{flex:1; min-width:0}
#screen-onboard .btn{
  background:linear-gradient(135deg,var(--ob-acc),var(--ob-blue-soft)); color:var(--ob-acc-ink);
  font-weight:700; font-size:0.78rem; padding:8px 16px; border-radius:10px; border:none; cursor:pointer;
  box-shadow:0 8px 18px rgba(12,127,251,.22);
}
#screen-onboard .btn:disabled{opacity:.45; cursor:default; box-shadow:none}
#screen-onboard .btn.ghost{background:var(--ob-bg3); color:var(--ob-ink); border:1px solid var(--ob-line2); font-weight:600; box-shadow:none}
#screen-onboard .btn.gold{background:var(--ob-gold); color:#231c00; box-shadow:none}
#screen-onboard .btn.wide{width:100%}
#screen-onboard .feenote{font-size:0.63rem; color:var(--ob-ink3); text-align:left; margin-top:0}
#screen-onboard .feenote b{color:var(--ob-ink2)}
#screen-onboard .linklike{display:block; margin:14px auto 0; background:none; border:none; color:var(--ob-blue); font-size:0.75rem; cursor:pointer; text-decoration:underline; text-underline-offset:3px}
#screen-onboard .qrframe{display:flex; justify-content:center; margin:6px 0 2px}
#screen-onboard #obQrBox{width:190px; height:190px; border-radius:12px; background:#fff; border:1px solid var(--ob-line); display:flex; align-items:center; justify-content:center; position:relative}
#screen-onboard #obQrBox .qrph{font-family:var(--ob-mono); font-size:2.1rem; color:var(--ob-line2); letter-spacing:2px}
#screen-onboard #obQrBox svg{width:100%; height:100%; display:block; border-radius:10px}
#screen-onboard .settingsec{border-top:1px solid var(--ob-line); margin-top:12px; padding-top:10px}
#screen-onboard .settingsec h4{font-size:0.69rem; text-transform:uppercase; letter-spacing:.07em; color:var(--ob-ink3); margin-bottom:6px}
#screen-onboard .spinner{display:inline-block; width:13px; height:13px; border:2px solid var(--ob-line2); border-top-color:var(--ob-blue); border-radius:50%; animation:spin .8s linear infinite; vertical-align:-2px; margin-right:4px}

/* The two ways in: both full width so neither reads as the small option;
   the vault one is ghost so the lead is unambiguous. */
#screen-onboard #obChoose{ display:flex; flex-direction:column; gap:10px; margin:16px 0 4px; }
#screen-onboard #obChoose .btn{ height:46px; font-size:0.94rem; font-weight:700; }
#screen-onboard #obBack{ margin-top:0; margin-bottom:10px; display:inline-block; }

/* ---- password fields: show/hide, and problems stated in place ----
   The eye sits INSIDE the field's box (the input reserves room for it
   via padding-right) so it never reflows the form when it appears. */
.pwwrap{ position:relative; display:block; }
.pwwrap input{ width:100%; padding-right:44px; }
.pweye{
  position:absolute; top:50%; right:6px; transform:translateY(-50%);
  width:32px; height:32px; display:flex; align-items:center; justify-content:center;
  border-radius:8px; color:var(--ob-ink3, var(--muted)); cursor:pointer; padding:0;
  background:none; border:none;
}
#screen-onboard .pweye:hover{ color:var(--ob-ink); background:var(--ob-hover); }
.pweye svg{ width:20px; height:20px; display:block; }
.pweye.on{ color:var(--ob-acc, var(--accent)); }
/* In Arabic and Hebrew the text starts on the RIGHT, so an eye pinned to
   the right sits on top of it. Mirror the whole arrangement, padding
   included. The onboarding card restyles its inputs behind an ID, which
   outranks a plain class rule - so the room for the eye is reserved at
   the same specificity, in both directions. */
html[dir="rtl"] .pwwrap input{ padding-right:12px; padding-left:44px; }
html[dir="rtl"] .pweye{ right:auto; left:6px; }
#screen-onboard .pwwrap input{ padding-right:44px; }
html[dir="rtl"] #screen-onboard .pwwrap input{ padding-right:9px; padding-left:44px; }

#screen-onboard .fieldErr{ color:#c62828; border-color:#c62828; background:rgba(198,40,40,.07); }
html[data-theme="dark"] #screen-onboard .fieldErr,
html[data-theme="deep"] #screen-onboard .fieldErr,

html[data-theme="hc"] #screen-onboard .fieldErr{ color:#8c0000; border-color:#8c0000; background:rgba(140,0,0,.10); }
#screen-onboard input.err{ border-color:#c62828; box-shadow:0 0 0 3px rgba(198,40,40,.16); }

/* ---------------------------------------------------------------------
   Settings - the tabbed modal (Receipts / Look / Billing / App / Account)
   --------------------------------------------------------------------- */

.modeRow { display: flex; gap: 0.3rem; margin-bottom: 1rem; }
/* INK, NEVER MUTED OR ACCENT, ON CONTROLS THAT MUST BE READ: only --ink
   is DEFINED by contrast against the theme's own surfaces. And every
   control answers the finger: a pressed tab moves. */
.modebtn {
  flex: 1;
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 0.4rem 0.3rem;
  font-size: 0.78rem;
  cursor: pointer;
  transition: transform var(--m-tap) ease-out;
}
.modebtn:active { transform: scale(0.94); }
.modebtn.on {
  color: var(--accent-ink);
  background: var(--accent);
  border-color: transparent;
}
/* The font cards: name above its note, each on its own line with real
   spacing - inline they ran together ("SystemWhatever this device
   speaks natively", owner-seen). */
#setFont .modebtn { text-align: left; padding: 0.6rem 0.8rem; }
#setFont .modebtn b { display: block; font-size: 0.95rem; margin-bottom: 3px; }
#setFont .modebtn .setNote { display: block; margin: 0; line-height: 1.4; }
/* A note INSIDE a filled chip must follow the fill's ink, not stay muted -
   muted-on-accent is exactly the illegible font card the owner saw. */
.modebtn.on .setNote { color: var(--accent-ink); opacity: 0.78; }

.setPane { display: none; }
.setPane.on { display: block; }

.setRow {
  display: flex;
  /* WRAPS: the quick-action row carries six chips, and an unwrapped flex
     row dragged a horizontal scrollbar across the whole settings dialog
     (owner-visible, 2026-08-26). Label+control rows are two items and
     never wrap in practice. */
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem 1rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--rule);
}
.setRow > span:first-child { flex: 1; }
/* The row's own flex beats the UA's [hidden] rule (the dock precedent). */
.setRow[hidden] { display: none; }
.setRow select {
  font-family: var(--sans);
  font-size: 0.82rem;
  padding: 0.3rem 0.4rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink);
}
.setRow input[type='checkbox'] { width: 1.1rem; height: 1.1rem; flex: none; }

/* The dark toggle the owner asked for BY NAME (spec 8.4): a real switch,
   not a row in a list. The input is the state; the track re-tints. */
.switch {
  position: relative;
  display: inline-block;
  width: 46px; height: 28px;
  flex: none;
}
.switch input {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.switch i {
  position: absolute;
  inset: 0;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--rule) 70%, var(--muted));
  transition: background var(--m-quick);
  pointer-events: none;
}
.switch i::after {
  content: '';
  position: absolute;
  top: 3px; left: 3px;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  transition: transform var(--m-quick) var(--ease-enter);
}
.switch input:checked + i { background: var(--accent); }
.switch input:checked + i::after { transform: translateX(18px); }
.switch input:focus-visible + i {
  outline: 2px solid var(--blue-bright);
  outline-offset: 2px;
}
html[dir='rtl'] .switch input:checked + i::after { transform: translateX(-18px); }

/* ---------------------------------------------------------------------
   LOOK SWATCHES - the four theme picks as small self-portraits instead
   of a row of plain text, shared by the Look pane and the first-run
   theme shelf (Themes.swatchHTML in themes.js paints the markup for
   both). A tap applies the theme at once, ambient-preview style; the
   ring marks the active room.

   The colours on .sw-deep/.sw-paper/.sw-dark/.sw-hc below are LITERAL,
   not var() - a swatch must show ANOTHER theme's colours while the room
   is still sitting in this one. They mirror html[data-theme=...] in
   tab.css - update together.
   --------------------------------------------------------------------- */

.sw-row { display: flex; flex-wrap: wrap; gap: 14px; margin: 4px 0 12px; }

.sw-swatch {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 72px;
  border: none;
  background: transparent;
  padding: 0;
  cursor: pointer;
  font-family: var(--sans);
  font-size: 0.78rem;
  color: var(--ink);
}
.sw-swatch:active .sw-card { transform: scale(0.96); }

.sw-card {
  width: 72px;
  height: 96px;
  border-radius: var(--r-md);
  border: 1px solid;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: transform var(--m-tap) ease-out, box-shadow var(--m-quick);
}
.sw-swatch.on .sw-card { box-shadow: 0 0 0 2px var(--blue-bright); }

.sw-bar { height: 20px; border-bottom: 1px solid; }
.sw-line { margin: 12px 10px 0; height: 4px; border-radius: 2px; }
.sw-dots { margin-top: auto; display: flex; gap: 6px; padding: 0 10px 10px; }
.sw-dot { width: 8px; height: 8px; border-radius: 50%; }
.sw-name { max-width: 72px; text-align: center; }

/* mirrors html[data-theme='deep'] in tab.css - update together */
.sw-deep { background: #14181f; border-color: #2a3140; }
.sw-deep .sw-bar { background: #1b212b; border-color: #2a3140; }
.sw-deep .sw-line { background: #e9e7e2; }
.sw-deep .sw-dot-accent { background: #4d9bff; }
.sw-deep .sw-dot-gold { background: #f5c711; }

/* mirrors html[data-theme='paper'] in tab.css - update together */
.sw-paper { background: #faf9f6; border-color: #e3e0d8; }
.sw-paper .sw-bar { background: #ffffff; border-color: #e3e0d8; }
.sw-paper .sw-line { background: #171a1f; }
.sw-paper .sw-dot-accent { background: #1c3a5e; }
.sw-paper .sw-dot-gold { background: #f5c711; }

/* mirrors html[data-theme='dark'] in tab.css - update together */
.sw-dark { background: #0d0f13; border-color: #262b33; }
.sw-dark .sw-bar { background: #161a20; border-color: #262b33; }
.sw-dark .sw-line { background: #e8e6e1; }
.sw-dark .sw-dot-accent { background: #4d9bff; }
.sw-dark .sw-dot-gold { background: #f5c711; }

/* mirrors html[data-theme='hc'] in tab.css - update together */
.sw-hc { background: #ffffff; border-color: #000000; }
.sw-hc .sw-bar { background: #ffffff; border-color: #000000; }
.sw-hc .sw-line { background: #000000; }
.sw-hc .sw-dot-accent { background: #000000; }
.sw-hc .sw-dot-gold { background: #a06f00; }

/* The jurisdiction select (Settings - Receipts): a code from a fixed
   list reads oddly stretched to the full dialog width. */
#setJur { width: 100%; max-width: 10rem; }

.verVal { font-size: 0.76rem; color: var(--muted); font-variant-numeric: tabular-nums; }
/* The unit sits BESIDE the label, quiet - the house money-row rule;
   block display stacked it underneath (owner's second look). */
.rowunit {
  display: inline;
  margin-left: 0.45em;
  font-size: 0.72rem;
  color: var(--muted);
  font-family: var(--sans);
  letter-spacing: 0.04em;
}

/* ---------------------------------------------------------------------
   First run - the welcome sequence (spec 8.5), the house fr.* shape
   --------------------------------------------------------------------- */

.fr {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: calc(var(--safe-t) + 30px) 26px calc(var(--safe-b) + 30px);
  max-width: 26rem;
  margin: 0 auto;
  font-family: var(--sans);
  position: relative;
}
.fr-art {
  width: 130px;
  height: 130px;
  border-radius: 50%;
  background-image: var(--hero-art);
  background-size: cover;
  box-shadow: var(--shadow-float);
  margin-bottom: 8px;
}
.fr-head {
  font-family: var(--serif-display);
  font-size: 1.6rem;
  line-height: 1.25;
  color: var(--ink);
  text-align: center;
  margin: 0;
}
.fr-sub { color: var(--muted); text-align: center; margin: 0 0 8px; }
.fr-promise {
  color: var(--muted);
  text-align: center;
  font-size: 0.9rem;
  margin: 0 0 8px;
  padding-top: 10px;
  border-top: 1px solid var(--rule);
}
.fr-q {
  font-family: var(--serif-display);
  font-size: 1.3rem;
  color: var(--ink);
  text-align: center;
  margin: 0;
}
.fr-dots { display: flex; gap: 8px; }
.fr-dot.on { background: var(--accent); }
.fr-prog { display: flex; gap: 6px; position: absolute; top: calc(var(--safe-t) + 20px); }
.fr-prog span { width: 34px; height: 4px; border-radius: var(--r-full); background: var(--rule); }
.fr-prog span.on { background: var(--accent); }
.fr-q { font-size: 1.3rem; }
.fr-opts { display: flex; flex-direction: column; gap: 8px; width: 100%; }
.fr-opt {
  border: 1.5px solid var(--rule);
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--r-md);
  padding: 13px 16px;
  font-family: var(--sans);
  font-size: 0.95rem;
  text-align: left;
  cursor: pointer;
}
.fr-opt.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--surface)); }
.fr-thm.on { border-color: var(--accent); color: var(--ink); }
.thm-mini-card.short { width: 72%; }

/* ---------------------------------------------------------------------
   The language screen - one pre-answered question, before everything
   --------------------------------------------------------------------- */

.langcard {
  max-width: 30rem;
  margin: 0 auto;
  padding: calc(var(--safe-t) + 28px) 20px calc(var(--safe-b) + 24px);
  display: flex;
  flex-direction: column;
  gap: 14px;
  height: 100%;
  box-sizing: border-box;
}
.langcard h1 { font-size: 1.5rem; margin: 0 0 4px; font-family: var(--serif-display); color: var(--ink); }
.langlist {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: var(--surface);
}
.langrow {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 11px 14px;
  border: 0;
  border-bottom: 1px solid var(--rule);
  background: transparent;
  color: var(--ink);
  font-size: 1rem;
  text-align: start;
  cursor: pointer;
}
.langrow:last-child { border-bottom: 0; }
.langrow.on {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  box-shadow: inset 3px 0 0 var(--accent);
}
.langflag { flex: none; width: 24px; display: inline-flex; }
.langflag svg { width: 24px; height: 16.5px; border-radius: 2px; display: block; }
.langname { flex: 1; }
.langen { color: var(--muted); font-size: 0.82rem; }

/* ---------------------------------------------------------------------
   The text-size calibrator - the sample scrolls, the controls stay
   pinned under the thumb
   --------------------------------------------------------------------- */

.tsc {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  /* The calibration reads in a column, not a broadsheet: same measure as
     the language door, safe-area padded, clear of the dock. */
  max-width: 30rem;
  margin: 0 auto;
  padding: calc(var(--safe-t) + 24px) 20px calc(var(--dock) + 8px);
  box-sizing: border-box;
}

/* With the dock gone (the calibration screen hides it - the person is
   not yet anywhere it leads), its reserved strip goes too: the content
   sits on the real bottom edge instead of floating above a gap. */
.pane.no-dock { padding-bottom: 0; }
.pane.no-dock .tsc { padding-bottom: calc(var(--safe-b) + 24px); }
.tsc-head { margin: 0 0 0.8rem; }
.tsc-sample {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: color-mix(in srgb, var(--surface) 86%, transparent);
  border: 1px solid var(--rule);
  border-radius: 10px;
  padding: 1rem 1.1rem;
}
.tsc-sample p {
  font-family: var(--serif-body, serif);
  font-size: var(--body-size);
  font-weight: 400;
  line-height: 1.55;
  margin: 0;
}
.tsc-bar {
  position: sticky;
  bottom: 0;
  padding: 12px 0 calc(var(--safe-b) + 8px);
  background: var(--paper);
}
.tsc-steps { display: flex; gap: 10px; margin-bottom: 10px; }
.tsc-btn {
  flex: 1 1 0;
  min-height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
/* The direction glyphs are FIXED PX chrome: they encode smaller/bigger
   and must hold still while everything else moves. */
.tsc-a { font-size: 14px; line-height: 1; }
.tsc-A { font-size: 24px; line-height: 1; font-weight: 600; }
.tsc-ch { display: inline-flex; }
.tsc-ch.up svg { transform: rotate(180deg); }
.tsc-btn.dim { opacity: 0.35; }

/* ---------------------------------------------------------------------
   The app lock
   --------------------------------------------------------------------- */

.lockgate {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: var(--paper);
  background-image: var(--hero-art);
  background-size: cover;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.lock-card {
  background: var(--surface);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-float);
  padding: 26px 22px;
  max-width: 21rem;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  font-family: var(--sans);
  color: var(--ink);
}
/* The padlock glyph is the seal's colour: what is locked is the proof. */
.lock-card > svg { color: var(--gold-ink); }
.lock-title { font-family: var(--serif-display); font-size: 1.15rem; font-weight: 700; margin: 0; }
.lock-dots { display: flex; gap: 10px; min-height: 14px; }
.lock-dots i {
  width: 12px; height: 12px;
  border-radius: 50%;
  border: 1.5px solid var(--muted);
  display: inline-block;
}
.lock-dots i.on { background: var(--accent); border-color: var(--accent); }
.lock-dots.err i { border-color: var(--danger); }
.lock-pad {
  display: grid;
  grid-template-columns: repeat(3, 64px);
  gap: 10px;
  justify-content: center;
}
.lock-key {
  height: 54px;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: transparent;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1.2rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.lock-key:active { transform: scale(0.94); background: color-mix(in srgb, var(--accent) 10%, transparent); }
.lock-note { font-size: 0.74rem; color: var(--muted); text-align: center; margin: 4px 0 0; }

/* ---------------------------------------------------------------------
   The capture surface (record.js): the card with the viewfinder, the
   meter, the keep/again/discard row
   --------------------------------------------------------------------- */
.recwrap {
  position: fixed;
  inset: 0;
  z-index: 85;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 1rem;
  /* A card taller than the screen must still reach its buttons. */
  overflow-y: auto;
  overscroll-behavior: contain;
}
@keyframes recPulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.45; transform: scale(0.8); }
}

/* THE FRAME FOLLOWS THE CLIP, NEVER THE OTHER WAY ROUND: sized under a
   ceiling, the element IS the image and the paper shows around it - a
   full-width box would letterbox a portrait shot into black pillars. */
.recview {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 42vh;
  margin: 10px auto;
  border-radius: var(--r-md);
  background: transparent;
}
#recPreview img { display: block; max-width: 100%; max-height: 42vh; margin: 10px auto; border-radius: var(--r-md); }

/* Desktop capture: drag-and-drop is how desktop users actually have
   receipts (spec 8.3). JS raises this veil on dragover, over everything,
   taking no pointer events so the drop lands on the window. */
.drop-veil {
  position: fixed;
  inset: 12px;
  z-index: 120;
  border: 2px dashed var(--accent);
  border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--sans);
  font-size: 1.05rem;
  color: var(--ink);
  pointer-events: none;
}
.drop-veil[hidden] { display: none !important; }

/* =====================================================================
   TAB'S OWN COMPONENTS
   ===================================================================== */

/* ---------------------------------------------------------------------
   The home: this month,
and what needs you (spec 8.1). The home is not
   a file list - it is a state of affairs.
   --------------------------------------------------------------------- */

.home-body {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 10px 16px;
  font-family: var(--sans);
}

/* This month's figures: one card, spent and came in, in display serif
   because these two numbers are the headline of the whole app. */
.sum-card {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-card);
  padding: 18px 20px;
  margin: 10px 0 4px;
}
.sum-row.in .sum-big { font-size: 1.25rem; font-weight: 500; }

/* What needs you: a warranty expiring, a return window closing, an
   unreviewed receipt. Attention is the ACCENT's job - gold would spend
   the seal on a to-do list. */
.needs { margin: 18px 0 6px; }
.need-card {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: start;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-card);
  padding: 12px 14px;
  margin: 8px 0;
  cursor: pointer;
  font-family: var(--sans);
  color: var(--ink);
}
.need-card:active { transform: scale(0.985); }
.need-title { font-size: 0.95rem; font-weight: 600; overflow-wrap: break-word; }
.need-card.urgent { border-left-color: var(--danger); }
.need-card.urgent .need-when { color: var(--danger); font-weight: 600; }
/* A SHARE ROW holds two doors, not one (owner, 2026-09-14): the person
   named, their receipts and their shopping list side by side beneath.
   The card itself is no longer a button, so it must not behave like
   one. The doors stack full width so a long name or a large text
   setting never crushes them. */
.need-card.share-card { display: block; cursor: default; }
.need-card.share-card:active { transform: none; }
.share-card .need-title { display: block; }
.share-doors { display: flex; gap: 8px; margin-top: 10px; }
.share-doors .btn { flex: 1; min-width: 0; }

/* If nothing needs you, SAY so, plainly - that is the reward. Calm, not
   a sad grey box. */
.needs-clear {
  border: 1px dashed var(--rule);
  border-radius: var(--r-lg);
  padding: 26px 20px;
  text-align: center;
  font-family: var(--serif-display);
  font-size: 1.05rem;
  color: var(--ink);
  margin: 8px 0;
}

/* ---------------------------------------------------------------------
   The receipt list: month groups with right-aligned totals, then
   photo-forward rows. The photograph leads; the document follows.
   --------------------------------------------------------------------- */

/* The month header sticks under the top bar, whole-row frost so the
   name and its total travel together. */
.month-h {
  position: sticky;
  top: calc(var(--safe-t) + 52px);
  z-index: 3;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  background: var(--frost);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-radius: var(--r-full);
  padding: 3px 12px;
  margin: 14px 0 2px;
  font-family: var(--sans);
}
.month-name {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.month-total {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.rc-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: start;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-card);
  padding: 10px 12px;
  margin: 10px 0;
  cursor: pointer;
  overflow: hidden;             /* the swipe rail hides behind the edge */
  font-family: var(--sans);
  color: var(--ink);
}
.rc-row:active { transform: scale(0.985); }

/* =====================================================================
   THE RECEIPT DETAIL - the object, its seal, its facts. Every class
   here was painted from the start and styled never: the most-looked-at
   page in the app rendered a natural-size photograph that shoved every
   fact off screen (owner, live, first kept receipt).
   ===================================================================== */

/* The photograph wears the review's clothes: bounded, centred, a plate
   with a shadow - and one tap from the full-size original.

   THE SPACE IS RESERVED BEFORE THE PHOTO ARRIVES. The img has no src
   until the decrypt lands, and an empty img is 0px tall - the facts
   painted at the top of the page and then LEAPT down when the photo
   popped in, with the row's view-transition morphing into nothing
   (the owner's "uncomfortable flash"). The figure holds its full
   height always, and the photo fades in centred within it - no
   layout ever moves. */
.rd-photo {
  margin: 4px 0 0;
  height: 46vh;
  display: flex;
  align-items: center;
  justify-content: center;
}
.rd-photo img {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: 100%;
  border-radius: var(--r-md);
  box-shadow: var(--shadow-card);
  cursor: zoom-in;
  background: color-mix(in srgb, var(--rule) 30%, var(--paper));
  opacity: 0;
  transition: opacity 240ms ease;
}
.rd-photo img.on { opacity: 1; }
html[data-motion='off'] .rd-photo img { transition: none; }

/* Provenance, quiet and centred: the seal mark and the day it was
   sealed - a statement, never a badge. */
.rd-seal {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin: 10px 0 4px;
  font-size: 0.8rem;
  color: var(--muted);
}
.rd-seal svg { width: 14px; height: 14px; flex: none; }

/* The facts ride the house fact rows; the block just breathes. */
.rd-facts { margin-top: 12px; }

/* The printed receipt's own columns: label left, amount right, one
   line ALWAYS, mods muted underneath (the harvest's contract). */
.rd-lines {
  margin-top: 18px;
  border-top: 1px solid var(--rule);
  padding-top: 8px;
}
.rcpt-line {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin: 0;
  padding: 4px 0;
  font-family: var(--sans);
}
.rcpt-label {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rcpt-amt { flex: none; font-variant-numeric: tabular-nums; }
.rcpt-mod { margin: 0 0 2px; font-size: 0.8rem; color: var(--muted); }

/* The totals rule off from the lines; the LAST line is the total and
   carries the weight. */
.rcpt-totals {
  margin-top: 6px;
  border-top: 1px solid var(--rule);
  padding-top: 6px;
}
.rcpt-totals .rcpt-label { color: var(--muted); font-size: 0.9rem; }
.rcpt-totals .rcpt-line:last-child .rcpt-label {
  color: var(--ink);
  font-weight: 600;
  font-size: 1rem;
}
.rcpt-totals .rcpt-line:last-child .rcpt-amt {
  font-weight: 700;
  font-size: 1.05rem;
}

/* The whole transcript, kept readable - a document, framed. */
.rd-raw {
  margin-top: 16px;
  padding: 12px 14px;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  font-size: 0.85rem;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--ink);
  background: color-mix(in srgb, var(--surface) 60%, transparent);
  overflow-x: auto;
}

/* The governance menu (owner feature): three structures, stacked
   cards, the chosen one lit - a decision worth a sentence each. */
.gov-picks { display: flex; flex-direction: column; gap: 8px; margin: 6px 0 12px; }
.gov-pick,
.gov-pick-on {
  font: inherit;
  text-align: start;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 10px 12px;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}
.gov-pick-on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.gov-pick-name { font-weight: 600; }
.gov-pick-on .gov-pick-name { color: var(--navy); }
.gov-pick-note { font-size: 0.8rem; color: var(--muted); line-height: 1.4; }

/* Price memory chips (owner feature): what changed since last time
   at this merchant - up wears the danger red, down the good green,
   both tappable into the merchant's history. */
.price-up,
.price-down {
  font: inherit;
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  border: none;
  background: transparent;
  cursor: pointer;
  /* A GENEROUS INVISIBLE HALO (owner, 2026-09-10: taps beside the tiny
     arrow landed in the edit fields). The padding grows the hit area
     far past the glyph; the negative margins give the space back so
     nothing moves; the raise puts the halo ABOVE the inputs, so a tap
     in the band opens the graph, never an edit. */
  padding: 16px 18px;
  margin: -14px -14px;
  position: relative;
  z-index: 1;
  border-radius: var(--r-sm);
  white-space: nowrap;
}
.price-up { color: var(--danger, #c62828); }
.price-down { color: var(--ok, #2e7d32); }
/* The arrow rides the SAME line as its number, always - a tight span
   wrapped the leading arrow onto its own row, which read as "above the
   number" (owner, 2026-09-10). */
.rcpt-amt {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
}
.rcpt-amt .price-up,
.rcpt-amt .price-down { margin-right: 6px; }

/* The photo viewer: the room goes dark, the photograph takes it all.
   Fit centred by flex; ZOOMED switches to block flow so native scroll
   pans the true-size image. Above every dialog surface. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(8, 12, 20, 0.97);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: auto;
  overscroll-behavior: contain;
  cursor: zoom-in;
}
.lightbox img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  margin: auto;
}
.lightbox.zoomed {
  display: block;
  cursor: zoom-out;
}
.lightbox.zoomed img {
  max-width: none;
  max-height: none;
  width: auto;
  height: auto;
  margin: 0;
}
.lightbox-close {
  position: fixed;
  top: calc(var(--safe-t) + 10px);
  right: calc(var(--safe-r) + 10px);
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--r-full);
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  cursor: pointer;
}

/* =====================================================================
   BUDGETING - the personal envelope plan (owner feature). The hero is
   the number that matters: what is LEFT this period. Envelope rows are
   name + spent-of-allocated + a slim bar; the allocation edits in
   place, page clothes like the review.
   ===================================================================== */
.bg-hero {
  margin: 4px 0 18px;
  text-align: center;
}
.bg-hero-label {
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted);
}
.bg-hero-btn {
  border: none;
  background: transparent;
  padding: 0;
  cursor: pointer;
  font: inherit;
}
.bg-hero-btn:active { transform: scale(0.98); }
.bg-hero-left,
.bg-hero-over {
  display: block;
  margin: 2px 0 4px;
  font-family: var(--serif-display);
  font-size: 2.4rem;
  font-variant-numeric: tabular-nums;
  color: var(--navy);
}
.bg-hero-over { color: var(--danger, #c62828); }
.bg-income .rv-in { width: 10rem; }

.bg-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 14px;
  align-items: center;
  padding: 12px 0;
  border-bottom: 1px solid var(--rule);
}
.bg-row:last-child { border-bottom: none; }
.bg-name { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.bg-spent,
.bg-spent-over { font-size: 0.8rem; color: var(--muted); }
.bg-spent-over { color: var(--danger, #c62828); }
.bg-bar {
  display: block;
  height: 3px;
  max-width: 220px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--rule) 60%, transparent);
  overflow: hidden;
}
.bg-bar i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
  transition: width 400ms ease;
}
.bg-bar .bg-overbar { background: var(--danger, #c62828); }
.bg-edit { display: inline-flex; align-items: center; gap: 8px; }
/* The page-clothes width:100% must not stretch the little figure -
   the form scope arrives later in the file, so the width is pinned at
   higher specificity. */
.rv-form .bg-edit .bg-alloc,
.bg-edit .bg-alloc { width: 6rem; text-align: right; }

/* The receipt-less act: Spent asks, E edits - a quiet pill that lights
   when the envelope is counted (owner design). */
.bg-mark,
.bg-mark-on {
  font: inherit;
  font-size: 0.8rem;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-full);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  min-width: 2.4rem;
}
.bg-mark-on {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: transparent;
}
.bg-x {
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  padding: 6px;
  border-radius: var(--r-sm);
  display: inline-flex;
  align-items: center;
  transition: color var(--m-tap) ease-out;
}
.bg-x:hover { color: var(--danger, #c62828); }

.bg-tools {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 16px;
  flex-wrap: wrap;
}

/* The personal home's glance: the same figure, one tap from the plan. */
.bg-card {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  margin: 10px 0;
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-card);
  font: inherit;
  text-align: start;
  cursor: pointer;
}
.bg-card-label { font-size: 0.85rem; color: var(--muted); }
.bg-card-left,
.bg-card-over {
  font-family: var(--serif-display);
  font-size: 1.3rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--navy);
}
.bg-card-over { color: var(--danger, #c62828); }

/* The month's headline: a labelled ledger figure, not a bare number
   the owner had to ask about ("what is the $450 at the top?").
   (.hm-label is the MONTH HEADING's long-worn class - naming this
   label the same thing shrank "August 2026" to fine print for one
   commit; the label is hm-spent.) */
/* The lens word rides the month heading's right edge (owner,
   2026-09-01): you always know which section you are standing in. */
.hm-label { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
/* The lens word wears the wordmark's gold (owner, 2026-09-01) - the
   wm-prod class brings the gradient; only the size is its own. */
/* It is a BUTTON now (one tap = next section, owner 2026-09-03), so it
   sheds every button default and keeps the word's exact look. The tap
   target grows past the glyphs without moving them. */
.hm-lens {
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  border: 0;
  /* background-COLOR only: `background: none` erased the wm-prod gold
     gradient this word is painted with (its own colour is transparent,
     so the word vanished outright - caught live, 2026-09-03). */
  background-color: transparent;
  padding: 4px 2px;
  margin: -4px -2px;
  font-family: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.hm-lens:active { opacity: 0.6; }
.hm-spent {
  margin: 0;
  font-size: 0.78rem;
  color: var(--muted);
}
.hm-total {
  margin: 2px 0 0;
  font-size: 1.5rem;
  font-weight: 650;
  color: var(--navy);
}
.hm-total .hm-unknown { font-size: 0.8rem; font-weight: 400; color: var(--muted); }
.hm-count { margin: 2px 0 0; font-size: 0.85rem; color: var(--muted); }

/* The photo thumbnail: the receipt as an OBJECT - a plate with a shadow
   on the deep ground, never a scan in a box. The span is the FRAME and
   the img fills it: object-fit on the span itself did nothing, and the
   unconstrained img rendered at its natural size - the first kept
   receipt's row was a full-width strip of photograph (owner, live). */
/* The category mark a row carries for the PREMIUM face. Classic never
   shows it - the span is present in the markup on both faces so that
   switching faces is pure CSS, but here it costs nothing and shows
   nothing. */
.rc-cat-ic { display: none; }

.rc-thumb {
  width: 56px; height: 56px;
  flex: none;
  overflow: hidden;
  border-radius: var(--r-sm);
  border: 1px solid var(--rule);
  background: color-mix(in srgb, var(--rule) 30%, var(--paper));
}
.rc-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* THE TEXT COLUMN HAS A FLOOR: break-word, never anywhere - a flex text
   column with overflow-wrap:anywhere can be crushed to one letter per
   line (a shipped card-title bug upstream). One line per merchant; the amount
   NEVER leaves its column - the same law receipt.css enforces per line
   item. */
.rc-main { flex: 1 1 auto; min-width: 0; }
.rc-merchant {
  font-size: 0.98rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rc-amt {
  flex: none;
  font-size: 1.02rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-align: right;
  /* The chevron is an svg riding a text baseline - it sat visibly high
     of the row's centre (owner). Flex centres text and icon alike. */
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
}

/* The live narration on a waiting card: one plain line and a slim bar
   that walks the stages. The sheen keeps it visibly alive between
   stage jumps; the motion settings stand it down. */
.rc-live { display: block; }
.rc-bar {
  display: block;
  height: 3px;
  margin-top: 6px;
  max-width: 220px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--rule) 60%, transparent);
  overflow: hidden;
}
.rc-bar i {
  position: relative;
  display: block;
  height: 100%;
  width: 25%;
  border-radius: inherit;
  background: var(--accent);
  transition: width 600ms ease;
}
.rc-bar i::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.55), transparent);
  animation: rcSheen 1.6s ease-in-out infinite;
}
@keyframes rcSheen {
  from { transform: translateX(-100%); }
  to { transform: translateX(100%); }
}
html[data-motion='reduce'] .rc-bar i::after,
html[data-motion='off'] .rc-bar i::after { animation: none; }

/* Not yet reviewed: a quiet accent dot beside the amount - a nudge, not
   an alarm. */
.rc-dot {
  flex: none;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--accent);
}

/* A capture still sealing: optimistic - the row exists the instant the
   shutter fires, carrying its own progress. The amount shows as a
   pending figure ('-', dimmed), never a zero. */
.rc-row.pending { cursor: default; }
.rc-row.pending .rc-amt { color: var(--muted); }

/* The swipe rail: categorise / flag / delete behind a dragged row. The
   row's content translates by --drag (written by JS); the rail slides
   in from the end edge. */
.rc-row > .rc-thumb, .rc-row > .rc-main, .rc-row > .rc-amt, .rc-row > .rc-dot {
  transform: translate3d(var(--drag, 0px), 0, 0);
  transition: transform 0.22s ease;
}
.rc-row.dragging > .rc-thumb, .rc-row.dragging > .rc-main,
.rc-row.dragging > .rc-amt, .rc-row.dragging > .rc-dot {
  transition: none;
}
.rc-row.rail-open .rc-rail { transform: translate3d(0, 0, 0); }
.rail-btn.danger { background: var(--danger-fill); color: var(--danger-ink); }

/* Desktop gets the same actions inline on hover - a desktop user made
   to mime a swipe is being punished for their device. Capability query,
   never user-agent. */
.rc-acts {
  position: absolute;
  right: 10px;
  top: 50%;
  translate: 0 -50%;
  display: none;
  gap: 4px;
}
@media (hover: hover) and (pointer: fine) {
  .rc-row:hover .rc-acts,
  .rc-row:focus-within .rc-acts { opacity: 1; }
}
.rc-pick.on { background: var(--accent); border-color: var(--accent); }

/* ---------------------------------------------------------------------
   Category chips
   --------------------------------------------------------------------- */

.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--ink);
  border-radius: var(--r-full);
  padding: 6px 14px;
  font-family: var(--sans);
  font-size: 0.85rem;
  cursor: pointer;
}
.chip:active { background: color-mix(in srgb, var(--accent) 12%, transparent); transform: scale(0.96); }
/* The chosen chip is FILLED WITH INK, not accent - a filter is a fact
   about the list, not a call to action. */
.chip.on { background: var(--ink); border-color: var(--ink); color: var(--paper); }
/* The category's colour rides a dot, never the text - text colour is
   contrast-defined and category colours are not. */
.catdot {
  width: 8px; height: 8px;
  flex: none;
  border-radius: 50%;
  background: var(--cat, var(--muted));
}
.chip.on .catdot { background: var(--cat, var(--paper)); }

/* A tiny inline count on a chip. */
.chip small { color: var(--muted); font-size: 0.72rem; }
.chip.on small { color: inherit; opacity: 0.75; }

/* ---------------------------------------------------------------------
   The seal - the proof, quiet (spec 8.1: a small mark and a date, not a
   security badge; the confidence is in it being THERE). The ONLY place
   --gold spends itself in the whole app chrome.
   --------------------------------------------------------------------- */

.seal {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--sans);
  font-size: 0.76rem;
  color: var(--muted);
}
.seal svg { width: 13px; height: 13px; flex: none; color: var(--gold-ink); }

/* The moment of sealing: pressed on once, when the chain confirms - the
   one place the animation budget is spent. */
.seal-stamp {
  position: absolute;
  right: 14px;
  top: 50%;
  translate: 0 -50%;
  width: 44px; height: 44px;
  border-radius: var(--r-full);
  background: var(--gold);
  color: var(--on-gold);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--gold) 55%, transparent);
  animation: sealIn 420ms cubic-bezier(0.34, 1.4, 0.64, 1),
             sealRing 700ms ease-out 120ms,
             sealFade 600ms ease-in 1400ms forwards;
  pointer-events: none;
}
@keyframes sealIn {
  from { transform: scale(1.7); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}
@keyframes sealRing {
  from { box-shadow: 0 0 0 0 color-mix(in srgb, var(--gold) 55%, transparent); }
  to   { box-shadow: 0 0 0 22px color-mix(in srgb, var(--gold) 0%, transparent); }
}
@keyframes sealFade { to { opacity: 0; } }

/* The original: an object on the ground, one tap from anywhere. */
/* The side-by-side lens (owner, 2026-09-03): items down the left,
   retailers across the top, the biggest winner in the first column and
   the rest one swipe away. Colour stays subtle - gold-tinted where a
   row is cheapest, faintly warm where it is dearest. */
.px-toprow { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin: 8px 0 14px; }
/* The row owns the spacing; children sit on one centerline and the
   toggle stretches to the pills' exact height. */
.px-toprow .px-scope { margin: 0; }
.px-toprow .px-viewtl { align-self: stretch; }
/* The shop-filter burger rides the row's far edge. */
.px-burger { margin-left: auto; color: var(--ink); }
/* One track, two halves, a thumb that slides (owner: "looks more like
   a toggle") - the lit half rides the navy thumb. */
.px-viewtl {
  position: relative;
  display: inline-flex;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--surface);
  overflow: hidden;
}
.px-viewtl::before {
  content: '';
  position: absolute;
  top: 2px;
  bottom: 2px;
  left: 2px;
  width: calc(50% - 2px);
  border-radius: 999px;
  background: var(--accent);
  transition: transform 200ms ease;
}
.px-viewtl.right::before { transform: translateX(100%); }
html[data-motion='reduce'] .px-viewtl::before { transition: none; }
.px-viewtl button {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: none;
  padding: 8px 14px;
  min-width: 7.5rem;
  font-size: 0.8rem;
  color: var(--ink);
  cursor: pointer;
  transition: color 200ms ease;
}
.px-viewtl button.on { color: var(--accent-ink); font-weight: 600; }
.pxg-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 8px 0 16px; }
.pxg { border-collapse: separate; border-spacing: 0; min-width: 100%; }
.pxg th, .pxg td {
  padding: 10px 14px;
  text-align: right;
  white-space: nowrap;
  border-bottom: 1px solid var(--rule);
  font-size: 0.85rem;
}
/* EVEN RETAILER COLUMNS (owner, 2026-09-03): one width for every data
   column - the abbreviated headers keep long names inside it, and the
   overflow wrapper carries the swipe when there are many. */
.pxg th:not(.pxg-item), .pxg td:not(.pxg-item) {
  /* Snug, not roomy (owner, 2026-09-06): enough for a wide-ish
     abbreviated name ("Walgreens P.") and a full price, nothing more -
     the extra air pushed retailers off the first screen. */
  width: 5.6rem;
  min-width: 5.6rem;
  max-width: 5.6rem;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pxg thead th { font-weight: 600; color: var(--muted); font-size: 0.72rem; }
/* The shop's name sits centred over its column (owner, 2026-09-06);
   the prices below keep their right edge, where numbers line up. */
.pxg thead th:not(.pxg-item) { text-align: center; }
.pxg .pxg-item {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--paper);
  text-align: left;
  font-weight: 600;
  max-width: 11rem;
  white-space: normal;
}
.pxg-unit { display: block; font-weight: 400; color: var(--muted); font-size: 0.7rem; }
.pxg td.best { background: color-mix(in srgb, var(--gold) 16%, transparent); font-weight: 700; }
.pxg td.high { background: color-mix(in srgb, var(--danger) 7%, transparent); }
.pxg td.none { color: var(--muted); }
/* A figure that is the PERSON'S OWN sighting, not the pool's - said
   by COLOUR (the word broke row alignment; owner, 2026-09-03), with
   the legend under the table carrying the meaning. */
.pxg td.you .num { color: var(--blue); }
.pxg-legend { display: flex; align-items: center; gap: 7px; margin-top: 2px; }
.pxg-key {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: var(--blue);
  flex: none;
}

/* The area shelf speaks in sentences (owner, 2026-09-03): what you pay,
   what the area pays, and the verdict - lit gold when you already win,
   action blue when somewhere nearby beats you. */
.pxa-line { display: block; font-size: 0.85rem; line-height: 1.5; margin-top: 2px; }
.pxa-verdict { font-weight: 600; margin-top: 6px; }
.pxa-verdict.good { color: var(--gold-ink); }
.pxa-verdict.cheaper { color: var(--blue); }

/* The one-time attention pulse: the install nudge's SHOW ME lands the
   person on Settings and this lights the button they came for - three
   beats, then still. Reduced motion gets a steady outline instead. */
@keyframes pulseAttn {
  0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  50% { transform: scale(1.06); box-shadow: 0 0 0 9px color-mix(in srgb, var(--accent) 0%, transparent); }
}
.pulse-attn { animation: pulseAttn 900ms ease-in-out 3; }
html[data-motion='reduce'] .pulse-attn { animation: none; outline: 2px solid var(--accent); outline-offset: 2px; }

/* The mail section says its state out loud - a lit dot, never a guess
   (owner: "no visual indicator", 2026-09-02). */
.mail-state::before {
  content: '';
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--muted);
  margin-right: 7px;
}
.mail-state.on { color: var(--gold-ink); font-weight: 600; }
.mail-state.on::before {
  background: var(--gold);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold) 25%, transparent);
}

/* The public certificate (spec 10): the ONE page a stranger reaches
   with no account, so it carries the house face - a centered card, the
   seal leading, the verdict as the headline. These classes existed in
   markup for weeks with no styles behind them (owner: "a bit crude"). */
.vf-wrap { display: flex; justify-content: center; padding: 40px 16px 60px; }
.vf-card {
  width: 100%;
  max-width: 560px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-card);
  padding: 32px 28px;
  text-align: center;
}
.vf-sealmark { color: var(--gold-ink); margin-bottom: 6px; }
.vf-verdict {
  font-family: var(--serif-display);
  font-size: 1.35rem;
  color: var(--navy);
  margin: 4px 0 20px;
}
.vf-verdict.vf-no { color: var(--danger); }
.vf-facts { text-align: left; margin: 0 0 6px; }
.vf-facts .fact-value { word-break: break-all; }
.vf-card .setNote { text-align: left; }
.vf-check { margin-top: 20px; text-align: left; }
.vf-check .rv-label { display: block; margin-bottom: 8px; }
.vf-check .btn { margin-top: 2px; }
#vfFileOut.vf-yes { color: var(--gold-ink); font-weight: 600; }
#vfFileOut.vf-no { color: var(--danger); font-weight: 600; }

/* A mailed receipt body - text where a photograph would sit. Same seat,
   same card weight, readable instead of a broken image frame. */
.rv-mailtext {
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-card);
  padding: 12px 14px;
  margin: 8px auto 16px;
  max-height: 40vh;
  overflow-y: auto;
}

.rv-img {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: 46vh;
  margin: 8px auto 16px;
  border-radius: var(--r-md);
  box-shadow: var(--shadow-card);
  cursor: zoom-in;
  background: color-mix(in srgb, var(--rule) 30%, var(--paper));
}

/* The photograph is a FIGURE by markup but not by browser default:
   the UA's 1em/40px figure margins pinched the receipt on phones.
   The space is HELD before the photo arrives and the photo fades in,
   exactly as on the detail page - the empty img was 0px tall and the
   form leapt down when the decrypt landed. */
.rv-photo {
  margin: 4px 0 0;
  height: 46vh;
  display: flex;
  align-items: center;
  justify-content: center;
}
.rv-photo .rv-img {
  max-height: 100%;
  margin: 0;
  opacity: 0;
  transition: opacity 240ms ease;
}
.rv-photo .rv-img.on { opacity: 1; }
html[data-motion='off'] .rv-photo .rv-img { transition: none; }

/* The review form - the app's moment of truth, and it dresses like it.
   The authored intent kept: fields wear the PAGE's clothes, editable in
   place, ruled quietly, lit only on focus. (This treatment was written
   for this pane and worn only inside dialogs until the first live
   reading rendered in raw browser chrome - owner, 2026-08-28.) Scoped
   to the pane so the dialogs' boxed fields stay their own. */
.rv-form .rv-label,
.rv-more .rv-label {
  display: block;
  width: auto;
  margin: 16px 0 0;
  font-size: 0.78rem;
  color: var(--muted);
}
.rv-form .rv-in,
.rv-more .rv-in {
  display: block;
  width: 100%;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--ink);
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  padding: 6px 0;
}
.rv-form .rv-in:focus,
.rv-more .rv-in:focus { border-bottom-color: var(--blue); outline: none; box-shadow: none; }
/* The raw transcript is a DOCUMENT, not a line - it keeps a frame. */
.rv-form textarea.rv-in {
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 10px 12px;
  margin-top: 6px;
  line-height: 1.5;
}
.rv-row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 20px; }

.rv-h {
  font-family: var(--serif-display);
  font-size: 1.05rem;
  color: var(--navy);
  margin: 24px 0 2px;
}
/* A line: what it says, what it cost, whose it is. The amount keeps
   the money discipline; the split select stays small and to the side. */
.rv-item {
  display: grid;
  /* THE LABEL OWNS THE ROOM (owner, 2026-09-10: "Cucumber L" beside a
     price lounging in half the screen). The amount and the split take
     what they need and no more; every spare pixel names the item. */
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 8px;
  align-items: center;
  padding: 2px 0;
}
.rv-amt-cell {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
}
.rv-item .rv-item-amt {
  /* The field wears the NUMBER's width (owner, 2026-09-10: "if it's
     only 7.58 why take so much space") - review.js sets it in ch from
     the value, growing only when the figure does. This is the floor. */
  width: 3.5ch;
  min-width: 3.5ch;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.rv-item-biz.rv-item { padding-left: 8px; }
.rv-split {
  font: inherit;
  font-size: 0.8rem;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 4px;
  overflow: hidden;
  white-space: nowrap;
  /* The select's own text is INVISIBLE - the face span shows the
     letter; the native menu keeps the full words. The UA's up-down
     chevrons go too (owner, 2026-09-10) - the painted face made them
     noise; the letter sits centred instead. */
  appearance: none;
  -webkit-appearance: none;
  color: transparent;
}
.rv-split option { color: var(--ink); }
.rv-splitwrap { position: relative; display: inline-block; }
.rv-splitface {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  font-size: 0.8rem;
  color: var(--ink);
  font-family: var(--sans);
}
.rcpt-mod { margin: 0 0 4px; font-size: 0.8rem; color: var(--muted); }

/* The three-way answer: quiet pills, the chosen one lit. */
.rv-seg { display: inline-flex; gap: 6px; margin-top: 8px; }
.rv-seg button {
  font: inherit;
  font-size: 0.88rem;
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: var(--r-full);
  padding: 0.4rem 0.95rem;
  cursor: pointer;
  transition: transform var(--m-tap) ease-out;
}
.rv-seg button:active { transform: scale(0.96); }
.rv-seg button.on {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: transparent;
}

/* The rarely-needed fields fold away under a hairline. */
.rv-more {
  margin-top: 22px;
  border-top: 1px solid var(--rule);
  padding-top: 2px;
}
.rv-more summary {
  cursor: pointer;
  padding: 10px 0;
  font-size: 0.92rem;
  color: var(--muted);
}

/* The verdict FLOATS (owner, 2026-09-08): keep or discard is the whole
   point of this screen, so the two buttons ride fixed at the bottom,
   side by side, visible from the first moment - never something to
   scroll for. The dock is hidden on this route, so the space is theirs;
   the form carries clearance so nothing hides behind them. */
.rv-actions {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: max(10px, calc(var(--safe-b) + 2px));
  z-index: 40;
  width: min(calc(100% - 24px), 30rem);
  display: flex;
  align-items: stretch;
  gap: 0.7rem;
  margin: 0;
}
.rv-actions .btn {
  flex: 1 1 0;
  min-width: 0;
  min-height: 52px;
  font-size: 1rem;
  box-shadow: var(--shadow-float);
}
.rv-form { padding-bottom: calc(78px + var(--safe-b)); }

/* The empty states - painted by book, own and the capture queue, and
   never styled until the owner saw a bare paragraph jammed edge-to-edge
   (2026-08-26). An empty pane is a promise, not an absence: centered,
   measured, quiet, with a faint echo of the welcome's disc above it. */
.empty-note {
  display: block;
  max-width: 26rem;
  margin: 16vh auto 0;
  padding: 0 24px;
  text-align: center;
  color: var(--muted);
  font-family: var(--sans);
  font-size: 0.95rem;
  line-height: 1.6;
}
.empty-note::before {
  content: '';
  display: block;
  width: 44px;
  height: 44px;
  margin: 0 auto 16px;
  border-radius: 50%;
  background-image: var(--hero-art);
  opacity: 0.22;
}

/* ---------------------------------------------------------------------
   What I own - the photo-forward grid, the view opened for pleasure
   --------------------------------------------------------------------- */

.own-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 14px;
  max-width: 52rem;
  margin: 0 auto;
  padding: 14px 16px;
}
.own-card {
  display: flex;
  flex-direction: column;
  text-align: start;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  padding: 0;
  cursor: pointer;
  font-family: var(--sans);
  color: var(--ink);
}
.own-card:active { transform: scale(0.97); }
.own-name {
  font-size: 0.88rem;
  font-weight: 600;
  padding: 8px 10px 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.own-sub {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 2px 10px 10px;
  font-size: 0.76rem;
  color: var(--muted);
}
.own-price {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--ink);
}

/* ---------------------------------------------------------------------
   View Transitions - the thumbnail grows into the detail view. Native,
   no library; degrades to a plain swap where unsupported. JS puts
   .vt-receipt on the ONE tapped thumbnail (a shared name on two visible
   elements voids the transition), then removes it after.
   --------------------------------------------------------------------- */

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: var(--m-quick);
  animation-timing-function: var(--ease-enter);
}
.vt-receipt { view-transition-name: receipt-hero; }
::view-transition-group(receipt-hero) {
  animation-duration: var(--m-enter);
  animation-timing-function: var(--ease-enter);
}
::view-transition-old(receipt-hero),
::view-transition-new(receipt-hero) {
  height: 100%;
  object-fit: cover;
}
/* Reduced means reduced, here too - both signals. */
html[data-motion='reduce']::view-transition-group(*),
html[data-motion='reduce']::view-transition-old(*),
html[data-motion='reduce']::view-transition-new(*) {
  animation-duration: 0.001ms !important;
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 0.001ms !important;
  }
}

/* ---------------------------------------------------------------------
   Wider layouts: the dock walks to the left edge; the grid breathes
   --------------------------------------------------------------------- */

@media (min-width: 1024px) {
  .dock {
    left: 24px;
    right: auto;
    transform: none;
    top: 50%;
    bottom: auto;
    translate: 0 -50%;
    flex-direction: column;
    height: auto;
    padding: 14px 8px;
  }
  .fab { transform: none; margin: 8px 0; }
  .fab:active { transform: scale(0.93); }
  .pane { padding-bottom: 24px; }
  .own-grid { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
}

@media (max-width: 34rem) {
  .home-body { padding-left: 12px; padding-right: 12px; }
}

/* ---------------------------------------------------------------------
   WALKTHROUGH 2 (owner-driven, pixels-first) - the shared pane body,
   the book's toolbar, stacked modal fields. Fixes measured against the
   live screens, not the stylesheet's hopes.
   --------------------------------------------------------------------- */

/* se-body is what seats, orgs and the share panes actually paint into -
   it had NO styles, so every one of those panes ran edge-to-edge. */
.se-body {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 10px 16px calc(var(--dock) + 8px);
  font-family: var(--sans);
}

/* The book's toolbar: a real search field and select, in the measure -
   they rendered as naked native controls at the viewport edge. */
.bk-tools {
  display: flex;
  align-items: center;
  gap: 8px;
  max-width: var(--measure);
  margin: 8px auto 0;
  padding: 0 16px;
}
.bk-q {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: max(1em, 16px); /* scales with the step, never under the iOS no-zoom floor */
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-full);
  padding: 9px 14px;
  outline: none;
}
.bk-q:focus { border-color: var(--blue); }
.bk-cat {
  font: inherit;
  font-size: 0.9rem;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 8px 10px;
  max-width: 11rem;
}
.bk-pins {
  max-width: var(--measure);
  margin: 6px auto 0;
  padding: 0 16px;
}
/* An empty strip must not paint as an empty pill. */
.bk-pins:empty { display: none; }

/* Modal form fields stack: label over a full-width input, never a
   cramped inline pair with a clipped placeholder. */
#modal .rv-label { display: block; margin: 12px 0 4px; }
#modal input.rv-in, #modal select.rv-in {
  display: block;
  width: 100%;
  box-sizing: border-box;
  font: inherit;
  font-size: max(1em, 16px); /* scales with the step, never under the iOS no-zoom floor */
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 9px 12px;
  outline: none;
}
#modal input.rv-in:focus, #modal select.rv-in:focus { border-color: var(--blue); }
#modal select#setJur { max-width: 10rem; }

/* The home bar composes like the house bar: the mark holds the CENTER,
   the menu keeps its corner (owner, 2026-08-26 - the mark sat left). */
.tbar-home { position: relative; justify-content: center; }
.tbar-home #hmMenu { position: absolute; left: calc(8px + var(--safe-l)); }
/* The circle door MIRRORS the burger (owner, 2026-09-14): hard right,
   not beside the wordmark. Absolute like its twin, so the mark keeps
   the true centre whether the dots stand or not. */
.tbar-home #hmDots { position: absolute; right: calc(8px + var(--safe-r)); }

/* The drawer's mode tabs: two wordmarks, the active lens lit, the
   other waiting at half voice. The words ride the wm-* treatments the
   mark already owns on every theme. */
/* Three tabs in any language (owner, 2026-09-01, seen in Portuguese):
   words too long for one row WRAP, and the odd one out sits centred
   beneath the other two instead of being cut off. */
.drawer-head.mode-tabs { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; justify-content: center; }
.mode-tab {
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  padding: 6px 10px;
  cursor: pointer;
  /* Full-strength gold on every tab (owner, 2026-09-01: the halved
     opacity read as faded words, not as a quiet state) - the blue
     wash below is what marks the one you are standing in. */
  opacity: 1;
  transition: background var(--m-quick) ease-out;
}
.mode-tab .wm { font-family: var(--serif-display); font-size: 1.05rem; letter-spacing: -0.01em; }
/* Three gold bars under ONE light (owner, 2026-09-01): a shine stamped
   dead-centre on every word read as printed, not metal - three real
   bars would each catch the light somewhere else. The gradient gets
   room to slide, and each tab holds it at its own angle. */
.mode-tabs .wm-prod { background-size: 220% 100%; }
.mode-tabs .mode-tab:nth-child(1) .wm-prod { background-position: 85% 0; }
.mode-tabs .mode-tab:nth-child(2) .wm-prod { background-position: 30% 0; }
.mode-tabs .mode-tab:nth-child(3) .wm-prod { background-position: 60% 0; }
/* The active tab wears the ITZA word's own blue (owner, 2026-09-01) -
   as a LIGHT wash with the blue outline, not a solid slab (owner: the
   full blue read too dark). The same literals tab.css gives .wm-itza,
   theme for theme. */
.mode-tab.on {
  background: color-mix(in srgb, #0c7ffb 12%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, #0c7ffb 12%, transparent);
}
html[data-theme='deep'] .mode-tab.on,
html[data-theme='dark'] .mode-tab.on {
  background: color-mix(in srgb, #4d9bff 16%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, #4d9bff 16%, transparent);
}

/* QUIET SCROLLBARS everywhere content scrolls (the settings dialog wore
   the platform's fat grey default - owner, 2026-08-26). Thin, tokened,
   trackless; the content is the thing, the scrollbar is furniture.

   .pane IS NOT IN THIS LIST, deliberately (found in review, 2026-09-14).
   tab.css hides the pane's bar outright and brings a thin one back only
   inside `(hover: hover) and (pointer: fine)` - a phone shows none, a
   mouse without a wheel gets one. Naming .pane here, unconditionally and
   later in the cascade, quietly undid exactly that: a phone grew a pane
   scrollbar, and the desktop bar came back 8px where tab.css asks for
   10. The pane's scrollbar belongs to tab.css alone. */
#modal, .drawer, .langlist, .tsc-sample, .se-body, .home-body {
  scrollbar-width: thin;
  scrollbar-color: var(--rule) transparent;
}
#modal::-webkit-scrollbar, .drawer::-webkit-scrollbar,
.langlist::-webkit-scrollbar, .tsc-sample::-webkit-scrollbar,
.se-body::-webkit-scrollbar, .home-body::-webkit-scrollbar { width: 8px; height: 8px; }
#modal::-webkit-scrollbar-thumb, .drawer::-webkit-scrollbar-thumb,
.langlist::-webkit-scrollbar-thumb, .tsc-sample::-webkit-scrollbar-thumb,
.se-body::-webkit-scrollbar-thumb, .home-body::-webkit-scrollbar-thumb {
  background: var(--rule);
  border-radius: 8px;
}
#modal::-webkit-scrollbar-thumb:hover,
.drawer::-webkit-scrollbar-thumb:hover { background: var(--muted); }
#modal::-webkit-scrollbar-track,
.drawer::-webkit-scrollbar-track { background: transparent; }

/* The settings identity card: who this is, in one calm block - the
   monogram, the name, the one line that explains the account model. */
.set-id {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  margin-bottom: 12px;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--surface) 60%, transparent);
}
.set-avatar {
  flex: none;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-sm);
  background: var(--hero-art);
  color: #fff;
  font-family: var(--serif-display);
  font-weight: 700;
  letter-spacing: 0.02em;
}
.set-who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.set-who b { color: var(--navy); font-size: 1.05rem; }
/* The note's own paragraph margins (6px above, 13px below) pushed the
   visible text ~14px high of the avatar's center while the BOXES
   centered perfectly - measured against the owner's eye. Inside the
   card the column's gap is the only spacing. */
.set-who .setNote { margin: 0; }

/* The settings tabs: QUIET, the active one lit - six identical filled
   pills gave the active tab nowhere to be (live, vs the house dialog).
   ONE ROW ON EVERY WIDTH (owner rule, broken again by a phone): the
   rail scrolls sideways under the finger instead of wrapping into a
   second row, scrollbar hidden, the lit tab pulled into view. */
.set-tabs {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  margin-bottom: 12px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
/* On a desktop the settings dialog is wide enough for its whole row of
   tabs - cut-off buttons with an invisible scrollbar read as missing
   (owner, 2026-09-01). A phone keeps the narrow dialog and the swipe. */
@media (min-width: 768px) {
  #modal:has(.set-tabs) { max-width: 44rem; }
  /* The whole row fits here, so it sits centred (owner). */
  .set-tabs { justify-content: center; }
}
.set-tabs::-webkit-scrollbar { display: none; }
.set-tab { flex: none; white-space: nowrap; }
.set-tab {
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 0.45rem 0.8rem;
  font: inherit;
  font-size: 0.88rem;
  cursor: pointer;
  transition: transform var(--m-tap) ease-out;
}
.set-tab:active { transform: scale(0.96); }
.set-tab.on {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: transparent;
}

.fact-strong .fact-label { color: var(--ink); font-weight: 600; }
.fact-strong .fact-value { font-size: 1.05rem; }

/* Billing's fact rows - painted for years, styled never ("My
   Allowance1000", live). A fact is a row: label left, number right. */
/* fact-strong is a WHOLE token, never appended beside fact - the i18n
   prose scanner reads multi-word class strings as English (house rule),
   so both tokens carry the base row here. */
.fact, .fact-strong {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--rule);
}
.fact-label { color: var(--muted); }
.fact-value { font-weight: 600; font-variant-numeric: tabular-nums; }

/* Action rows in the share/org panes: centered - a lone primary button
   hugging the left edge under a centered empty state read as lopsided
   (owner, on the seats pane). The note that follows one centers with it. */
.rd-actions {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px;
  margin: 16px 0 6px;
}
.rd-actions + .setNote { text-align: center; }

/* The waiting list breathes: room between the add button, the cards
   and each other (owner: the top three sat too close). */
.qu-list { margin-top: 18px; }
.qu-list .rc-row { margin: 10px 0; }

/* The queue card's left column - date above status, never glued on one
   line (the class shipped unstyled; the owner's phone showed
   "Aug 27Waiting to be read" as one run of words). */
.rc-left {
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* The text column's floor (the rc-main law, worn by the class the
     rows actually paint): it grows, it may shrink, the amount never
     leaves its column. */
  flex: 1 1 auto;
  min-width: 0;
  text-align: left;
}

/* The install row's guided highlight: the nudge's SHOW ME lands here and
   the row breathes twice in the accent - easy to see, then done. */
@keyframes rowPulse {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  40% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--blue-bright) 45%, transparent); }
}
.setRow.pulse {
  animation: rowPulse 1.5s ease-out 2;
  border-radius: var(--r-sm);
}
html[data-motion='reduce'] .setRow.pulse,
html[data-motion='off'] .setRow.pulse {
  animation: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--blue-bright) 45%, transparent);
}

/* A file picker row: the house button, the chosen name at its side. */
.mbtns-file { justify-content: flex-start; align-items: center; gap: 10px; }
.mbtns-file .setNote { margin: 0; }

/* The mileage pane: the footer clears the dock, its fields wear the
   field treatment (rv-in was styled only inside the dialog - the
   place-name input rendered naked, owner-seen), and an empty note
   INSIDE a section sits with its section, not 16vh adrift. */
.mi-wrap { padding-bottom: calc(var(--dock) + 24px); }
.mi-wrap .rv-in {
  display: block;
  width: 100%;
  box-sizing: border-box;
  font: inherit;
  font-size: max(1em, 16px); /* scales with the step, never under the iOS no-zoom floor */
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  padding: 9px 12px;
  outline: none;
  margin-bottom: 8px;
}
.mi-wrap .rv-in:focus { border-color: var(--blue); }
.mi-wrap .empty-note { margin-top: 20px; margin-bottom: 8px; }

/* ---------------------------------------------------------------------
   The language screen - one pre-answered question, before everything.
   Copied from an earlier ITZA app (spec 8.1/8.5), riding Tab's tokens unchanged.
   --------------------------------------------------------------------- */
/* The language screen is styled ONCE, further up this file. A second,
   near-identical copy lived here and - being later - won: it hardcoded
   a 12px radius where the original uses var(--r-md), dropped the h1's
   colour and dropped overscroll-behavior from the list. Removed in
   review, 2026-09-14; the surviving copy is the tokened one. */

/* THE QUESTION MARK (owner, 2026-09-14) - a statement's explanation is
   always one tap away, and the dot itself never shouts: it sits at the
   end of the sentence it explains, sized in em so it grows with the
   words beside it at every text-size setting. */
/* ONE DRAWING, PLACED EVERYWHERE (owner, 2026-09-14). This used to be a
   CSS circle with a font '?' centred in it, and the two would not agree:
   a glyph is hinted and snapped by the text engine, a border is not, and
   the font's own metrics decide where the ink lands. The same mark came
   out right beside "App lock" and wrong beside "Guidance" - the only
   difference being that one ends in a tall letter and the other does
   not. The circle and the question mark are now a single icon from the
   house set, so the drawing is correct by construction, everywhere,
   and nothing here has to be nudged into place. */
.helpdot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Sized against the words it follows, so it grows with them at every
     text-size step. The icon's circle fills 0.83 of its box, which puts
     the visible circle at ~0.88em of the text. */
  /* A button does NOT inherit font-size, and every length here is in em:
     without this the mark would freeze at the browser's default 13.33px
     and stop growing with the reader's text. */
  font-size: inherit;
  width: 1.05em;
  height: 1.05em;
  flex: none;
  margin-inline-start: 0.3em;
  vertical-align: middle;
  /* `middle` is the x-height centre; a capital's centre is 0.094em
     higher. Half of that gap sits the mark between the two, so it looks
     settled after a tall letter and after a short one alike. */
  position: relative;
  top: -0.047em;
  padding: 0;
  color: var(--blue);
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: transform var(--m-tap) ease-out;
}
.helpdot svg { width: 100%; height: 100%; display: block; }
.helpdot:active { transform: scale(0.9); }
/* ABOVE the sheet (81) and the modal (60): an explanation raised from
   inside a sheet must not open underneath the thing it explains. */
.help-scrim {
  position: fixed;
  inset: 0;
  z-index: 95;
  display: flex;
  align-items: safe center;
  justify-content: center;
  padding: 1rem;
  overflow-y: auto;
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity var(--m-quick);
}
.help-scrim.on { opacity: 1; }
.help-card {
  width: min(100%, 24rem);
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-float);
  padding: 18px;
  font-family: var(--sans);
}
.help-head {
  font-family: var(--serif-display);
  color: var(--accent);
  font-size: 1.2rem;
  margin-bottom: 10px;
}
.help-body { font-size: 0.95rem; line-height: 1.5; margin: 0 0 4px; }

/* The shopping add-row's field reads at the chips' size, and rides the
   reader's preference with them (owner, 2026-09-14). Declared HERE, not
   beside the rest of its rule further down, because the floor block
   below must remain the last font-size rule in the cascade - from up
   here it can out-rank this on iOS, exactly as it does every other
   field in the app. */
.sh-addrow input,
.sh-addrow .btn { font-size: 0.9rem; }

/* ONE VOICE IN THE CHOICES SHEET (owner, 2026-09-14: "there's 3
   different font sizes by looks of it"). It was five - the question at
   0.92rem, the pill buttons 0.88, the labels 0.82, the close button
   0.85 and the note under a label 0.74. They are all the same KIND of
   thing, so they are all one size now. Every value is in rem, so the
   whole sheet still grows and shrinks with the reader's text setting -
   only the disagreement is gone. The sheet's own title and the stars
   stay larger, being plainly different things. */
.sh-sheet .mtext,
.sh-sheet .rv-label,
.sh-sheet .setNote,
.sh-sheet .check-row,
.sh-sheet .rv-seg button,
.sh-sheet .mbtns .btn { font-size: 0.92rem; }

/* ---------------------------------------------------------------------
   The iOS field floor USED TO LIVE HERE and now lives at the end of
   receipt.css, which is the last stylesheet index.html loads. It called
   itself the last font-size rule in the cascade while seventy rules sat
   below it in this file and a whole stylesheet loaded after it - true
   only by everyone's good behaviour. Moving it to the actual end makes
   the ordering real, and verify_css_floor.mjs now fails the gate if any
   rule that can match a field declares font-size after it.
   --------------------------------------------------------------------- */


/* While a native View Transition is snapshotting a pane change, the CSS
   keyframes stand down - two systems animating one move reads as jank
   (spec 8.3; nav.js sets vt-live for exactly the transition's life). */
html.vt-live .pane-enter,
html.vt-live .pane-exit,
html.vt-live .pane-fade { animation: none !important; }

/* The right-click menu (spec 8.3 desktop equivalents): the pointer's
   swipe-rail. Positioned at the pointer by book.js; one open at a time. */
.ctx-menu {
  position: fixed; z-index: 90; min-width: 12rem;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--r-md);
  box-shadow: var(--shadow-float); padding: 0.3rem; 
}
.ctx-menu button {
  display: block; width: 100%; text-align: left;
  font: 500 0.95rem/1.3 var(--sans); color: var(--ink);
  background: none; border: none; border-radius: var(--r-sm);
  padding: 0.55rem 0.7rem; cursor: pointer;
}
.ctx-menu button:hover { background: var(--hover, rgba(127,127,127,0.12)); }
.ctx-menu button.danger { color: var(--danger); }

/* =====================================================================
   MILEAGE (register item B4) - APPENDED BLOCK, one owner: js/mileage.js.
   Every colour rides the tokens; z-indexes follow the header convention
   (sticky bars 20, dock 30, scrims 60+). NO font-size rule here targets
   input/textarea/select, so the iOS field floor above stays the last
   word on fields.
   ===================================================================== */

/* The running-trip bar: fixed, on every screen, tapped to open the trip
   pane. It rides --dock (which already carries --safe-b) so it always
   floats clear of the dock and can never cover the FAB. Under the dock
   (30) and every scrim, over sticky bars (20). */
.mi-bar {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--dock) + 8px);
  z-index: 29;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  max-width: min(92vw, 30rem);
  padding: 8px 16px;
  border: 1px solid var(--rule);
  border-radius: var(--r-full);
  background: var(--frost);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  backdrop-filter: blur(18px) saturate(1.3);
  box-shadow: var(--shadow-float);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 0.85rem;
  cursor: pointer;
}
.mi-bar[hidden] { display: none !important; }
.mi-bar:active { transform: translateX(-50%) scale(0.97); }
.mi-bar-dot {
  width: 9px;
  height: 9px;
  flex: none;
  border-radius: var(--r-full);
  background: var(--accent);
  animation: miPulse 1.6s ease-in-out infinite;
}
@keyframes miPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}
html[data-motion='reduce'] .mi-bar-dot,
html[data-motion='off'] .mi-bar-dot { animation: none; }
@media (prefers-reduced-motion: reduce) { .mi-bar-dot { animation: none; } }
.mi-bar-label { color: var(--muted); }
.mi-bar-dist { font-weight: 600; white-space: nowrap; }
.mi-bar-time { color: var(--muted); white-space: nowrap; }

/* The pane's content column. Extra bottom air so the running bar never
   sits on the last row of the pane that describes it. */
.mi-wrap {
  max-width: 42rem;
  margin: 0 auto;
  padding: 12px 16px 64px;
}
.mi-h {
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 22px 0 8px;
}

/* The home-surface card: one tap to start (the orchestrator embeds it). */
.mi-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  width: 100%;
  text-align: start;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-card);
  padding: 12px 14px;
  cursor: pointer;
  font-family: var(--sans);
  color: var(--ink);
}
.mi-card:active { transform: scale(0.985); }
.mi-card-title { font-weight: 600; font-size: 0.95rem; }
.mi-card-sub { font-size: 0.78rem; color: var(--muted); }

/* The three ways to measure, explicit, in falling order of accuracy. */
.mi-methods {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 10px 0;
}
.mi-method {
  display: flex;
  flex-direction: column;
  gap: 3px;
  text-align: start;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-card);
  padding: 12px 14px;
  cursor: pointer;
  font-family: var(--sans);
  color: var(--ink);
}
.mi-method:active { transform: scale(0.985); }
.mi-method b { font-size: 0.95rem; }
.mi-method span { font-size: 0.78rem; color: var(--muted); line-height: 1.45; }

/* The open trip: the number leads, the wake-lock line is stated plainly
   with its one-tap stop beside it. */
.mi-open {
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-card);
  padding: 18px 16px 14px;
  margin-bottom: 8px;
}
.mi-live {
  font-family: var(--sans);
  font-size: 2rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  margin: 0;
}
.mi-live-sub {
  font-family: var(--sans);
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  margin: 2px 0 8px;
}
.mi-badge-line { margin: 0 0 8px; }
.mi-wake {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px;
  font-family: var(--sans);
  font-size: 0.78rem;
  color: var(--ink);
  border: 1px dashed var(--rule);
  border-radius: var(--r-md);
  padding: 8px 10px;
  margin: 0 0 10px;
}
.mi-wake[hidden] { display: none !important; }
.mi-open-acts { display: flex; flex-direction: column; gap: 8px; }
.mi-meas {
  font-family: var(--sans);
  font-size: 1.3rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  margin: 4px 0 8px;
}

/* Method labels that must not lie: 'measured' rides a solid edge,
   'estimate' a dashed one - visibly a different kind of number. */
.mi-badge {
  display: inline-block;
  font-family: var(--sans);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--rule);
  border-radius: var(--r-full);
  padding: 1px 8px;
  vertical-align: middle;
}
.mi-badge-tracked { color: var(--accent); border-color: var(--accent); }
.mi-badge-twopoint { border-style: dashed; }

/* Trip rows: name -> name, never coordinates; the claim right-aligned
   in tabular numerals like every other figure in the app. */
.mi-trip {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: start;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--rule);
  padding: 10px 2px;
  cursor: pointer;
  font-family: var(--sans);
  color: var(--ink);
}
.mi-trip:active { opacity: 0.7; }
.mi-trip-left { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.mi-route {
  font-weight: 600;
  font-size: 0.95rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mi-sub { font-size: 0.76rem; color: var(--muted); }
.mi-claim {
  flex: none;
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
  font-weight: 600;
}

/* Saved places: the names that make a claim form readable. Coordinates
   are detail, muted and small - never a trip's name. */
.mi-place {
  display: flex;
  align-items: center;
  gap: 10px;
  border-bottom: 1px solid var(--rule);
  padding: 4px 2px;
  font-family: var(--sans);
}
.mi-place-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.mi-coords {
  flex: none;
  font-size: 0.72rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.mi-place-x { width: 36px; height: 36px; color: var(--muted); }
.mi-place-add {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
}
.mi-place-btns { display: flex; gap: 8px; flex-wrap: wrap; }

/* BUSINESS HIGHLIGHTS (register A2) - APPENDED BLOCK. Business lines are
   visually distinct (spec 7.1: highlight it, do not hide it); the review
   leads with the proposal; the book's running total sits under the tools.
   Tokens only. */
.rv-bizlead {
  display: flex; flex-direction: column; gap: 0.25rem;
  border: 1px solid var(--rule); border-left: 3px solid var(--muted);
  border-radius: var(--r-md); padding: 0.7rem 0.9rem; margin: 0.8rem 0;
}
.rv-bizlead.rv-biz-yes { border-left-color: var(--accent); }
.rv-item-biz .rv-item-label,
.rv-item-biz.rv-item { border-left: 3px solid var(--accent); }
.bk-bizbar .num { color: var(--ink); font-weight: 700; }

/* SEATS (stage 2 item 1) - APPENDED BLOCK. The accountant seat: the
   owner's seat rows, the grant sheet's scope words, the reader-side
   shared book. Tokens only; the shapes follow the book's rows and the
   settings' notes so the pane reads as the same product. */
.seat-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 2px;
  border: 0;
  border-bottom: 1px solid var(--rule);
  background: none;
  text-align: left;
  font-family: var(--sans);
  color: var(--ink);
}
.seat-row-go { cursor: pointer; }
.seat-row-go:active { transform: scale(0.985); }
.seat-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.seat-who {
  font-size: 0.95rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.seat-scope { font-size: 0.78rem; color: var(--muted); }
.seat-pending { border-left: 3px solid var(--danger); padding-left: 8px; }
.seat-pending .seat-who { font-weight: 400; font-size: 0.85rem; color: var(--danger); }

/* The grant sheet: the scope stated in plain words, set off so the
   sentence is the thing the eye lands on before the button. */
.seat-widen {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0.6rem 0 0.2rem;
  font-family: var(--sans);
  font-size: 0.9rem;
}
.seat-scope-say {
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-md);
  padding: 0.6rem 0.8rem;
  margin: 0.6rem 0 0.2rem;
  font-family: var(--sans);
  font-size: 0.9rem;
}
.seat-err {
  color: var(--danger);
  font-family: var(--sans);
  font-size: 0.85rem;
  margin: 0.4rem 0 0;
}

/* The home chip rides the need-card shape; only the accent line marks
   it as an inbound thing rather than a chore. */
.seat-chip { border-left: 3px solid var(--accent); }

/* The reader side: the photograph of a receipt somebody shared.
   These two rules were invisible for a while - the comment above them
   lost its terminator, so a CSS parser read them as prose and the photo
   rendered at its own pixel size: 725x1400 inside a 560px figure, live
   on itzatab.com under v195 (owner, 2026-09-15). Braces stayed balanced
   and every suite stayed green, which is why the gate could not see it.
   A .shared-banner rule used to sit here too; the markup says that with
   setNote now, so it is gone rather than kept as decoration. */
.shared-photo {
  margin: 0.6rem 0;
  border-radius: var(--r-md);
  overflow: hidden;
}
.shared-photo img {
  display: block;
  width: 100%;
  max-height: 40vh;
  object-fit: contain;
  background: var(--surface);
}

/* THE DONE TICK (owner, 2026-09-15): a green disc with a white check,
   centred, that lives for half a second. It rises and fades on its own -
   pointer-events none, so it can never intercept the next tap, and it is
   removed from the document when it finishes. --ok carries the green and
   is already defined for both themes, so the disc stays legible in dark.
   The motion is skipped for anyone who asked for less of it. */
.tickpop {
  position: fixed;
  left: 50%;
  /* ABOVE THE CAMERA (owner, 2026-09-15). --dock is the bar plus the safe
     area; the capture button rides 14px higher than the bar, so clearing
     the dock and then the button puts the disc just over the shutter,
     where the thumb already is. */
  bottom: calc(var(--dock) + 22px);
  width: 72px;
  height: 72px;
  margin: 0 0 0 -36px;
  border-radius: var(--r-full, 999px);
  /* A BADGE colour, not a text colour: --ok is tuned for contrast against
     the paper and goes pale in dark, which is wrong for a filled disc that
     carries its own white mark. This green holds in both themes, the way
     --gold does. */
  background: var(--ok-badge, #00a14b);
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.22);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 96;
  pointer-events: none;
  animation: tickPop 520ms ease-out forwards;
}
.tickpop svg {
  width: 44px;
  height: 44px;
  fill: #fff;
  stroke: none;
}
@keyframes tickPop {
  0%   { opacity: 0; transform: scale(0.72); }
  26%  { opacity: 1; transform: scale(1.06); }
  40%  { transform: scale(1); }
  72%  { opacity: 1; }
  100% { opacity: 0; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .tickpop { animation: tickFade 520ms linear forwards; }
  @keyframes tickFade { 0%, 72% { opacity: 1; } 100% { opacity: 0; } }
}

/* =====================================================================
   MATCH (stage 3 item 5) - APPENDED BLOCK, one owner: js/match.js.
   Statement matching: the picker rows and the three result lists.
   Every colour rides the tokens. NO font-size rule here targets
   input/textarea/select, so the iOS field floor above stays the last
   word on fields.
   ===================================================================== */

/* A result row: date | description | amount. Buttons where a receipt
   can be opened, plain divs where there is nothing to open - both wear
   the same clothes so the lists read as one table. */
.match-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  width: 100%;
  padding: 8px 2px;
  border: 0;
  border-bottom: 1px solid var(--rule);
  background: none;
  text-align: left;
  font-family: var(--sans);
  color: var(--ink);
}
button.match-row { cursor: pointer; }
button.match-row:active { transform: scale(0.985); }
.match-when {
  flex: none;
  font-size: 0.78rem;
  color: var(--muted);
  white-space: nowrap;
}
.match-desc {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.9rem;
}
.match-sub {
  display: block;
  font-size: 0.76rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
}
.match-amt {
  flex: none;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* The actionable list is the one marked: a spend with no receipt takes
   the danger edge (the seat-pending convention), matched and merely
   unmatched-the-other-way rows stay quiet. */
.match-miss { border-left: 3px solid var(--danger); padding-left: 8px; }
.match-miss .match-amt { color: var(--danger); }

/* Group headings carry their counts in tabular numerals like every
   other figure in the app (.num rides the heading's number spans). */
.match-group {
  margin: 1.1rem 0 0.2rem;
  font-family: var(--sans);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The column picker: label above field, one per role - the same quiet
   shape the review screen's fields wear. */
.match-pick { margin: 0.5rem 0; }
.match-pick-err { color: var(--danger); }

/* =====================================================================
   ORGS (stage 3) - APPENDED BLOCK, one owner: js/orgs.js.
   Organisations: the funds head, member and claim rows, the one-tap
   decision strip, the audit trail. Tokens only; rows wear the seat-row
   clothes so the pane reads as the same product, and the honest-limit
   callouts wear the seat-scope-say shape - a sentence set off for the
   eye to land on before any button. NO font-size rule here targets
   input/textarea/select, so the iOS field floor stays the last word on
   fields.
   ===================================================================== */

/* The funds head: the custodial balance is the pane's headline figure -
   what an approval can actually pay is the first thing a team asks. */
.org-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  margin: 0.8rem 0 0.2rem;
  font-family: var(--sans);
}
.org-allow {
  font-size: 1.9rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.org-allow-label { font-size: 0.85rem; color: var(--muted); }

/* The require-kyc mark: a quiet chip, accent-edged like the seat chip -
   a property of the org, never an alarm. */
.org-kyc {
  font-size: 0.72rem;
  color: var(--muted);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-full);
  padding: 2px 10px;
  white-space: nowrap;
}

/* Section headings pace the single scroll: funds, approvals, members,
   decided, trail (the month-h voice, without its total slot). */
.org-sec {
  margin: 1.4rem 0 0.2rem;
  font-family: var(--sans);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink);
}

/* A row: member, claim or listed org - the seat-row shape verbatim so
   the two panes read as siblings. */
.org-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 2px;
  border: 0;
  border-bottom: 1px solid var(--rule);
  background: none;
  text-align: left;
  font-family: var(--sans);
  color: var(--ink);
}
.org-row-go { cursor: pointer; }
.org-row-go:active { transform: scale(0.985); }
.org-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.org-name {
  font-size: 0.95rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.org-sub { font-size: 0.78rem; color: var(--muted); }
.org-amt {
  flex: none;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* A claim awaiting decision carries the accent edge (the actionable
   list is the one marked - the match-miss convention, but this list is
   an invitation, not a danger). Its decision strip sits under the row,
   outside the tap target that opens the detail. */
.org-exp, .org-exp-open { border-bottom: 1px solid var(--rule); }
.org-exp > .org-row, .org-exp-open > .org-row { border-bottom: 0; }
.org-exp-open > .org-row { border-left: 3px solid var(--accent); padding-left: 8px; }
.org-acts {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 0 2px 10px 11px;
  font-family: var(--sans);
}
.org-noteline { color: var(--ink); font-size: 0.82rem; }

/* The honest callouts: the not-attested approver, the not-a-member
   view, the rotation limit - a bordered sentence, never a toast. */
.org-honest {
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-md);
  padding: 0.6rem 0.8rem;
  margin: 0.6rem 0 0.2rem;
  font-family: var(--sans);
  font-size: 0.9rem;
}

/* Sheet furniture: the create toggle, the receipt pick line, the
   field-level refusal. */
/* A checkbox row anywhere: box beside words, one tap target. The
   generic twin of the org sheet's toggle, for settings and any pane. */
.check-row,
#modal label.check-row,
.mform label.check-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0.9rem 0 0.3rem;
  font-family: var(--sans);
  font-size: 0.92rem;
  color: var(--ink);
  cursor: pointer;
}
.check-row input[type="checkbox"] {
  width: 1.1rem;
  height: 1.1rem;
  flex: none;
  margin: 0;
}
.check-row span { flex: 1; }

/* The check-row as a BUTTON (the area pickers): the global button
   rule paints an accent fill, which turned a quiet list row into a
   navy pill (owner, 2026-09-12, gas area sheet - the Comparison
   sheet had it too). The row variant must stay a row. */
button.check-row {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0.15rem 0;
  width: 100%;
  text-align: left;
  font-size: 0.92rem;
  color: var(--ink);
}
button.check-row svg { color: var(--blue); flex: none; }

/* The gas board's area name is itself the link into the picker. */
button.gx-arealink {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--blue);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

/* .mform label declares display:block at higher specificity, which
   silently flattened this flex row - the checkbox glued to its words
   (owner, twice). The selector carries the form scope so the row WINS. */
.org-toggle,
.mform label.org-toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0.9rem 0 0.3rem;
  font-family: var(--sans);
  font-size: 0.92rem;
  cursor: pointer;
}
.org-toggle span { flex: 1; }
.org-pickline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: 0.4rem 0;
  font-family: var(--sans);
}
.org-err {
  color: var(--danger);
  font-family: var(--sans);
  font-size: 0.85rem;
  margin: 0.4rem 0 0;
}

/* The claim detail facts (the settings fact voice, local so the sheet
   does not lean on another pane's block). */
.org-fact {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 2px;
  border-bottom: 1px solid var(--rule);
  font-family: var(--sans);
}
.org-fact-label { font-size: 0.8rem; color: var(--muted); white-space: nowrap; }
.org-fact-value { font-size: 0.9rem; text-align: right; overflow-wrap: anywhere; }
.org-note {
  font-family: var(--sans);
  font-size: 0.9rem;
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: 0.6rem 0.8rem;
  margin: 0.6rem 0;
}

/* The audit trail: sequence-numbered rows, newest first. The number
   column keeps the trail readable as the record it is - and every
   figure rides tabular numerals like everywhere else. */
.org-audit-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 8px 2px;
  border-bottom: 1px solid var(--rule);
  font-family: var(--sans);
  color: var(--ink);
}
.org-audit-seq {
  flex: none;
  min-width: 2.2em;
  font-size: 0.78rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.org-audit-act { font-size: 0.9rem; font-weight: 600; }

/* ---------------------------------------------------------------------
   Dock shortcuts (owner, 2026-09-04): two extra doors, one each side.
   Five items must still fit a small phone: the shortcut labels crop
   politely instead of pushing the dock past the screen edge.
   --------------------------------------------------------------------- */
.dock-short span {
  max-width: 4.2rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (max-width: 400px) {
  /* The OUTER BUFFER (owner, 2026-09-14): a long label - Shopping,
     Comparison in some languages - is wider than its 52px button and
     spills a little each side. With only 8px of pill padding the
     outermost label sat against the glass edge, so the buttons step
     inward and the rim gets room. max-width keeps the widened pill
     honest on the narrowest phones. */
  .dock {
    gap: 2px;
    padding: 0 14px;
    max-width: calc(100vw - 16px);
  }
  .dock-btn { width: 52px; }
}

/* The chooser: one row per destination, the current one marked. */
.dock-pick {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 0.7rem 0.4rem;
  border: 0;
  background: none;
  font-family: var(--sans);
  font-size: 0.95rem;
  color: var(--ink);
  cursor: pointer;
  border-bottom: 1px solid var(--rule);
  text-align: start;
}
.dock-pick svg:first-child { color: var(--muted); flex: none; }
.dock-pick span { flex: 1 1 auto; }
.dock-pick.on { color: var(--navy); font-weight: 600; }
.dock-pick.on svg { color: var(--navy); }

/* The "did you know" bubble: floats above the dock, arrow down. */
.dock-tip {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(max(8px, calc(var(--safe-b) - 8px)) + var(--bar-h) + 14px);
  z-index: 31;
  width: min(88vw, 21rem);
  background: var(--surface);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-float);
  padding: 12px 14px;
  animation: tipUp 240ms var(--ease-enter);
}
/* No arrow tail: the tip POINTS by lighting the two outer buttons
   themselves with the house pulse (owner, 2026-09-04 - an arrow at the
   middle said the wrong thing, and two arrows looked wrong too). */
.dock-tip .dock-tip-txt b { color: var(--navy); }
/* The pulse loops for as long as the tip is up - the pair stays lit
   until the person answers the bubble. */
.dock-short.pulse-attn { animation: pulseAttn 1400ms ease-in-out infinite; color: var(--navy); }
.dock-tip-txt {
  margin: 0 0 8px;
  font-family: var(--sans);
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--ink);
}
.dock-tip-btns { display: flex; align-items: center; gap: 12px; }
/* The quiet link rides the row's midline here - its usual life is BELOW
   a button stack, and that stance's top margin dragged it low. */
.dock-tip-btns .mlink { margin: 0; }
@keyframes tipUp {
  from { opacity: 0; transform: translateX(-50%) translateY(8px); }
  to { opacity: 1; transform: translateX(-50%) translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .dock-tip { animation: none; }
}

/* ---------------------------------------------------------------------
   Price history graph (owner, 2026-09-04): dates across, prices up,
   green fell / red rose - the pattern at a glance.
   --------------------------------------------------------------------- */
.ph-chart { margin: 4px 0 2px; }
.ph-chart svg { width: 100%; height: auto; display: block; }
.ph-line { fill: none; stroke: var(--navy); stroke-width: 2; stroke-linejoin: round; }
.ph-dot.up { fill: var(--danger, #c62828); }
.ph-dot.down { fill: var(--ok, #2e7d32); }
.ph-dot.flat, .ph-dot.first { fill: var(--muted); }
/* The price in hand, distinct from history: gold, ringed. */
.ph-dot.today { fill: var(--gold, #c99a10); stroke: var(--navy); stroke-width: 2; }
.ph-lab { font-family: var(--sans); font-size: 10px; fill: var(--muted); }
.ph-vg rect {
  fill: var(--surface);
  opacity: 0.93;
  stroke: var(--rule);
  stroke-width: 1;
}
.ph-vg text { font-family: var(--sans); font-size: 10px; fill: var(--ink); }
.ph-vg.today rect { stroke: var(--gold, #c99a10); opacity: 1; }
.ph-vg.today text { font-weight: 700; fill: var(--navy); }
.ph-lab.end { text-anchor: end; }
.ph-key {
  display: inline-block;
  width: 10px; height: 10px;
  border-radius: 50%;
  margin-inline-end: 5px;
}
.ph-legend { display: flex; align-items: center; gap: 16px; margin: 2px 0 6px; }
.ph-leg {
  display: inline-flex;
  align-items: center;
  font-family: var(--sans);
  font-size: 0.8rem;
  color: var(--muted);
}
.ph-pill.up rect { fill: var(--danger, #c62828); }
.ph-pill.down rect { fill: var(--ok, #2e7d32); }
.ph-pill text {
  fill: #fff;
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 700;
}
.ph-key.down { background: var(--ok, #2e7d32); }
.ph-key.up { background: var(--danger, #c62828); }

/* The capture journal's readout: small mono, its own scroll, never the
   page's. Metadata only lives here - see capture.js JOT. */
.cap-log {
  max-height: 46vh;
  overflow: auto;
  font-size: 0.72rem;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
  background: color-mix(in srgb, var(--ink) 4%, transparent);
  border-radius: var(--r-md);
  padding: 10px 12px;
  margin: 4px 0 10px;
}

/* ---------------------------------------------------------------------
   The in-app camera (owner, 2026-09-08): the viewfinder is the page's
   own - nothing leaves the app, so Android cannot eat the shot.
   --------------------------------------------------------------------- */
.cam {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: #000;
}
/* THE HIDDEN ATTRIBUTE MUST ALWAYS WIN here too (the dock learned this
   first): the torch and Done carry display-setting classes that would
   otherwise defeat [hidden] - seen live as stray buttons over a blank
   viewfinder (owner, 2026-09-09). */
.cam [hidden] { display: none !important; }
.cam video { background: #000; }
.cam video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.cam-top {
  position: absolute;
  top: calc(var(--safe-t) + 10px);
  left: 12px;
  right: 12px;
  display: flex;
  justify-content: space-between;
}
.cam-tbtn {
  width: 46px;
  height: 46px;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.45);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
.cam-tbtn.on { background: rgba(255, 255, 255, 0.85); color: #000; }
/* The icon's size is the BUTTON's law, not the svg attribute's - one
   LG rendered the bolt near-invisible (owner, 2026-09-09). */
.cam-tbtn svg { width: 26px; height: 26px; }
.cam-confirm {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--safe-b) + 26px);
  display: flex;
  justify-content: center;
  gap: 14px;
  padding: 0 18px;
}
.cam-confirm .btn {
  flex: 0 1 12rem;
  min-height: 52px;
  font-size: 1rem;
  box-shadow: var(--shadow-float);
}
.cam-bottom {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--safe-b) + 26px);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 22px;
}
.cam-shutter {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  border: 5px solid #fff;
  background: rgba(255, 255, 255, 0.28);
  cursor: pointer;
  transition: transform 90ms ease;
}
.cam-shutter:active { transform: scale(0.9); }
.cam-shutter:disabled { opacity: 0.5; }
.cam-count {
  min-width: 46px;
  text-align: center;
  color: #fff;
  font-family: var(--sans);
  font-size: 1.15rem;
  font-weight: 700;
}
.cam-done { position: absolute; right: 18px; }
.cam-flash {
  position: absolute;
  inset: 0;
  background: #fff;
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease;
}
.cam-flash.on { opacity: 0.85; }

/* The arithmetic alarm on the review: quiet when the numbers agree,
   unmissable when they do not. */
.rv-sum {
  margin: 10px 0 0;
  padding: 10px 12px;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--danger) 9%, transparent);
  color: var(--danger);
  font-family: var(--sans);
  font-size: 0.88rem;
  line-height: 1.5;
}

/* The one quiet line while the seals open - present, unhurried, gone
   the moment the book stands. */
.hm-opening {
  margin: 26px 0;
  font-family: var(--sans);
  color: var(--muted);
  font-size: 0.95rem;
  animation: openingPulse 1.2s ease-in-out infinite;
}
@keyframes openingPulse {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .hm-opening { animation: none; }
}

/* ------------------------------------------------------------------
   The gas board (gas.js): grade sections, station rows, brand badges.
   Badge colours are fixed brand pairs - both halves ship together and
   never follow the theme; the unbranded station wears house neutrals.
   ------------------------------------------------------------------ */
.gx-sec { margin-top: 1.15rem; }
.gx-row {
  display: flex; align-items: center; gap: 0.7rem;
  padding: 0.55rem 0.1rem;
  border-bottom: 1px solid var(--rule);
}
.gx-badge {
  width: 38px; height: 38px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 1.02rem; letter-spacing: 0.01em;
  flex: none;
}
.gx-badge[data-two] { font-size: 0.82rem; }
.gx-badge.plain { background: var(--surface); color: var(--navy); border: 1px solid var(--rule); }
.gx-badge.logo { background: #ffffff; border: 1px solid var(--rule); overflow: hidden; }
.gx-badge.logo img { width: 74%; height: 74%; object-fit: contain; display: block; }

/* The row is a BUTTON (it opens the history popup) - the global
   button rule's accent fill must not turn the board navy. */
/* An in-flight reading's status line: the need-card's face without
   its invitation - no pointer, no press, slightly settled. */
div.need-card.quiet { cursor: default; opacity: 0.75; }
div.need-card.quiet:active { transform: none; }

button.gx-row {
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  width: 100%;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.gx-name {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gx-price { display: flex; flex-direction: column; align-items: flex-end; font-weight: 600; }
.gx-fresh { font-size: 0.72rem; color: var(--muted); font-weight: 400; }
.gx-pcrow { display: flex; gap: 0.6rem; margin-top: 0.95rem; }
.gx-pcrow input { flex: 1; min-width: 0; }
.gx-pcrow .btn { flex: none; }

/* ------------------------------------------------------------------
   Product ratings (rate.js): the star chip on each detail line, the
   merchant star beside the title, and the two-tab popup.
   ------------------------------------------------------------------ */
.rt-chip {
  background: none; border: 0; padding: 0;
  cursor: pointer;
  /* The line is a BASELINE-aligned flex row, which hung the star high
     (its baseline is its bottom edge) - the chip centres itself
     against the line instead (owner, 2026-09-13). */
  align-self: center;
  display: inline-flex;
  margin-right: -0.35em;
}
.rt-chip svg { width: 28px; height: 28px; }
.rt-chip svg path { fill: none; stroke: var(--muted); stroke-width: 1.4; }
/* Real gold, a fixed sheen on every theme (owner overruled the
   seal-only gold rule for stars); the figure rides ON the star,
   large, in navy - the same fixed pair light or dark. */
.rt-chip.on svg path { fill: url(#rtgold); stroke: #b8860b; stroke-width: 0.9; }
.rt-chip.on svg text {
  fill: #1c3a5e; font-size: 11px; font-weight: 800;
  font-family: var(--sans);
}
.rt-mstar {
  font-size: 0.8rem; color: var(--navy); margin-left: 0.5rem;
  white-space: nowrap;
}
.rt-mstar b { color: #e0a90f; font-weight: 400; }
.rt-tabs { margin: 0.4rem 0 0.9rem; }
.rt-pane[hidden] { display: none; }
.rt-row { border-bottom: 1px solid var(--rule); padding: 0.5rem 0; }
.rt-row:last-child { border-bottom: 0; }
.rt-stars { color: #e0a90f; letter-spacing: 0.1em; }
.rt-day { float: right; font-size: 0.78rem; color: var(--muted); }
.rt-note { margin: 0.25rem 0 0; font-size: 0.9rem; }
.rt-pick { display: flex; gap: 0.4rem; justify-content: center; margin: 0.6rem 0; }
.rt-pick button {
  background: none; border: 0; font-size: 1.9rem; line-height: 1;
  color: var(--muted); cursor: pointer; padding: 0.2rem;
}
.rt-pick button.on { color: #e0a90f; }
#rtNote { width: 100%; margin-top: 0.4rem; }

/* ------------------------------------------------------------------
   The shopping list (shop.js): peer + merchant selectors, the add
   row, and tickable items.
   ------------------------------------------------------------------ */
/* The section heading inside What you use - the same quiet, spaced
   capitals the shopping list's groups wear. */
.use-lens {
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 1.2rem 0 0.2rem;
}
.use-lens:first-of-type { margin-top: 0.6rem; }

/* REMIND ME: the same card and row the list itself wears, with the
   count-and-age line under the name and the Add button on the right. */
/* Both filters on one line, each taking only the width of its own
   words, wrapping only when they truly cannot sit together. */
.rm-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 1.4rem;
  margin: 0.3rem 0 0.7rem;
}
.rm-filter { margin: 0; flex: 0 1 auto; }
.rm-item .sh-name { display: flex; flex-direction: column; gap: 0.1rem; }
.rm-when { font-size: 0.78em; color: var(--muted); }
.rm-add { flex: none; padding: 0.3rem 0.85rem; font-size: 0.85rem; }
/* A row the list already covers, shown only when asked for: tinted and
   lettered in the link blue so it is obvious at a glance which lines are
   here because you ticked for them (owner, 2026-09-15). */
.rm-item[data-out] { background: color-mix(in srgb, var(--blue) 7%, transparent); }
.rm-item[data-out] .sh-name { color: var(--blue); }
.rm-item[data-out] .rm-when { color: var(--blue); opacity: 0.8; }

/* THE CHOICES SHEET KEEPS ONE RHYTHM (owner, 2026-09-14: the spacing
   between titles and buttons was not even). Every title takes the same
   air above it and the same small gap below, so each question sits with
   its own answer instead of drifting toward the one before. The
   controls carry no margins of their own - the titles set the whole
   measure. */
.sh-sheet .mtext,
.sh-sheet .rv-label {
  display: block;
  margin: 18px 0 8px;
}
.sh-sheet .mhead + .mtext { margin-top: 0; }
.sh-sheet .rv-seg,
.sh-sheet .rt-pick,
.sh-sheet .rv-in,
.sh-sheet .sheet-list { margin: 0; }
/* Three answers need the full width; they share it evenly and wrap
   rather than overflow when the reader's text is large. */
.sh-sheet .rv-seg { display: flex; width: 100%; flex-wrap: wrap; }
.sh-sheet .rv-seg button { flex: 1 1 0; min-width: 5.5rem; }

/* Merchant and peer pickers are HOUSE CHIPS (owner, 2026-09-13: the
   raw dropdown was ugly): one scrollable pill row, the active pill
   filled, counts as little badges. */
.sh-chips {
  display: flex; gap: 0.5rem;
  overflow-x: auto;
  padding: 0.25rem 0.1rem 0.65rem;
  scrollbar-width: none;
}
.sh-chips::-webkit-scrollbar { display: none; }
.sh-chip {
  flex: none;
  display: inline-flex; align-items: center; gap: 0.45rem;
  border: 1px solid var(--rule); border-radius: 999px;
  padding: 0.42rem 0.95rem;
  background: var(--surface); color: var(--ink);
  font-family: var(--sans); font-size: 0.9rem;
  cursor: pointer; white-space: nowrap;
}
.sh-chip.on {
  background: var(--accent); color: var(--accent-ink);
  border-color: var(--accent);
}
.sh-chip .n {
  font-size: 0.72rem; line-height: 1.4;
  padding: 0 0.45rem; border-radius: 999px;
  background: var(--paper); color: var(--ink);
}
.sh-chip.on .n { background: var(--accent-ink); color: var(--accent); }
.sh-addrow { display: flex; gap: 0.6rem; margin: 0.35rem 0 0.9rem; }
/* The add row wears the SAME pill as the chips above it (owner,
   2026-09-14: the bare browser box read as a stranger, and its text
   ignored the reader's size preference). Its font-size is declared
   ABOVE the iOS floor block instead of here - see there. */
.sh-addrow input {
  flex: 1; min-width: 0;
  font-family: var(--sans);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.42rem 1rem;
}
.sh-addrow input:focus { border-color: var(--blue); outline: none; }
.sh-addrow input::placeholder { color: var(--muted); }
/* The button keeps the chips' build, not the base .btn's - the row
   reads as one family, no member outsized. */
.sh-addrow .btn { flex: none; padding: 0.42rem 1rem; }
.sh-card { padding: 0.15rem 0.95rem; }
.sh-empty { text-align: center; padding: 1.4rem 0; }
.sh-item {
  display: flex; align-items: center;
  /* A TIGHTER GAP AFTER THE CIRCLE (owner, 2026-09-17): the quantity
     stands between the tick and the words, and the row had plenty of
     room on the left to give it. The circle sits further out, the digit
     takes the space that opens up, and the words do not move. */
  gap: 0.45rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--rule);
  transition: opacity 0.28s ease;
}
/* A ticked item STAYS, struck and settled - readable enough to untick
   a mistake (owner, 2026-09-14: it used to vanish on first tap). */
.sh-item.done { opacity: 0.55; }
.sh-item.done .sh-name { text-decoration: line-through; }
.sh-item.done .sh-tick {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.sh-clearrow { text-align: center; padding: 0.45rem 0 0.1rem; }
/* A heading only appears when the list is in two minds - the things
   with no shop yet, and everything else. */
.sh-group {
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0.9rem 0 0.35rem;
}
.sh-group:first-child { margin-top: 0.2rem; }
/* The choice offered in place of a guess. */
.sh-pick {
  cursor: pointer;
  color: var(--blue);
  border-color: var(--blue);
  background: transparent;
  font-family: var(--sans);
}
.sh-pickrow { padding-top: 0.5rem; flex-wrap: wrap; }
/* WHAT THE TRIP IS LIKELY TO COST - the figure leads, and what is not
   counted says so beside it rather than hiding inside it. */
.sh-total {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.75rem 0.95rem 0.2rem;
  border-top: 1px solid var(--rule);
  margin-top: 0.35rem;
}
.sh-total-k { font-size: 0.92rem; color: var(--muted); white-space: nowrap; }
.sh-total-v { margin-inline-start: auto; font-size: 1.15rem; font-weight: 700; }
.sh-total-n { flex-basis: 100%; font-size: 0.8rem; color: var(--muted); }
.sh-item:last-child { border-bottom: 0; }
.sh-tick {
  width: 26px; height: 26px; flex: none;
  border: 1.5px solid var(--rule); border-radius: 50%;
  background: none; color: transparent;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; padding: 0;
}
.sh-tick:active { color: var(--accent-ink); background: var(--accent); }
.sh-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* In the combined ALL view each line names its own shop, quietly - the
   item is what you are reading, the shop is where it waits. */
.sh-where {
  display: inline-block;
  margin-inline-start: 0.5em;
  padding: 0.05em 0.5em;
  border-radius: 999px;
  background: var(--frost);
  border: 1px solid var(--rule);
  color: var(--muted);
  font-size: 0.78em;
  white-space: nowrap;
  vertical-align: 0.05em;
}
.sh-price { color: var(--muted); font-size: 0.85rem; flex: none; }
.sh-stars button { font-size: 1.4rem; }

/* THE FACE (owner, 2026-09-16). One shape wherever a person is drawn:
   a monogram until this device has their picture, the picture over it
   the moment it does. .set-avatar keeps its own size in Settings. */
.tab-face {
  flex: none;
  width: 46px;
  height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-sm);
  background: var(--hero-art);
  background-size: cover;
  background-position: center;
  color: #fff;
  font-family: var(--serif-display);
  font-size: 1.02rem;
  letter-spacing: 0.02em;
  overflow: hidden;
}
.tab-face.has-pic { color: transparent; }

/* Choosing and removing it, under the name in Settings. */
.set-facebtns { display: flex; gap: 14px; margin-top: 2px; }
.set-facebtns .mlink { padding: 0; }

/* A SHARED PERSON IS A ROW, NOT A BLOCK (owner, 2026-09-16): the size of
   a Recent receipt - face left, name beside it, the two doors small on
   the right. .need-card.share-card is display:block for the ask card, so
   this puts the row layout back for a person. */
.need-card.share-person {
  display: flex;
  align-items: center;
  /* NO FLEX GAP. A row gap sits between the face and the doors, so it
     landed on the LEFT of the auto margins and nowhere on the right -
     measured live as 34px against 25px. The auto margins must own both
     sides, so the only thing needing separation from the face asks for
     it directly (.sp-who below). */
  gap: 0;
  /* No right padding either, for the same reason: the gap the eye sees
     to the card edge is then the gap the rule produces. */
  padding: 10px 0 10px 12px;
}
/* WITH NO NAME PRINTED, THE FACE IS THE IDENTIFIER (owner,
   2026-09-17) - so it takes the size a Recent row gives its receipt
   photograph, not the squeezed 40px it wore while sharing the row with
   a name that should never have been there. */
.share-person > .tab-face { width: 56px; height: 56px; font-size: 1.16rem; }
.share-person .sp-who {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1 1 auto;
}
/* The name is gone from this row (owner, 2026-09-17: "just the
   avatar"), so .sp-who now holds only the state note when there is one,
   and takes no space at all when there is not. It must not stay a flex
   child with width, or it would hold the doors off the right edge. */
.share-person .sp-who:empty { display: none; }
/* THE NOTE GETS ITS OWN LINE (measured, 2026-09-17). Sitting between the
   face and the doors it ate the free space the auto margins divide, and
   the row went from 29px/30px to 174px/1px - the door jammed against the
   card edge and squeezed narrower than its pair. The face-and-doors line
   is the shape that was asked for, so nothing else may stand in it. */
.share-person { flex-wrap: wrap; }
.share-person .sp-who {
  order: 3;
  flex: 0 0 100%;
  margin: 2px 12px 0 12px;
}
/* EQUAL WIDTHS, EQUAL GAPS (owner, 2026-09-17). Two things were asked
   for and each needs its own trick:

   WIDTH. "Receipts" is a short word and "Shopping list" a long one, so
   as plain flex children they were always going to be a mismatched
   pair. A grid of `1fr` columns sized to `max-content` makes every
   column as wide as the widest one - so both doors match whatever the
   longest label happens to be, in any of the nineteen languages, with
   no hardcoded width to go stale.

   GAP. An auto margin on BOTH sides splits the free space evenly, which
   puts the same distance between the avatar and the doors as between
   the doors and the card's edge. The card gives up its right padding to
   make that true of the VISIBLE edge - otherwise the right-hand gap
   would always be one padding wider than the left. */
.share-person .sp-doors {
  display: inline-grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  width: max-content;
  margin-top: 0;
  gap: 6px;
  margin-inline: auto;
}
.share-person .sp-doors .btn { width: 100%; min-width: 0; }
.share-person .sp-doors .btn { flex: none; }

/* The ask keeps its block card - two full doors, because answering is
   the whole point of it - but the face rides beside the sentence. */
.ask-who { display: flex; align-items: center; gap: 12px; }
.ask-who > .need-title { display: block; }

/* Two doors on one row will always be wide, and the NAME is what the eye
   wants first - it was ellipsing at seven characters. The doors give back
   what they can afford: tighter padding, a smaller face, no wrapping. */
.share-person .sp-doors .btn {
  padding: 8px 12px;
  font-size: 0.84rem;
  white-space: nowrap;
}

/* A LONG PRESS, NOT A TEXT SELECTION (owner, 2026-09-16). Holding words
   on a phone normally starts selecting them and raises the system's own
   copy menu, which would fight the edit sheet for the same gesture. The
   shop picker inside keeps its own behaviour - it is a button. */
.sh-name {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
.sh-name .sh-pick { -webkit-user-select: auto; user-select: auto; }

/* WHY A RECEIPT IS WAITING (owner, 2026-09-17: "I don't see anything
   wrong nor is it telling me that something is wrong"). It sits above
   the fields, quiet rather than alarming - the receipt is not broken,
   it just needs an eye. The red sum box below stays the loud one,
   because it is the only line naming actual figures. */
.rv-why {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0 0 14px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  background: var(--surface);
}
.rv-why > b { font-family: var(--sans); font-size: 0.95rem; }

/* THE SHOP'S STAR MUST SURVIVE A LONG NAME (owner, 2026-09-17, who runs
   a bigger text size). The star is appended INSIDE the title, and the
   title ellipses - so the star was the thing being cut. The name is
   wrapped at decorate time; the box becomes a row where the NAME gives
   way and the star never does. */
.tbar-title.has-mstar,
.mhead.has-mstar {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}
.rt-mname {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.has-mstar > .rt-mstar { flex: none; }

/* CHARGE OR CREDIT (owner, 2026-09-17). The phone's decimal keypad has
   no minus key, so a line the reading put on the wrong side could be
   seen and not corrected. The button says what the number would BECOME.

   It shows only while that row is being edited: twenty-five of these
   standing down a Costco receipt would be noise, and every one but the
   row in hand is irrelevant. review.js keeps focus on the field through
   the press, or :focus-within would pull the button out from under the
   finger mid-tap. */
.rv-sign {
  display: none;
  flex: none;
  padding: 4px 8px;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 0.72rem;
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
}
.rv-item:focus-within .rv-sign { display: inline-block; }
.rv-sign:active { transform: scale(0.96); }
/* A credit reads as one at a glance, focused or not. */
.rv-item-amt.is-credit { color: var(--danger); }

/* THE PICKER: A LIST OF CHOICES, NOT A PRICE TABLE (owner, 2026-09-17
   for the shape, 2026-09-18 for the clarity). The first list's way in,
   before a single receipt has been scanned - so the affordance has to
   carry itself to somebody who has never seen this app decide
   anything. Bare rows with prices on the right read as something to
   LOOK AT; a bordered card with a chevron reads as something to tap. */
/* ONE SCROLLER, NOT TWO. .sheet-list caps itself at 52vh and scrolls,
   which is right for a long settings list but wrong here: the sheet
   ALREADY scrolls, so at a large text setting the offers ended up in a
   small scrolling window inside a scrolling popup - two nested touch
   targets, and neither obviously the one to drag. The sheet is the
   scroller; the list just stacks. */
.sp-list { margin-top: 6px; max-height: none; overflow: visible; }
.sp-group + .sp-group { margin-top: 1rem; }
/* The shop is a heading, not a choice: quiet, small, and it carries a
   rule out to the edge so the goods beneath plainly belong to it. It
   asked for var(--dim) once, which this palette has never defined -
   the declaration was dropped and the heading rendered in full body
   ink, as loud as the goods under it. */
.sp-shop {
  display: flex;
  align-items: center;
  gap: 0.5em;
  padding: 0 2px 0.15em;
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  /* No text-transform: the shop headings shouted for the same reason
     the goods did, and the owner's note was about the whole sheet. It
     is still plainly a heading - smaller, bolder, muted, letterspaced,
     and carrying a rule out to its group. */
  color: var(--muted);
}
.sp-shop::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--rule);
}
/* Spacing in EM, not px: the text setting scales the rem this card is
   sized in, so a padding in px would stay put while the words grew and
   turn a frame into a pinch. */
.sp-offer {
  display: flex;
  align-items: center;
  gap: 0.65em;
  width: 100%;
  margin-top: 0.4em;
  padding: 0.7em 0.7em 0.7em 0.85em;
  text-align: start;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 12px;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--ink);
  cursor: pointer;
}
.sp-offer:active {
  background: color-mix(in srgb, var(--rule) 25%, var(--surface));
}
/* The name takes what is left and ellipsises; the price and the
   chevron keep their line, or a long product name pushes the figure
   off the card (the same defect the circle rows had). */
.sp-good {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}
/* TWO MONEY COLUMNS (owner, 2026-09-18): the RATE on the left, what you
   actually pay on the right where the money has always been. Both are
   fixed-width so the figures column up the sheet and the eye can run
   down either one; the name takes what is left.

   The rate is quieter than the till price on purpose - it is the
   comparison, not the cost. */
.sp-per {
  flex: none;
  min-width: 5.5em;
  text-align: end;
  color: var(--muted);
  font-size: 0.9em;
}
.sp-price { flex: none; min-width: 4.5em; text-align: end; font-weight: 700; }
/* Not an error and not a missing value: the shop sells it by weight, so
   there is no fixed price to show. Said in words, small. */
.sp-na { font-weight: 600; font-size: 0.82em; color: var(--muted); }

/* THE PASTE DOOR (owner, 2026-09-18). A whole order page lands here, so
   the box is tall enough to show that something arrived and scrolls for
   the rest - and it is monospaced, because what is pasted is a machine s
   text and reading it back as prose invites tidying it by hand. */
/* The two roads, each with its cost written under it - the reason to
   ask here at all is that the person understands before the browser
   does the asking. */
.pm-why {
  display: block;
  margin-top: 0.25em;
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--muted);
  white-space: normal;
}
#pmAuto .shc-p, #pmManual .shc-p { min-width: 0; }
#pmAuto .shc-p > b, #pmManual .shc-p > b { white-space: normal; overflow: visible; }

/* When the browser refused the clipboard, the sheet says so above the
   box - the press is for looking, and this is the thing to look at. */
.pst-warn { color: var(--danger); font-weight: 600; }

.pst-box {
  width: 100%;
  min-height: 8.5em;
  max-height: 40vh;
  resize: vertical;
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
  font-size: 0.8rem;
  line-height: 1.45;
  white-space: pre;
  overflow: auto;
}
.sp-go { flex: none; display: inline-flex; color: var(--muted); }
/* The chevron follows the label. UI.icon writes width and height as
   attributes, which these declarations override - left alone, a 16px
   mark shrinks away beside text at a large setting. */
.sp-go svg { width: 1em; height: 1em; }
/* ONE ANSWER SITS IN THE MIDDLE (owner, 2026-09-18). The house .mbtns
   right-aligns because a pair reads as primary-last; a lone button
   pushed to one edge reads as an afterthought instead of the answer it
   is. Narrow screens already stretch it full width, so this is the wide
   case only. */
.sp-btns { justify-content: center; }


/* HOW MANY - one digit, its own tap target, between the circle and the
   name. Quiet by default: a list of ones should read as a list of
   things, not a column of numbers, so 1 is dimmed and anything more
   speaks up. */
.sh-qty {
  flex: none;
  min-width: 1.6rem;
  padding: 2px 0;
  border: 0;
  background: none;
  font-family: var(--sans);
  font-size: 0.98rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  opacity: 0.45;
  text-align: center;
  cursor: pointer;
}
.sh-item .sh-qty:not(.one) { opacity: 1; font-weight: 600; }
.sh-qty:active { color: var(--accent); }
.sh-item.done .sh-qty { text-decoration: line-through; }
/* Copy the item's words: a small mark sitting right after them, in the
   line of text - the row's end belongs to the price. */
.sh-copy {
  width: 26px; height: 26px;
  margin: -4px 0 -4px 0.1em;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  display: inline-flex; align-items: center; justify-content: center;
  vertical-align: middle;
  cursor: pointer;
}
.sh-copy:active { color: var(--accent); }
/* The 1-9 row in the sheet: the same chips the shop picker uses. */
.sh-qtyrow { justify-content: flex-start; }
.sh-qtyrow .sh-chip { min-width: 2.6rem; text-align: center; }

/* ALL DONE (owner, 2026-09-17) - the trip is over. Centred under the
   money, with air above it so it is not mistaken for part of the total,
   and narrow enough that it does not read as the page's primary action
   while there is still shopping to do. */
.sh-donerow {
  display: flex;
  justify-content: center;
  padding: 0.9rem 0 0.2rem;
}
.sh-donerow .btn { min-width: 11rem; }
