
  /* hsl first as a fallback, oklch second — an engine without oklch drops the
     later declarations and still gets a readable (if less even) chip. */
  .cochip{
    background:hsl(var(--co-h,214) 82% 94%); color:hsl(var(--co-h,214) 58% 34%);
    background:oklch(var(--co-l-bg) var(--co-c-bg) var(--co-h,214));
    color:oklch(var(--co-l) var(--co-c) var(--co-h,214));
  }
  *{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
  html,body{height:100%}
  body{margin:0;font-family:var(--font-sans);
    background:#e9ebee}
  /* The app shell: full viewport on phones; a centered column on desktop. */
  .screen{width:100%;height:100dvh;background:var(--bg);overflow:hidden;position:relative;display:flex;flex-direction:column;margin:0 auto}
  @media (min-width:600px){
    .screen{max-width:480px;border-left:1px solid var(--line);border-right:1px solid var(--line);
      box-shadow:0 0 40px rgba(20,25,40,.08)}
  }
  /* ---- THE SIGN-IN, ON A COMPUTER (Drew, 2026-09-01) ------------------------
   *
   * *"make it have a more native looking login for the computer. its silly to
   * send them to a phone looking screen for this."*
   *
   * And it is the ONE screen where the phone column cannot be argued for. The
   * 480px shell is right for the app -- every screen behind it was drawn for a
   * thumb, and `resolveSurface` sends a real computer to the planner rather
   * than stretching them. But a rep on a laptop cannot REACH the planner
   * without signing in first, and the login form lives only here (see `boot`),
   * so this is the single phone-shaped screen a desktop rep is required to
   * pass through. A bordered 480px strip with a phone nav bar on top of it, on
   * a 27" monitor, reads as the wrong app rather than as a considered one.
   *
   * A CLASS ON <body>, SET BY `render` FOR THE LOGIN VIEW ONLY, and cleared by
   * the next navigation -- so the moment a session exists this stops applying
   * and the app is the app again. It is not a second layout to maintain: the
   * card, the field, the button and the type scale are all the shipped ones.
   * What changes is the FRAME -- no phone chrome, the panel centred in the
   * viewport instead of pinned under a nav bar, and a comfortable reading
   * width for a cursor rather than a thumb.
   *
   * GATED ON `_looksLikeAComputer`, the same predicate the boot redirect and
   * the desktop chip already share, so a wide phone or a tablet in landscape
   * keeps the app's own sign-in. Three spellings of "is this a computer" would
   * eventually disagree; there is still only one.
   */
  body.deskauth{background:var(--bg)}
  body.deskauth .screen{max-width:none;border:0;box-shadow:none;overflow:auto}
  /* The phone chrome goes: a back arrow that cannot go back, a title that
     repeats the words on the panel, and an overflow circle `render` already
     hides on this view. The tab bar is hidden on login already. */
  body.deskauth .nav{display:none}
  body.deskauth .body{padding:40px 24px;display:flex;align-items:center;
    justify-content:center}
  body.deskauth #v-login{width:100%;max-width:420px}
  /* Roomier than the phone's 42px, because there is room. */
  body.deskauth #v-login .dl-mark{margin:0 0 26px}
  body.deskauth #v-login .card{padding:26px;border-radius:var(--r-card);
    box-shadow:0 1px 3px rgba(20,25,40,.06),0 12px 32px rgba(20,25,40,.07)}
  body.deskauth #v-login input.txt{height:46px;font-size:var(--t-m)}
  /* A cursor gets a focus ring; a thumb never needed one. */
  body.deskauth #v-login input.txt:focus{outline:2px solid var(--accent);
    outline-offset:-2px;border-color:var(--accent)}
  body.deskauth #v-login .btn{height:46px}
  /* The one line of copy this screen owes a desktop rep: what is on the other
     side of it. Hidden on the phone, where the app IS the other side. */
  .dl-note{display:none}
  /* The sign-in screen's one new line (mock 1b): a quiet dashed note in the
     good-soft pairing, never a second card. */
  .login-hint{margin-top:12px;padding:10px 12px;border-radius:var(--r-control);background:var(--ok-bg);
    border:1px dashed var(--ok-line);font-size:var(--t-xs);color:var(--ink3);text-align:center;line-height:1.45}
  body.deskauth .dl-note{display:block;text-align:center;margin-top:18px;
    font-size:var(--t-xs);color:var(--ink3);line-height:1.6}
  /* Safe areas (standalone PWA w/ viewport-fit=cover): the white nav extends
     under the iOS status bar; the tab bar clears the home indicator. */
  /* THE NAV IS THE SCREEN'S HEADER, AND SAYS ITS NAME ONCE (rep app refresh,
     2026-09-28). It used to be a 52px bar with the title at 17px beside the
     controls -- and on every screen that grew a header block of its own
     (Follow-ups' old #rem-hero, the account's #acc-hero) the same name was then
     said twice, once small and once at the size that ranks it. Now the nav
     IS that header block: Back and the corner controls in a 40px row, the
     title beneath at --t-xl, the subtitle at --t-s. On a root screen there is
     no Back, so the title takes the top row beside the controls rather than
     leaving a 40px row empty above it (`.nav.root`, set by render).
     Grid areas rather than a second wrapper so the markup keeps its ids and
     the order app.js already writes into. */
  .nav{display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"back ctl" "head head";
    align-items:center;column-gap:8px;padding:calc(10px + env(safe-area-inset-top, 0px)) 16px 12px;
    background:var(--card);border-bottom:1px solid var(--line);flex:0 0 auto}
  .nav.root{grid-template-areas:"head ctl";align-items:start;
    padding-top:calc(14px + env(safe-area-inset-top, 0px))}
  .nav .nav-head{grid-area:head;min-width:0;margin-top:6px}
  .nav.root .nav-head{margin-top:0}
  .nav .title{font-size:var(--t-xl);line-height:var(--lh-xl);font-weight:600;letter-spacing:-.02em;color:var(--ink)}
  .nav .sub{font-size:var(--t-s);color:var(--ink3);margin-top:3px}
  .nav .sub:empty{display:none}
  .nav .nav-ctl{grid-area:ctl;display:flex;align-items:center;gap:8px;justify-self:end}
  .nav .back{grid-area:back;justify-self:start;height:40px;padding:0 6px;margin-left:-6px;
    font-size:var(--t-m);color:var(--accent);cursor:pointer;display:flex;align-items:center;gap:2px}
  .nav .back .ti{font-size:22px}
  /* THE CONTEXTUAL "?" — 38px, and TINTED rather than FILLED (2026-08-27).
   *
   * The reasoning for filling it was legibility on white, and that reasoning
   * is sound: a bare grey icon on a white nav is hard to see. The OUTCOME was
   * that help became the highest-contrast element in the nav, on every screen
   * -- louder than the screen's own title, which is the thing that tells you
   * where you are. A tinted chip keeps the 38px target and keeps the icon
   * clearly an icon; it just stops shouting.
   *
   * `--accent-bg` behind `--accent` is the same pairing every pill on this
   * page uses, and it clears 4.5:1 (6.6:1 in light, 5.3:1 in dark) -- the
   * legibility argument is met by contrast rather than by loudness.
   *
   * AND NOW NEUTRAL, NOT TINTED (14a, 2026-08-28). The tint solved the
   * contrast argument and left the RANKING one standing: `--accent` is the
   * app's "this is the thing to press", and help is never the thing to press.
   * On the account screen it was competing with the one accent-filled button
   * in the dock. `--chip-bg` behind `--ink2` is the same neutral chip the
   * hero's Go/Email/Call wear -- still a chip, still a 38px target, still
   * legible on white, no longer a candidate for the eye. */
  /* The rule below moved from #helpBtn to #acctMore when the four nav icons
     were retired (2026-08-31). The ARGUMENT is unchanged and is the reason it
     is a rule rather than an inline style: whatever single control sits in
     that corner is a neutral chip -- a real 38px+ target, legible on white,
     and never --accent, because accent is the app's "this is the thing to
     press" and the overflow door never is. */
  /* A PILL, NOT A CIRCLE, BECAUSE IT NOW SAYS ITS NAME (2026-09-02). The
     neutral-chip argument above is untouched -- what changed is that a bare
     `ti-user` glyph in a corner does not say "settings, help, your orders and
     recent fixes live here", and the four named buttons it replaced did. The
     word is the affordance; the circle was only ever the shape left over when
     there was nothing to write in it. */
  /* A CONTROL, SO A CONTROL'S CORNER (2026-09-28): 12px, not the 20px pill it
     was -- a pill is a status in this app now, and Menu is a door. `.navchip`
     is the same chip for the account screen's Details door, which moved up
     here from beside the venue name when the nav became the header. */
  #acctMore{height:40px;padding:0 13px;gap:6px;flex:0 0 auto;justify-content:center;
    border-radius:var(--r-control);background:var(--chip-bg);color:var(--ink2);
    font-size:var(--t-s);font-weight:600}
  .navchip{height:40px;padding:0 13px;gap:6px;flex:0 0 auto;justify-content:center;
    border-radius:var(--r-control);background:var(--chip-bg);color:var(--ink2);
    font-size:var(--t-s);font-weight:600;
    display:flex;align-items:center;border:none;cursor:pointer;font-family:inherit}
  #acctMore .ti,.navchip .ti{font-size:18px}
  #acctMore .navword,.navchip .navword{white-space:nowrap}
  /* A FOCUS RING ON EVERYTHING KEYBOARD-REACHABLE. `:focus-visible`, so it
     appears for a keyboard and never for a thumb -- the reason a ring was
     never added is that `:focus` alone put one under every tap. */
  [role="button"]:focus-visible,a:focus-visible,button:focus-visible,
  input:focus-visible,select:focus-visible,textarea:focus-visible{
    outline:2px solid var(--accent);outline-offset:2px;border-radius:8px}
  .body{flex:1 1 auto;overflow-y:auto;padding:14px 14px 90px}
  .view{display:none}
  .view.active{display:block}
  .card{background:var(--card);border:1px solid var(--line);border-radius:var(--r-card);padding:var(--card-pad);margin-bottom:12px}
  /* Skeletons: shimmer placeholders shown on a COLD load (no cached copy yet).
     A warm load paints real data straight from the SWR cache and skips these. */
  .skel{position:relative;overflow:hidden;background:var(--chip-bg);border-radius:7px}
  .skel::after{content:"";position:absolute;inset:0;transform:translateX(-100%);
    background:linear-gradient(90deg,transparent,rgba(255,255,255,.65),transparent);
    animation:skel-sweep 1.25s ease-in-out infinite}
  @keyframes skel-sweep{100%{transform:translateX(100%)}}
  /* The planner's spinner. Honest waiting: the optimiser really is doing
     work (travel matrix + 2-opt on a worker thread), so it gets a real
     indicator rather than a frozen screen. */
  @keyframes spin{100%{transform:rotate(360deg)}}
  @media (prefers-reduced-motion:reduce){
    .ti-loader-2{animation:none !important}
  }
  .skel-line{height:11px;margin:7px 0}
  @media (prefers-reduced-motion:reduce){.skel::after{animation:none}}
  /* An optimistic row awaiting server confirmation (reminder just set, etc.). */
  .pending{opacity:.6}
  .search{display:flex;align-items:center;gap:8px;background:var(--card);border:1px solid var(--line);border-radius:var(--r-control);padding:10px 12px;margin-bottom:12px;color:var(--ink3);font-size:var(--t-s)}
  .stop{display:flex;gap:12px;align-items:center;cursor:pointer}
  .stop .dot{width:10px;height:10px;border-radius:50%;flex:0 0 auto}
  .stop .mid{flex:1 1 auto;min-width:0}
  .stop .name{font-size:var(--t-m);font-weight:600;color:var(--ink)}
  .stop .meta{font-size:var(--t-xs);color:var(--ink2);margin-top:2px;display:flex;gap:8px;flex-wrap:wrap;align-items:center}
  /* STATUS PILLS (rep app refresh, rule 5, 2026-09-28): sentence case, SANS,
     12px/600, 2px 8px, a pill's own radius, and a leading 6px dot in the
     pill's colour. tokens.css sets `.pill` in mono for every surface; the
     phone opts out here (this sheet loads after it), because a pill says a
     STATE in words -- "Overdue", "Paid" -- and mono is for figures that
     change. The dot is drawn only where the pill carries no glyph of its own,
     is not a brand chip (identity, not state), and is not a CONTROL wearing
     the pill's shape -- a filter you tap is not a status you read. */
  .pill{display:inline-flex;align-items:center;gap:4px;font-size:var(--t-xs);font-weight:600;padding:2px 8px;border-radius:var(--r-pill);
    font-family:var(--font-sans);font-variant-numeric:normal}
  .pill:not(.cochip):not(:has(.ti)):not(button):not([data-act])::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:0 0 auto;margin-right:1px}
  .pill.hot{background:var(--hot-bg);color:var(--hot)} .pill.warm{background:var(--warm-bg);color:var(--warm)}
  .pill.cool{background:var(--cool-bg);color:var(--cool)} .pill.done{background:var(--ok-bg);color:var(--ok)}
  .pill.due{background:var(--hot-bg);color:var(--hot)}
  .sechead{font-size:var(--t-xs);font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--ink3);margin:6px 4px 8px}
  .row{display:flex;align-items:center;justify-content:space-between;padding:var(--row-pad);border-bottom:1px solid var(--line)}
  .row:last-child{border-bottom:none}
  .row .lab{font-size:var(--t-s);color:var(--ink2);display:flex;align-items:center;gap:9px}
  .row .val{font-size:var(--t-s);color:var(--ink);font-weight:500}
  .row .val.link{color:var(--accent)}
  .btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;height:48px;border-radius:var(--r-control);font-size:var(--t-m);font-weight:600;border:none;cursor:pointer}
  .btn.primary{background:var(--accent);color:var(--on-fill)}
  .btn.ghost{background:var(--accent-bg);color:var(--accent)}
  /* Filled, not bordered (refresh rule 3, 2026-09-28). */
  .btn.sub{background:var(--chip-bg);color:var(--ink);border:none}
  .btn.row2{height:44px;font-size:var(--t-s)}
  .grid2{display:grid;grid-template-columns:1fr 1fr;gap:10px}
  .hero{display:flex;justify-content:space-between;align-items:flex-start}
  .hero .h1{font-size:var(--t-l);font-weight:600;color:var(--ink);line-height:1.2}
  /* `.addr` OUTSIDE A HERO (order details handoff, 2026-09-28). It was only
     ever styled as `.hero .addr`, so its ~75 other uses -- every meta line on
     the order lists, the editor's explainers, "Loading…" -- rendered at 16px
     body ink, the same weight as the lines they annotate. Same values as the
     hero's; an inline size or colour on a use still wins. */
  .addr{font-size:var(--t-s);color:var(--ink2)}
  .hero .addr{font-size:var(--t-s);color:var(--ink2);margin-top:3px}
  .stat{background:var(--card);border:1px solid var(--line);border-radius:var(--r-card);padding:12px;text-align:center}
  .stat .n{font-size:var(--t-l);font-weight:600;color:var(--ink)} .stat .l{font-size:var(--t-xs);color:var(--ink3);margin-top:2px}
  .interaction{display:flex;gap:10px;padding:10px 0;border-bottom:1px solid var(--line)}
  .interaction:last-child{border-bottom:none}
  .ico{width:32px;height:32px;border-radius:var(--r-control);display:flex;align-items:center;justify-content:center;font-size:var(--t-m);flex:0 0 auto}
  .ico.b{background:var(--accent-bg);color:var(--accent)} .ico.g{background:var(--ok-bg);color:var(--ok)} .ico.o{background:var(--warm-bg);color:var(--warm)}
  .interaction .t{font-size:var(--t-s);color:var(--ink);font-weight:500} .interaction .s{font-size:var(--t-xs);color:var(--ink3);margin-top:1px}
  /* Your OWN entries. A rail, not a colour: hue already means COMPANY, and a
     second colour system in the same row would weaken both. The rail is
     self-relative — it marks a subset of one company's rows — so the two
     signals answer different questions and never compete. Padding absorbs
     the border so rows stay aligned whether or not they're yours. */
  /* THE OWN-ROW RAIL (Drew, Jul 2026), and the account timeline keeps it
     through 14a's loss of the card. Deliberately NOT a colour: hue already
     means COMPANY across both apps, and a second colour system in the same row
     would weaken both. A rail is self-relative -- it marks a subset of one
     company's rows -- so the two signals answer different questions and never
     compete for the same channel. On the Overview it hangs into the pane's own
     16px gutter, which is where a card's padding used to be. */
  .interaction.mine,.actrow.mine{border-left:3px solid var(--accent);padding-left:9px;margin-left:-12px}
  .interaction .s .you{color:var(--accent);font-weight:600}
  .seg{display:flex;background:var(--tabs-bg);border-radius:var(--r-control);padding:4px;gap:4px;margin-bottom:14px}
  .seg button{flex:1;height:38px;border:none;background:transparent;border-radius:calc(var(--r-control) - 4px);font-size:var(--t-s);font-weight:600;color:var(--ink2);cursor:pointer}
  .seg button.on{background:var(--card);color:var(--ink);box-shadow:0 1px 3px rgba(0,0,0,.08)}
  .gpschip{display:flex;align-items:center;gap:8px;background:var(--ok-bg);color:var(--ok);font-size:var(--t-s);font-weight:600;padding:10px 12px;border-radius:var(--r-control);margin-bottom:12px}
  .chips{display:flex;gap:8px;margin:4px 0 14px}
  /* Booking a tasting (section 20). Deliberately few new classes -- .txt,
     .chip and .card already carry most of this screen. */
  .fldlab{display:block;font-size:var(--t-xs);font-weight:600;color:var(--ink2);margin:12px 0 5px}
  .tickrow{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid var(--line);font-size:var(--t-s);cursor:pointer}
  .tickrow:last-of-type{border-bottom:none}
  .tickbox{width:20px;height:20px;border-radius:6px;border:1.5px solid #c9cfd6;flex:none;position:relative}
  .tickbox.on{background:var(--accent);border-color:var(--accent)}
  .tickbox.on::after{content:"";position:absolute;left:6.5px;top:2.5px;width:5px;height:10px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg)}
  .winbox{border:1px solid var(--line);border-radius:var(--r-control);padding:10px;margin-bottom:8px}
  /* Booking a tasting from a calendar (section 20). Thumb-sized cells: a
     rep taps these standing in a doorway, so 40px is the floor. */
  .bsub{font-size:var(--t-xs);color:var(--ink2);line-height:1.45}
  .bhead{font-size:var(--t-xs);font-weight:600;letter-spacing:.06em;color:var(--ink2);margin-bottom:8px}
  .bmon{display:flex;align-items:center;justify-content:space-between;margin:12px 0 6px;font-size:var(--t-m)}
  /* 34 -> 44. The month arrows are the one control on the booking calendar
     that is NOT a day cell, and they were the smallest thing on a screen whose
     cells were deliberately made thumb-sized. */
  .bnav{width:44px;height:44px;margin-left:6px;border-radius:var(--r-control);border:none;background:var(--chip-bg);color:var(--ink);font-size:var(--t-m);line-height:1;cursor:pointer}
  .bnav.dim{opacity:.3;cursor:default}
  .bdow{display:grid;grid-template-columns:repeat(7,1fr);gap:4px;font-size:var(--t-xs);color:var(--ink3);text-align:center;margin-bottom:3px}
  .bgrid{display:grid;grid-template-columns:repeat(7,1fr);gap:4px}
  .bd{height:40px;display:flex;align-items:center;justify-content:center;border-radius:var(--r-control);border:none;background:var(--chip-bg);font-size:var(--t-s);cursor:pointer;position:relative}
  .bd.off{background:transparent;color:var(--ink3);opacity:.45;cursor:default}
  .bd.has::after{content:"";position:absolute;bottom:5px;width:4px;height:4px;border-radius:50%;background:var(--mute-line)}
  .bdot{display:inline-block;width:4px;height:4px;border-radius:50%;background:var(--mute-line);vertical-align:middle;margin-right:5px}
  .bset{display:flex;align-items:center;gap:9px;padding:9px 10px;border:none;background:var(--chip-bg);border-radius:var(--r-control);margin-bottom:6px;font-size:var(--t-s);cursor:pointer}
  .bset.on{background:var(--accent-bg)}
  .bsw{width:11px;height:11px;border-radius:50%;flex:none}
  .blink{border:none;background:none;color:var(--ink2);font-size:var(--t-xs);padding:4px;cursor:pointer;text-decoration:underline}
  .btwo{display:grid;grid-template-columns:1fr 1fr;gap:8px}
  .hintbox{background:var(--warm-bg);color:#7a5510;border-radius:var(--r-control);padding:10px 12px;font-size:var(--t-xs);margin:8px 0 12px}
  /* SECONDARY CONTROLS ARE FILLED, NEVER BORDERED (rep app refresh, rule 3,
     2026-09-28). A chip was a 1.5px outline on whatever it sat on, and an
     outline is the idiom this app uses for a FIELD -- so a row of chips and a
     row of inputs looked like the same kind of thing. A chip is a --chip-bg
     block now; ON is its own tint and ink, and the border that used to carry
     the lit state is gone with the rest of the outline. The variants are
     unchanged in meaning (chip_selected_state.test.mjs): cold takes the
     --cold trio tokens.css built for exactly this, instead of two raw hexes. */
  .chip{flex:1;text-align:center;padding:10px 0;border-radius:var(--r-control);font-size:var(--t-s);font-weight:600;border:none;background:var(--chip-bg);color:var(--ink2);cursor:pointer}
  .chip.hot.on{background:var(--hot-bg);color:var(--hot)}
  .chip.warm.on{background:var(--warm-bg);color:var(--warm)}
  .chip.cool.on{background:var(--cool-bg);color:var(--cool)}
  .chip.cold.on{background:var(--cold-bg);color:var(--cold)}
  .chip.neutral.on{background:var(--line2);color:var(--ink)}
  .chip.pick.on{background:var(--accent-bg);color:var(--accent)}
  label.fld{display:block;font-size:var(--t-xs);font-weight:600;color:var(--ink2);margin:0 2px 6px}
  textarea,input.txt{width:100%;border:1px solid var(--line);border-radius:var(--r-control);padding:11px;font-size:var(--t-s);font-family:inherit;color:var(--ink);background:var(--card);resize:none}
  /* Selects were falling through to the tiny native control (the rule above
     only covered textarea/input). Style them like every other field, with a
     drawn chevron (appearance:none removes the native one). background-color,
     not the background shorthand — the dark-theme rule must not erase the
     chevron image. */
  select.txt{width:100%;border:1px solid var(--line);border-radius:var(--r-control);padding:11px 36px 11px 11px;font-size:var(--t-s);font-family:inherit;color:var(--ink);
    background-color:var(--card);background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="%238a9099" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>');
    background-repeat:no-repeat;background-position:right 12px center;background-size:17px;-webkit-appearance:none;appearance:none}
  /* The check-in's purpose picker: a REQUIRED field that was rendering smaller
     than the optional warmth chips below it. Scale it to the same visual weight
     (chip-height tap target, same 600 text) so the required thing reads as the
     bigger thing. */
  #log-purpose{padding-top:13px;padding-bottom:13px;font-size:var(--t-m);font-weight:600}
  /* THE AREA SEARCH BOX HAS NO NATIVE CLEAR BUTTON (2026-09-14). Drew, of
     Plan my day: *"typing a name for the location is painful. it keeps
     deleting."* A `type=search` field draws a grey cross inside its own right
     edge on WebKit and Blink, and this one is full width on a phone -- so one
     mis-aimed thumb at the end of a word wipes the query, with no undo and
     nothing on screen to say what happened. It stays a search input (the role
     is right, and the keyboard hint is useful); it just stops offering a
     destructive control the rep never asked for. */
  .plan-q::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}
  /* PICK MY STOPS (2026-09-26). The footer that routes the ticked list stays
     in view however long her book is: sticky to the sheet's bottom edge, full
     bleed across the sheet's 18px gutter, opaque so rows scroll under it, and
     pulled into the card's own bottom padding so the end of the list does not
     carry two gaps. THE NEGATIVE `bottom` IS THAT SAME PADDING: sticky
     measures from inside it, so at 0 the footer floated 20px up and rows
     showed through underneath (seen in the browser, 2026-09-26). */
  .pick-foot{position:sticky;bottom:calc(-20px - env(safe-area-inset-bottom, 0px));z-index:1;
    margin:12px -18px calc(-20px - env(safe-area-inset-bottom, 0px));
    padding:10px 18px calc(14px + env(safe-area-inset-bottom, 0px));background:var(--card);
    border-top:1px solid var(--line);line-height:1.45}
  /* The picked route's map: a fixed height, the ground colour where no tile
     has drawn, and numbered pins in the route's own accent. Done stops grey
     out, as their rows strike through. */
  /* ITS OWN STACKING CONTEXT, or the map draws over the sheet: Leaflet gives
     its panes z-indexes of 400 and up, which otherwise compete with the page's
     overlays (the plan sheet is 120) -- found in the browser, 2026-09-26, with
     the pins painted across Edit stops' list. */
  .route-map{height:210px;border-radius:var(--r-card);overflow:hidden;margin:4px 0 2px;
    background:var(--sub);border:1px solid var(--line);position:relative;z-index:0;
    isolation:isolate}
  .rmpin-wrap{background:none;border:0}
  .rmpin{width:24px;height:24px;border-radius:50%;background:var(--accent);color:var(--on-fill);
    font-family:var(--font-mono);font-size:var(--t-xs);font-weight:600;line-height:21px;
    text-align:center;border:1.5px solid var(--card);box-shadow:0 1px 3px rgba(0,0,0,.25)}
  .rmpin.done{background:var(--chip-bg);color:var(--ink3)}
  textarea:focus,input.txt:focus{outline:none;border-color:var(--accent)}
  .li{display:flex;align-items:center;gap:10px;padding:11px 0;border-bottom:1px solid var(--line)}
  .li:last-child{border-bottom:none}
  .li .qty{width:34px;height:34px;border-radius:var(--r-control);background:var(--tabs-bg);display:flex;align-items:center;justify-content:center;font-weight:600;font-size:var(--t-s);color:var(--ink)}
  .li .nm{flex:1;font-size:var(--t-s);color:var(--ink);font-weight:500} .li .pr{font-size:var(--t-s);color:var(--ink2)}
  .tag{font-size:var(--t-xs);font-weight:600;padding:2px 7px;border-radius:var(--r-pill);margin-left:6px;vertical-align:1px}
  .tag.beer{background:var(--warm-bg);color:var(--warm)} .tag.wine{background:#fbeaf0;color:#993556} .tag.spirits{background:#efe9f6;color:#534ab7}
  /* FIVE TABS AT THE 12px FLOOR. The type scale raised these labels from 10.5,
     and at 320px (the narrowest phone still in the field) "Follow-ups" wanted
     62px against 61.6px of tab — half a pixel, paid for out of the bar's own
     side padding rather than out of the floor. `nowrap` is the guard: a label
     that wraps makes its tab two lines tall, which moves the whole bar and the
     FAB above it, and that is a worse failure than a tight fit. */
  /* THE LIVE TAB WEARS A CAPSULE (rep app refresh, 2026-09-28). Colour alone
     marked it -- an indigo glyph among four grey ones, which in sun is one
     grey among five. A 52x30 `--accent-bg` capsule behind the icon is a SHAPE,
     and a shape survives glare where a hue does not. Drawn on the glyph itself
     so the markup is untouched; `--r-pill` because it is a pill. The bar grew
     ~5px for it, which is why every dock's `bottom` offset moved 58 -> 64. */
  .tabbar{position:absolute;bottom:0;left:0;right:0;background:rgba(255,255,255,.94);backdrop-filter:blur(10px);border-top:1px solid var(--line);
    display:flex;padding:6px 4px calc(10px + env(safe-area-inset-bottom, 8px));z-index:30}
  .tab{flex:1;min-width:0;white-space:nowrap;display:flex;flex-direction:column;align-items:center;gap:2px;font-size:var(--t-xs);color:var(--ink3);cursor:pointer;font-weight:600}
  .tab .ti{font-size:22px;width:52px;height:30px;border-radius:var(--r-pill);
    display:flex;align-items:center;justify-content:center}
  .tab.on{color:var(--accent)} .tab.on .ti{background:var(--accent-bg)}
  /* AN EXTENDED PILL, NOT A CIRCLE (2026-09-28): 52px, the icon and the
     word, --r-pill. `syncFab` sets display:flex, so it stays a flex row. */
  .fab{position:absolute;right:16px;bottom:88px;height:52px;padding:0 20px 0 16px;gap:8px;border-radius:var(--r-pill);
    background:var(--accent);color:var(--on-fill);border:none;font-family:inherit;
    display:flex;align-items:center;justify-content:center;font-size:var(--t-m);font-weight:600;z-index:35;cursor:pointer;
    /* rgba of --accent first for engines without color-mix; the token wins
       after. The literal used to be rgba(47,109,246,.4) -- the commodity blue
       --accent replaced with indigo, left behind so the FAB cast a shadow in a
       colour nothing else on the screen used, and cast the LIGHT one in dark
       mode where --accent lifts to #8b93f0. */
    box-shadow:0 8px 20px rgba(58,69,176,.4);
    box-shadow:0 8px 20px color-mix(in srgb, var(--accent) 40%, transparent)}
  .fab .ti{font-size:22px}
  /* ==== THE ORDER EDITOR (rep app refresh, 2026-09-28) =====================
     THE DOCK. The running total (2026-08-06) was a sticky bar that settled
     back into the flow above the buyer card, and Submit sat alone at the foot
     of the form, because a sticky SUBMIT Drew tried in Jul 2026 covered the
     totals and was reverted. The refresh answers that the way Log a visit's
     dock already does: the running total rides IN the dock, directly over
     Submit, and the view pads behind the dock so nothing in the form is ever
     under it. Same shell as #log-dock; the line keeps its ids and classes. */
  #order-dock{position:absolute;left:0;right:0;bottom:calc(64px + env(safe-area-inset-bottom, 8px));
    padding:10px 16px 12px;
    background:rgba(245,244,241,.95);
    background:color-mix(in srgb, var(--bg) 95%, transparent);
    backdrop-filter:blur(10px);border-top:1px solid var(--line);z-index:29}
  :root[data-theme='dark'] #order-dock{background:rgba(23,22,19,.95);
    background:color-mix(in srgb, var(--bg) 95%, transparent)}
  body.ov-open #order-dock{display:none}
  #v-order{padding-bottom:136px}
  #order-dock .btn{height:52px;border-radius:var(--r-control)}
  /* Nothing in the cart yet: the button stays in place and says what it is,
     but tinted, the way Log a visit's unanswered Save is -- a solid button
     that does nothing when tapped reads as broken. */
  #order-dock .btn:disabled{background:var(--accent-bg);color:var(--accent);cursor:default}
  .order-pinbar{display:flex;align-items:baseline;justify-content:space-between;gap:10px;
    margin:0 2px 8px;font-size:var(--t-s);color:var(--ink2)}
  .order-pinbar .opb-money{font-family:var(--font-mono);font-size:var(--t-m);color:var(--ink)}
  /* Find a product or size: the field on the ground, so bordered. */
  .o-search{height:44px;padding:0 12px;margin-bottom:14px}
  .o-search .ti{font-size:17px}
  #order-q{flex:1;min-width:0;border:none;background:transparent;font-family:inherit;
    font-size:var(--t-s);color:var(--ink);outline:none;padding:0}
  /* THE PRODUCT CARD: the repeat row first, then one row per product --
     tile, name, price, the row's own line total once it holds a quantity,
     and the pill stepper. Rows inside, hairlines between, 14px sides. */
  .card.ocard{padding:0 14px}
  .orow{display:flex;align-items:center;gap:12px;padding:12px 0;border-bottom:1px solid var(--line-soft)}
  .ocard>:last-child{border-bottom:none}
  .orow:has(+ .orow2){border-bottom:none;padding-bottom:6px}
  .orow2{display:flex;justify-content:flex-end;padding:0 0 12px;border-bottom:1px solid var(--line-soft)}
  .orepeat{border-bottom-color:var(--line)}
  /* WHITE tile, hairline edge (Drew): stored product images are flattened
     onto white, so the tile is the image's own white, not a grey letterbox. */
  .orow .otile{width:44px;height:44px;object-fit:contain;border-radius:var(--r-control);
    background:#fff;border:1px solid var(--line);flex:none;padding:3px}
  .orow .otile.o-hist{display:flex;align-items:center;justify-content:center;padding:0;
    background:var(--chip-bg);border:none;color:var(--ink2)}
  .o-hist .ti{font-size:22px}
  .omid{flex:1;min-width:0}
  .oname{font-size:var(--t-s);font-weight:600;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .ometa{font-size:var(--t-xs);color:var(--ink3);margin-top:2px}
  .oline{font-size:var(--t-xs);font-weight:600;color:var(--ink);margin-top:2px}
  .oline:empty{display:none}
  .ofill{flex:none;height:36px;padding:0 12px;border:none;border-radius:var(--r-control);
    background:var(--chip-bg);color:var(--ink);font-family:inherit;font-size:var(--t-s);font-weight:600;cursor:pointer}
  /* ONE PILL, `−  qty  +`: 44px tall, a 44x44 hit area each side, the count
     in mono between. --chip-bg / --ink2 at zero; the accent tint once it
     holds a quantity, so the cart is visible down a long catalog without
     reading a number. */
  .ostep-wrap{display:flex;align-items:center;gap:10px;flex:none}
  .ostep-lab{font-size:var(--t-s);color:var(--ink2)}
  .orow .ostep-lab{display:none}
  .ostep{display:flex;align-items:center;height:44px;border-radius:var(--r-control);
    background:var(--chip-bg);color:var(--ink2)}
  .ostep.on{background:var(--accent-bg);color:var(--accent)}
  .ostep button{width:44px;height:44px;padding:0;border:none;background:none;color:inherit;
    display:flex;align-items:center;justify-content:center;cursor:pointer}
  .ostep button .ti{font-size:16px}
  .ostep b{min-width:20px;text-align:center;font-family:var(--font-mono);font-size:var(--t-m);font-weight:600}
  .ostep-none{flex:none;display:flex;align-items:center;gap:8px;max-width:150px;opacity:.55;
    font-size:var(--t-xs);color:var(--ink3)}
  .orow .ostep-none .ostep-lab{display:none}
  /* THE ANSWERS UNDER THE CARD: Buyer, the licence, the note -- rows on the
     ground, not three more cards with the weight of the catalog. */
  .osec{margin:22px 0 14px}
  .osec-lab{font-size:var(--t-s);font-weight:600;color:var(--ink);margin:0 0 8px}
  .oline-row{display:flex;align-items:center;gap:10px;min-height:44px;padding:12px 0;
    border-bottom:1px solid var(--line);font-size:var(--t-s);color:var(--ink2)}
  .oline-row .ogrow{flex:1;min-width:0}
  .oline-row b{color:var(--ink);font-weight:600}
  .oline-row.osel select{flex:1;margin:0}
  .olink{flex:none;color:var(--accent);font-weight:600;cursor:pointer}
  .onote{color:var(--ink3)}
  .onote .ti{font-size:17px;flex:none}
  #order-note{flex:1;min-width:0;border:none;background:transparent;font-family:inherit;
    font-size:var(--t-s);color:var(--ink);outline:none;padding:0}
  #order-totals{margin-top:16px}
  .toast{position:absolute;bottom:92px;left:16px;right:16px;background:var(--ink);color:var(--on-fill);border-radius:var(--r-card);padding:13px 15px;font-size:var(--t-s);
    display:flex;align-items:center;gap:9px;opacity:0;transform:translateY(8px);transition:.15s;z-index:50;pointer-events:none}
  .toast.show{opacity:1;transform:translateY(0)}
  .av{width:26px;height:26px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:var(--t-xs);font-weight:600}
  .addmine{display:flex;align-items:center;gap:3px;background:var(--accent-bg);color:var(--accent);border:none;font-size:var(--t-xs);font-weight:600;padding:6px 10px;border-radius:var(--r-pill);cursor:pointer;flex:0 0 auto}
  /* 44px, AND THE 44 IS THE POINT (2026-08-27). These predate the rule rather
     than being exceptions to it: the pour page's steppers are already 44 and
     the tasting pickrow was deliberately raised to 48 with the reasoning
     written down beside it, so a 30px control is what a stepper was before
     anybody measured one. This is the order editor's quantity control, tapped
     repeatedly, one-handed, by somebody holding a bottle.
     The BOX grows and the glyph does not -- a bigger + would read as a
     different control. */
  .stepper{display:flex;align-items:center;gap:10px;background:var(--tabs-bg);border-radius:var(--r-control);padding:2px 6px}
  .stepper button{width:44px;height:44px;border-radius:calc(var(--r-control) - 2px);border:none;background:var(--card);color:var(--ink);display:flex;align-items:center;justify-content:center;cursor:pointer}
  /* The +/- are GLYPHS, not type: the 44px hit area grew, the mark did not.
     Sizing them through the `ti` class is also what makes this an icon size
     rather than a twelfth text size (see lint-globals.mjs). */
  .stepper button .ti{font-size:15px}
  .stepper span{font-size:var(--t-m);font-weight:600;color:var(--ink);min-width:20px;text-align:center}
  .daychips{display:flex;flex-wrap:wrap;gap:6px}
  .dc{flex:1 1 40px;min-width:40px;padding:9px 0;text-align:center;border-radius:var(--r-control);border:none;background:var(--chip-bg);font-size:var(--t-xs);font-weight:600;color:var(--ink2);cursor:pointer}
  .dc.on{background:var(--accent-bg);color:var(--accent)}
  /* ==== ACCOUNT DETAILS, 16a: the hub, the four screens, one Save each ====
   *
   * THE SAVE BAR IS THE WHOLE POINT OF THE REDESIGN, so it is a class rather
   * than four hand-rolled footers. It pins to the foot of its screen, names
   * what it governs, and goes inert when there is nothing to write — which is
   * how the rep can tell a saved screen from an unsaved one WITHOUT reading
   * the form back.
   *
   * BORDER-TOP AND BLUR, NO SHADOW: rule 3 in tokens.css is that elevation is
   * surface lightness, and a shadow on the dark ground reads as grime. Same
   * shell as #count-dock, which solved this once already.
   *
   * `position:absolute` inside .view, NOT fixed: .view sits inside the phone
   * shell, and a fixed bar escapes that shell on the desktop preview — the bug
   * the tabbar already avoids the same way. `bottom` and the pane's padding are
   * MEASURED by _syncSavebar, not typed: the tab bar's height is a safe-area
   * calc and the bar's own height changes with a wrapped label, so both are
   * numbers only the layout knows. Same reason #acct-scroll is padded by
   * _syncScrollPad rather than by a literal. */
  .savebar{position:absolute;left:0;right:0;bottom:0;z-index:28;
    padding:10px 16px 12px;background:color-mix(in srgb, var(--bg) 92%, transparent);
    backdrop-filter:blur(10px);border-top:1px solid var(--line)}
  .savebar-lab{display:flex;align-items:center;gap:7px;margin-bottom:9px;font-size:var(--t-s)}
  /* The dot is the dirty light. --warm because unsaved work is ATTENTION, not
     alarm; it disappears entirely when clean rather than turning grey, so the
     row reads as one state and not two. */
  .savebar-dot{width:8px;height:8px;border-radius:50%;background:var(--warm);flex:0 0 auto}
  .savebar-what{flex:1;color:var(--ink2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .savebar-discard{color:var(--accent);font-weight:600;cursor:pointer;flex:0 0 auto}
  /* INERT, NOT HIDDEN, and not `disabled` alone: the button has to stay in
     place so the screen does not reflow the moment a rep types, and it has to
     LOOK spent so nobody taps it expecting a write. --chip-bg / --ink3 is the
     pair tokens.css already uses for "present but not actionable". */
  .savebar .btn.spent{background:var(--chip-bg);color:var(--ink3);cursor:default}

  /* THE HUB'S ROWS. Not `.li` and not `.hrow`: this is the only list in the
     app whose sub-line changes colour to mean "this blocks an order", and
     borrowing a class would put that behaviour one edit away from every other
     list. `border-top` on each with `border-bottom` on the last is what makes
     four rows read as one block without a card around them — the hub is a
     table of contents, and a card would make it look like content. */
  .hubrow{display:flex;align-items:center;gap:12px;padding:15px 0;border-top:1px solid var(--line);cursor:pointer}
  .hubrow:last-of-type{border-bottom:1px solid var(--line)}
  .hubrow .ti.hub-ico{font-size:20px;color:var(--ink3);flex:0 0 auto}
  .hubrow .hub-txt{flex:1;min-width:0}
  .hubrow .hub-lab{font-size:var(--t-m);font-weight:600;color:var(--ink)}
  /* --warm when something is NEEDED, --ink3 when set. The colour is the only
     thing on the row that is doing compliance work, so nothing else on it is
     allowed to be coloured. */
  .hubrow .hub-state{font-size:var(--t-s);color:var(--ink3);margin-top:1px;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .hubrow .hub-state.need{color:var(--warm)}
  .hubrow .hub-dot{width:8px;height:8px;border-radius:50%;background:var(--warm);flex:0 0 auto}
  .hubrow .ti.hub-ok{font-size:17px;color:var(--ok);flex:0 0 auto}
  .hubrow .ti-chevron-right{font-size:17px;color:var(--mute-line);flex:0 0 auto}

  /* The blocking callout, shared by the hub and the licence screen. --warm-wash
     on --warm-line, NOT --warm-bg: at full strength on the dark ground a tint
     that size reads as a highlighter. */
  .blockcall{display:flex;gap:9px;background:var(--warm-wash);border:1px solid var(--warm-line);
    border-radius:var(--r-control);padding:11px 12px;margin-bottom:16px;font-size:var(--t-s);color:var(--ink2)}
  .blockcall .ti{font-size:17px;color:var(--warm);flex:0 0 auto}

  /* THE APP'S OWN MULTI-SELECT, replacing the only bare native
     <input type="checkbox"> left in the product. 44px clears the iOS minimum;
     the check glyph appears only when on, so an unpicked chip is not a chip
     with an empty box in it. Also carries the venue screen's premise picker,
     which was a `.seg` — a segmented control says "exactly one of these, and
     one is already true", and premise is a question with a real answer the rep
     may not have given yet. */
  .mschips{display:flex;flex-wrap:wrap;gap:8px}
  .mschip{display:flex;align-items:center;justify-content:center;gap:6px;height:44px;padding:0 15px;
    border-radius:var(--r-control);border:none;background:var(--chip-bg);
    font-size:var(--t-s);font-weight:600;color:var(--ink2);cursor:pointer;font-family:inherit}
  .mschip.on{background:var(--accent-bg);color:var(--accent)}
  .mschip .ti{font-size:15px}

  /* The delivery day-hint. Grey, not --warm, and deliberately: it is guidance,
     and the requirement inside it is stated in words rather than coloured. */
  .dayhint{display:flex;gap:9px;background:var(--chip-bg);border-radius:var(--r-control);padding:11px 12px;
    margin-top:12px;font-size:var(--t-s);color:var(--ink2)}
  .dayhint .ti{font-size:16px;color:var(--ink3);flex:0 0 auto}
  /* LAST CHANGED: hairline rows, no card. A card around two lines of history
     gives it the weight of the thing it is history OF. */
  .histrow{display:flex;align-items:baseline;gap:10px;padding:9px 2px;border-top:1px solid var(--line-soft)}
  .histrow .hr-what{flex:1;font-size:var(--t-s);color:var(--ink2)}
  .histrow .hr-when{font-size:var(--t-xs);color:var(--ink3)}
  /* The certificate on file. 44 x 56 is the certificate's own portrait ratio,
     not the square a visit photo gets — a wall certificate cropped square is
     unreadable at thumbnail size and this thumbnail's whole job is to be
     recognisable as the right piece of paper. */
  .certrow{display:flex;align-items:center;gap:11px;margin-top:12px}
  .certrow .cert-thumb{width:44px;height:56px;border-radius:7px;object-fit:cover;
    background:var(--chip-bg);border:1px solid var(--line);flex:0 0 auto}
  .invrow{display:flex;align-items:center;gap:10px}
  /* Tasting scope: THE WHOLE ROW IS THE TARGET (Drew, 2026-08-16). A separate
     On/Off chip made the name look like a label and put a ~30px hit box at the
     far edge of the screen -- the worst place to reach one-handed while you are
     holding a bottle. 48px clears the iOS 44pt minimum with the row padding. */
  .pickrow{display:flex;align-items:center;gap:10px;min-height:48px;cursor:pointer;
    -webkit-tap-highlight-color:transparent}
  .pickrow .nm{flex:1;font-size:var(--t-s);color:var(--ink2);transition:color .12s}
  .pickrow .bx{font-size:var(--t-l);color:var(--ink3);line-height:1;transition:color .12s}
  .pickrow.on .nm{color:var(--ink);font-weight:600}
  .pickrow.on .bx{color:var(--accent)}
  .pickrow+.pickrow{border-top:1px solid var(--line)}
  .invhint{display:flex;justify-content:space-between;align-items:center;margin-top:10px;padding-top:10px;border-top:1px solid var(--line);font-size:var(--t-xs);color:var(--ink2)}
  /* `.remact`, `.ra`, `.ra.done`, `.snooze` and `.snooze button` are GONE
     (2026-08-28). 15a left them in place with a note saying they were "still
     the Today screen's task cards and `_laterChooser`, which are a separate
     ticket" — this is that ticket. Today's four sections of task cards are one
     ranked list of rows now, so the last markup wearing any of these classes
     went with them and every rule above was dead weight on a cellular
     download. The follow-up actions they used to draw live in the ⋯ sheet. */
  .dedupe{background:var(--warm-bg);border:1px solid #f0d59a;border-radius:var(--r-control);padding:12px;margin-bottom:16px}
  /* ~31 -> 44 by min-height rather than by padding, so the LABEL stays where
     it is and only the hit box grows -- a .mini padded to 44 would stop being
     mini and would re-flow every row it sits in. */
  .mini{background:var(--chip-bg);border:none;color:var(--ink);font-size:var(--t-xs);font-weight:600;padding:8px 12px;border-radius:var(--r-control);cursor:pointer;
    min-height:44px;display:inline-flex;align-items:center;justify-content:center}
  .mini.ghost{background:transparent;color:var(--ink2)}
  /* Contextual help bottom sheet (the nav "?" fills it per screen). The data-
     health drill-down (#detail-sheet) reuses the same bottom-sheet shell. */
  #help-sheet,#detail-sheet,#brand-sheet,#firstrun-sheet,#filing-sheet{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:120;display:none;align-items:flex-end;justify-content:center}
  #help-sheet.open,#detail-sheet.open,#brand-sheet.open,#firstrun-sheet.open,#filing-sheet.open{display:flex}
  #help-sheet .hs-card,#detail-sheet .hs-card,#brand-sheet .hs-card,#firstrun-sheet .hs-card,#filing-sheet .hs-card{background:var(--card);border-radius:var(--r-card) var(--r-card) 0 0;max-width:480px;width:100%;max-height:75vh;overflow-y:auto;padding:18px 18px calc(20px + env(safe-area-inset-bottom));font-size:var(--t-s);line-height:1.6;color:var(--ink)}
  /* First run sits ABOVE the other sheets: it opens on a cold landing and must
     not end up under a help sheet the rep opened on the way past. */
  #firstrun-sheet{z-index:130}
  .fr-step{display:flex;gap:11px;align-items:flex-start;padding:10px 0;border-top:1px solid var(--line)}
  .fr-step:first-of-type{border-top:0}
  .fr-step .ti{font-size:19px;color:var(--accent);flex:none;margin-top:1px}
  .fr-step b{display:block;color:var(--ink)}
  .fr-step span{color:var(--ink3)}
  /* §15a brand picker sheet: fixed-height list keeps the search box pinned
     while results scroll — the finger never chases the input. */
  #brand-sheet .hs-card{overflow:hidden;display:flex;flex-direction:column}
  #bs-list{overflow-y:auto;-webkit-overflow-scrolling:touch;flex:1;min-height:120px}
  .bs-row{display:flex;align-items:center;gap:10px;padding:12px 4px;border-top:1px solid var(--line);cursor:pointer;font-size:var(--t-s)}
  .bs-row:first-child{border-top:0}
  .bs-row .ti{font-size:18px;color:var(--line)}
  .bs-row.on .ti{color:var(--ok)}
  .bs-row.on{font-weight:600}
  #help-sheet h3,#detail-sheet h3{margin:0 0 8px;font-size:var(--t-m)}
  #help-sheet p{margin:0 0 10px} #help-sheet p:last-child{margin:0}
  #help-sheet b,#detail-sheet b{color:var(--accent)}
  #detail-sheet .dl-row{display:flex;justify-content:space-between;gap:12px;padding:7px 0;border-top:1px solid var(--line)}
  #detail-sheet .dl-row:first-of-type{border-top:0}
  #detail-sheet .dl-k{color:var(--ink3)} #detail-sheet .dl-v{color:var(--ink);font-weight:600;text-align:right}
  /* ==== ACCOUNT SCREEN (14a re-layout, 2026-08-28) =========================
     The account view owns its scrolling: #body stops scrolling (.acct), the
     hero and tab strip stay put, #acct-scroll scrolls, and the dock rides
     above the tab bar. All colours come from vendor/tokens.css — no mock
     hexes, and every font-size is a --t-* step (an icon glyph excepted).

     WHAT 14a CHANGED AND WHY, in one place. 10a fixed the structure — tabs
     above content, actions docked — then drew six sibling cards of identical
     width, radius, border and weight, so the sentence the screen exists to
     deliver carried the same visual rank as a desk-work row six down. 14a
     ranks instead of adding: ONE idiom in the pane (rows on the ground,
     hairline rules, no cards), the identity in a hero that owns the header
     with the nav, and exactly one accent-filled element on the screen. */
  .body.acct{padding:0;overflow:hidden;display:flex;flex-direction:column}
  #v-account.active{display:flex;flex-direction:column;flex:1;min-height:0}
  /* THE NAV IS PART OF THE HEADER BLOCK HERE (14a §3). The handoff writes
     this rule as `body.acct .nav`, which cannot match: `.acct` goes on #body,
     and the nav is #body's SIBLING, not its child. render() toggles the class
     on the nav itself instead — same intent, a selector that resolves. */
  /* 2026-09-28: the nav is a header block everywhere now, so here it is only
     the top row of this one -- Back and the corner doors -- and #acc-hero
     below it says the name. No title of its own, and no rule or padding
     between the row and the hero, or the header reads as two. */
  .nav.acct{border-bottom:none;padding-bottom:0}
  .nav.acct .nav-head{display:none}
  /* The hero: name, the status line that routes to store hours, three chips.
     #acc-hero draws the rule the nav gave up, so the two read as one block. */
  #acc-hero{flex:0 0 auto;background:var(--card);border-bottom:1px solid var(--line);padding:6px 16px 12px}
  /* Never ellipsised — it is the subject of the screen, so it wraps. */
  #acc-hero .hname{font-size:var(--t-xl);line-height:1.15;font-weight:600;color:var(--ink);letter-spacing:-.02em}
  /* THE EDIT DOOR sat beside the name from 2026-08-29 (`.hidrow` / `.hedit`)
     and moved up into the nav's corner on 2026-09-28, as `#navDetails` -- the
     nav became this screen's top row, so "at the top of the account" is one
     row higher and the name has its full width to wrap in. Labelled, as it
     has been since 2026-09-02: a bare pencil beside a venue name reads as
     "rename this", and the door opens the whole detail hub. */
  /* THE WHOLE ROW IS THE TAP TARGET. A tappable fragment would be ~70×18px,
     under the floor and invisible without an affordance. */
  #acc-hero .hstat{display:flex;align-items:center;gap:8px;min-height:44px;
    font-size:var(--t-s);color:var(--ink3);cursor:pointer}
  #acc-hero .hstat .htxt{flex:1;min-width:0}
  /* Only the FIGURES go mono — --font-mono is for numbers that change, and a
     person's name is not one (which is why the buyer stays in sentence case). */
  #acc-hero .hstat b{font-family:var(--font-mono);font-weight:600;color:var(--ink2)}
  #acc-hero .hstat>.ti-chevron-right{font-size:17px;color:var(--mute-line);flex:0 0 auto}
  #acc-hero .hchips{display:flex;gap:8px;margin-top:4px}
  /* NEUTRAL, NOT --accent-bg. All three answer one question — how do I get
     hold of this door — which is how you arrive at the work, not the work.
     Call is rightmost: the shortest one-handed reach, and a rep calls a buyer
     far more often than they email one. */
  #acc-hero .hchip{flex:1;height:44px;border-radius:var(--r-control);background:var(--chip-bg);border:none;
    display:flex;align-items:center;justify-content:center;gap:6px;
    font-family:inherit;font-size:var(--t-s);font-weight:600;color:var(--ink);cursor:pointer}
  #acc-hero .hchip .ti{font-size:17px;color:inherit}
  /* #acct-tabstrip IS GONE (2026-09-28) -- its rules with it. The strip was
     14a's underline tabs; the refresh folded them into the Overview as the
     three #acc-panes rows below, so only the app's own tab bar remains. */
  /* Bottom padding is MEASURED, not literal — the dock's height varies by
     account (add-on gating drops its upper row to one cell or removes it),
     and a literal would leave ~54px of dead space at a door without
     tastings. _syncScrollPad sets it once per paint and on resize. */
  #acct-scroll{flex:1 1 auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;
    padding:18px 16px 232px;scrollbar-width:none}
  #acct-scroll::-webkit-scrollbar{display:none}
  /* `.remdot` WENT WITH THE STRIP (2026-09-28). It was the word "Due" on the
     Follow-ups tab -- a word rather than a dot since 2026-09-02, and no count,
     because the tab opened onto the list that gave one. The row that replaced
     the tab IS that summary: "1 due, 2 open", the due clause in --hot. Still
     a word first; the figure is the list's, said where the list is. */
  #acc-pills:not(:empty){margin-bottom:16px}
  /* THE THREE PANE ROWS (2026-09-28): the Overview's `.hrow` idiom exactly --
     44px, hairline on top, the label in --ink, the answer in --ink2, a
     --mute-line chevron because the row continues elsewhere. The due clause
     is the one coloured thing, and only when something is due. */
  #acc-panes .hrow b{font-family:var(--font-sans);color:var(--ink)}
  #acc-panes .pr-due{color:var(--hot);font-weight:600}
  /* A pane reached from a row says which pane it is -- the strip said it by
     being lit. The --t-m section-question weight the refresh uses for every
     heading a rep reads as "where am I". */
  .acct-panehead{font-size:var(--t-m);font-weight:600;color:var(--ink);margin:0 0 10px}
  /* TODAY'S FIRST-RUN NUDGE (2026-09-28): an accent-tinted block, the
     screen's one filled button inside it. See _firstRunNudgeHtml. */
  .frn{background:var(--accent-bg);border-radius:var(--r-card);padding:16px;margin-bottom:12px}
  .frn-t{font-size:var(--t-m);font-weight:600;color:var(--ink);line-height:var(--lh-m)}
  .frn-s{font-size:var(--t-s);color:var(--ink2);line-height:var(--lh-s);margin-top:4px}
  .frn .btn.frn-go{width:auto;height:44px;padding:0 16px;margin-top:12px;font-size:var(--t-s)}
  .frn .btn.frn-go .ti{font-size:17px}
  /* ONE IDIOM IN THE PANE (14a §6): rows on the ground with hairline rules.
     No cards. The card border was drawing rectangles around groups that
     proximity already made, and it is why the header and the middle read as
     two different products. `.card`, `.outcard`, `.acctrow-card` and the old
     grid `.numstrip` are all unused on the Overview now. */
  /* Outstanding: what you owe this door. ONE row — money is a pill in the
     Last order tile now (§7b). The cap stands: a third class of problem
     REPLACES this row, it does not extend a stack. The one exception is the
     ILCC list, where a legal block takes the row and the follow-up moves
     beneath it. */
  .outblock{margin-bottom:18px;animation:briefIn .15s ease-out both}
  .outblock.still{animation:none}
  .outrow{display:flex;gap:9px;align-items:flex-start;cursor:pointer}
  .outrow+.outrow{margin-top:12px}
  /* 8px since 2026-09-28: one dot size app-wide -- the list's warmth dot,
     the hero's open/shut, this. Seven and eight side by side read as a slip. */
  .odot{width:8px;height:8px;border-radius:50%;flex:0 0 auto}
  .outrow>.odot{margin-top:7px}
  /* 17px, NOT 20px. This fires at nearly every door with an open follow-up,
     and a rep who opened the account already knows what they owe — at --t-l
     with a red edge it read as the app shouting. First on the screen, quiet
     about it. The late clause is the only thing in --hot. */
  .outrow .otxt{flex:1;min-width:0;font-size:var(--t-m);font-weight:500;line-height:1.35;
    color:var(--ink);text-wrap:pretty}
  .outrow .osub{font-size:var(--t-s);font-weight:400;color:var(--ink3);margin-top:3px}
  .outrow>.ti-chevron-right{font-size:17px;flex:0 0 auto;margin-top:4px}
  /* THE ONE BLOCK IN THE PANE, and it earns the exception (2026-08-28).
     §6's rule is rows on the ground with hairline rules, because 10a's six
     identical cards drew rectangles around groups that proximity already
     made. This is the opposite case: a statewide sales ban is not the same
     KIND of thing as a follow-up, and rendering it as one more row left it
     quieter than an overdue promise -- a 7px dot against the follow-up's red
     bold clause, which is the ranking exactly inverted.

     `--hot-wash` is the token for tinting a whole row rather than a chip; it
     is barely there on purpose, so the band separates without shouting, and
     the words carry the weight. A rep must not be able to scroll past this. */
  .outrow.barred{background:var(--hot-wash);border:1px solid var(--hot-line);
    border-radius:var(--r-control);padding:11px 12px;margin:0 -2px}
  .outrow.barred .otxt{color:var(--hot)}
  .outrow.barred>.odot{margin-top:8px}

  /* CHEVRONS ARE STRUCTURE, NOT ACTION (2026-08-28).
     `--accent` used to mark every TAPPABLE ROW as well as every action, and a
     colour that marks everything cannot mark anything: the account screen put
     four accent chevrons above the fold, plus Add, the outlined Order, the
     help circle and the live tab-bar item -- eight equally-indigo candidates
     for "the thing to press". A chevron says "this row continues elsewhere",
     which is structure, and structure belongs in `--mute-line`: the token that
     exists for non-text marks that owe no contrast ratio.

     BACK CHEVRONS ARE EXCLUDED. `ti-chevron-left` in a nav row is part of a
     labelled control and keeps its accent, as does a chevron sitting inside a
     labelled text link ("Fix >", "Attach it to the account >").

     What keeps the accent after this: the one primary button per screen, the
     FAB, the live tab-bar item, the active tab underline, text links, and lit
     filter chips. Things that ACT, and things that are ON. Nothing else.

     14a adds `.hrow` and the hero's status line to the list. Both are rows
     that continue elsewhere -- the licensee behind the business sheet, the
     details behind the edit form, the status line behind the hours sheet --
     which is the same job, so it is the same colour. */
  .acctrow .ti-chevron-right,
  .outrow  > .ti-chevron-right,
  .hrow    > .ti-chevron-right,
  .stop    .ti-chevron-right,
  .bt-more .ti-chevron-right,
  .li      .ti-chevron-right{color:var(--mute-line)}
  .outclean{display:flex;align-items:center;gap:10px;margin:0 4px 18px;font-size:var(--t-s);color:var(--ink2)}
  /* TWO FACTS, no grid, no borders, no dividers (§7b). A tile renders only
     when a real value exists — two tiles with a 28px gap need no filler. */
  /* Wraps rather than overflows: two --t-xxl figures ("$1,240" / "Tue Thu")
     fit side by side from 360px up, and on a 320px phone the second drops
     under the first instead of running off the edge. */
  .numstrip{display:flex;flex-wrap:wrap;gap:12px 28px;margin-bottom:16px}
  .numtile{min-width:0}
  /* THE LABEL IS A WORD, THE FIGURE IS THE NUMBER (2026-09-28). The label was
     mono uppercase, which set "LAST ORDER" in the same voice as the $1,240
     beneath it and made the pair read as two figures. Sentence case, sans,
     --ink3; the figure takes the dominant step (--t-xxl), which is what the
     scale reserves it for -- "a single dominant figure". */
  .numtile .nl{font-size:var(--t-xs);font-weight:600;color:var(--ink3)}
  .numtile .nv{font-size:var(--t-xxl);line-height:var(--lh-xxl);font-weight:600;font-family:var(--font-mono);font-variant-numeric:tabular-nums;
    letter-spacing:-.02em;color:var(--ink);margin-top:4px;white-space:nowrap}
  .numtile .ns{font-size:var(--t-xs);font-family:var(--font-mono);color:var(--ink3);margin-top:1px;white-space:nowrap}
  /* THE MONEY PILL occupies the Last order tile's third line — the slot the
     order date used to hold, so the tile never changes height whichever state
     it is in. THREE STATES, RANKED NOT STACKED: OVERDUE outranks DUE IN, and
     one pill only (a tile with two wraps). `NO SALES` is deliberately NOT in
     this slot: it is a permission state, not a money state, and ranking it
     first would hide the amount owed at exactly the doors where the amount
     matters. It lives in the outstanding row and on the Order button. */
  /* A STATUS PILL, SO THE PILL RULE (2026-09-28): sentence case, sans, a
     leading 6px dot in its own colour. "OVERDUE 12D" in mono caps was the
     loudest type on the screen after the venue's name. */
  .moneypill{display:inline-flex;align-items:center;gap:5px;margin-top:6px;padding:2px 8px;border-radius:var(--r-pill);
    font-family:var(--font-sans);font-size:var(--t-xs);font-weight:600;white-space:nowrap}
  .moneypill::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:0 0 auto}
  .moneypill.hot{background:var(--hot-bg);color:var(--hot)}
  .moneypill.warm{background:var(--warm-bg);color:var(--warm)}
  .numnone{border:1px dashed var(--line);border-radius:var(--r-control);padding:12px 14px;margin-bottom:24px}
  /* HAIRLINE ROWS (§7c/§7e): the shelf estimate, licensee, account details.
     44 by min-height, the rule on TOP so a row that does not render leaves no
     orphaned divider behind it. */
  .hrow{display:flex;align-items:center;gap:10px;min-height:44px;padding:11px 0;
    border-top:1px solid var(--line);font-size:var(--t-s);color:var(--ink2)}
  .hrow.tap{cursor:pointer}
  .hrow .hgrow{flex:1;min-width:0}
  .hrow b{font-family:var(--font-mono);font-weight:600;color:var(--ink)}
  /* "from 2 Sep" is a date said in words, not a figure that changes in
     place -- sans, sentence case, like every other trailing clause (2026-09-28). */
  .hrow .hend{font-size:var(--t-xs);color:var(--ink3);white-space:nowrap}
  .hrow>.ti-chevron-right{font-size:17px;flex:0 0 auto}
  /* Old card shells for the account rows. Still read by the Buyers tab's
     venue rows (#acc-venue); the Overview draws none. */
  .acctrow-card{background:var(--card);border:1px solid var(--line);border-radius:var(--r-card);margin-bottom:8px}
  .acctrow{display:flex;align-items:center;gap:10px;padding:12px 14px;cursor:pointer;font-size:var(--t-s)}
  .acctrow+.acctrow{border-top:1px solid var(--line-soft)}
  .acctrow .ti-chevron-right{font-size:16px;flex:0 0 auto;margin-left:auto}
  /* Section headers (SHELF PHOTOS / RECENT ACTIVITY) with a right action.
     Baseline-aligned since 14a: `All` is a word beside a label, not a
     full-width row costing 44px for a link that belongs next to its title. */
  .acct-sechead{display:flex;justify-content:space-between;align-items:baseline;margin:16px 2px 2px}
  /* A HEADING, NOT A LABEL (2026-09-28): 14px/600 in --ink, sentence case.
     The mono-caps 12px label it replaces was the style this pane used for
     figures, and a heading over a list is not a figure. */
  .acct-sechead .sh{font-size:var(--t-s);font-weight:600;color:var(--ink)}
  .acct-sechead .sha{font-size:var(--t-s);color:var(--accent);font-weight:600;cursor:pointer;padding:6px 4px}
  /* Activity rows: hairline-ruled, no card, one line less per row than 10a —
     the date joins the title as a mono clause instead of owning a meta line. */
  .actrow{display:flex;gap:11px;padding:12px 0;border-top:1px solid var(--line-soft)}
  .actrow .aico{width:32px;height:32px;border-radius:var(--r-control);flex:0 0 auto;
    display:flex;align-items:center;justify-content:center;font-size:var(--t-s)}
  .actrow .aico .ti{font-size:16px}
  /* Same four pairs `.ico` uses on every other timeline — a call goes neutral
     here (`.n`), because a phone call is the one touch that is neither a visit
     nor a commitment and warm was saying otherwise. */
  .actrow .aico.b{background:var(--accent-bg);color:var(--accent)}
  .actrow .aico.g{background:var(--ok-bg);color:var(--ok)}
  .actrow .aico.o{background:var(--warm-bg);color:var(--warm)}
  .actrow .aico.n{background:var(--chip-bg);color:var(--ink2)}
  .actrow .at{font-size:var(--t-s);font-weight:600;color:var(--ink)}
  .actrow .ab{font-size:var(--t-s);color:var(--ink2);line-height:1.45;margin-top:2px}
  .actrow .awhen{font-family:var(--font-mono);font-size:var(--t-xs);font-weight:400;color:var(--ink3)}
  /* The clauses 10a kept on a meta line of their own — who, warmth, shelf
     counts, the edited marker — ride the note line as a muted tail. */
  .actrow .ameta{color:var(--ink3)}
  .actrow .you{color:var(--accent);font-weight:600}
  /* The photos that went with a visit (PHOTOS_FROM_CAMERA_ROLL_2026-09-26.md):
     small squares under the note, painted from the strip's thumb cache. */
  .actrow .aphotos{display:flex;gap:6px;margin-top:8px}
  .actrow .aphoto{width:44px;height:44px;border-radius:8px;background-color:var(--line);
    background-size:cover;background-position:center;cursor:pointer;flex:0 0 auto}
  .actrow .aphoto-more{display:flex;align-items:center;justify-content:center;
    font-family:var(--font-mono);font-size:var(--t-xs);color:var(--ink2)}
  /* Shelf photos: three tiles, dashed empties. LAST in the pane now — they
     were the block the dock was slicing, and below activity they are never
     at rest under it. */
  .shelfgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:24px}
  .shelftile{height:76px;border-radius:var(--r-control);background-color:var(--line);background-size:cover;
    background-position:center;cursor:pointer}
  .shelftile-empty{height:76px;border-radius:var(--r-control);border:1px dashed var(--line);background:var(--chip-bg);
    display:flex;align-items:center;justify-content:center;color:var(--ink3);font-size:var(--t-l)}
  /* The dock. bottom clears the tab bar (same offset as .order-pinbar).
     TWO STATIC ROWS since 14a — the scroll-collapse is gone. 10a's grouping
     was right; its two defects were that the row MOVED UNTOUCHED (flagged
     unproven in 10a's own open questions) and that the pane did not pad
     behind it, so shelf photos were sliced by the dock at rest and the screen
     looked broken on first paint. Deleting the collapse fixes the first,
     _syncScrollPad fixes the second. */
  #acct-dock{position:absolute;left:0;right:0;bottom:calc(64px + env(safe-area-inset-bottom, 8px));
    padding:10px 16px 14px;
    /* rgba of --bg first for engines without color-mix; the token wins after. */
    background:rgba(245,244,241,.95);
    background:color-mix(in srgb, var(--bg) 95%, transparent);
    backdrop-filter:blur(10px);border-top:1px solid var(--line);z-index:29}
  :root[data-theme='dark'] #acct-dock{background:rgba(23,22,19,.95);
    background:color-mix(in srgb, var(--bg) 95%, transparent)}
  body.ov-open #acct-dock{display:none}
  /* THE DOCK IS THE OVERVIEW'S, AND ONLY THE OVERVIEW'S (Drew, 2026-08-28:
     "the log a visit/order stickiness is going into tabs it shoudlnt be").
     It was drawn inside #v-account, so it rode all four tabs — 133px of
     reflexes pinned over Buyers, Follow-ups and Orders, which are desk work
     and reference, not the thing you do standing at the door. `acctab` puts
     `tab-overview` on the view; _syncScrollPad re-pads the pane either way, so
     the tabs without it get their bottom back instead of dead space. */
  #v-account:not(.tab-overview) #acct-dock{display:none}
  /* AND NEVER OVER A SAVE BUTTON. #person-sheet and #buyhours-sheet are the
     two sheets that deliberately stay OFF the overlay stack (see their rule
     below), so `ov-open` — which every other sheet sets, and which is the only
     thing that used to lower the dock — never fires for them. DERIVED from the
     sheets' own state rather than a class six call sites have to remember to
     set: `.open` is already the one fact that says a sheet is up. */
  body:has(#person-sheet.open) #acct-dock,
  body:has(#buyhours-sheet.open) #acct-dock{display:none}
  /* 12a: the log form's docked save bar — same shell as #acct-dock. The view
     pads 128px behind it so the bar covers nothing (the July floating-Save
     revert is why this is a dock with padding, not a sticky button). */
  #log-dock{position:absolute;left:0;right:0;bottom:calc(64px + env(safe-area-inset-bottom, 8px));
    padding:10px 16px;
    background:rgba(245,244,241,.95);
    background:color-mix(in srgb, var(--bg) 95%, transparent);
    backdrop-filter:blur(10px);border-top:1px solid var(--line);z-index:29}
  :root[data-theme='dark'] #log-dock{background:rgba(23,22,19,.95);
    background:color-mix(in srgb, var(--bg) 95%, transparent)}
  body.ov-open #log-dock{display:none}
  #v-log{padding-bottom:128px}
  /* The Filed-as line, and ONLY that since 2026-09-28: the "what is missing"
     warning that shared this slot moved into the button as a counter pill,
     so the line appears when the form is answered and never before. */
  #log-dock-line{display:flex;gap:8px;align-items:center;margin:0 2px 8px;font-size:var(--t-xs);min-height:18px}
  #log-dock-line:empty{display:none}
  /* SAVE NEVER GOES GREY (2026-09-28). A grey button reads as "broken" or
     "not for you", and a rep who meets one does not look for why -- the
     warning that said why sat 12px tall above it. Unanswered, Save is the
     accent TINT with a counter pill inside it: "2 things left" is the reason
     and the progress in one place, and tapping still points at the question
     (saveLog flashes it). Answered, it fills: the one solid accent on the
     screen, the moment there is something to press. */
  #log-save.lg-need{background:var(--accent-bg);color:var(--accent);justify-content:space-between;padding:0 8px 0 18px}
  #log-save .lg-left{height:36px;padding:0 12px;display:flex;align-items:center;border-radius:var(--r-control);
    background:var(--card);color:var(--ink2);font-size:var(--t-s);font-weight:600;white-space:nowrap}

  /* ==== LOG A VISIT: THE QUESTIONS (rep app refresh, 2026-09-28) ==========
     Every question on the form is a --t-m heading with its state beside it:
     a Needed pill (--warm on --warm-bg: attention, not alarm) until it is
     answered, a check after, "Optional" in --ink3 on the ones that are. */
  .lq-head{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin:0 0 10px}
  .lq{font-size:var(--t-m);font-weight:600;color:var(--ink)}
  .lq-opt{font-size:var(--t-xs);color:var(--ink3)}
  .needpill{font-size:var(--t-xs);font-weight:600;color:var(--warm);background:var(--warm-bg);
    padding:2px 8px;border-radius:var(--r-pill);white-space:nowrap}
  .needpill.done{background:none;padding:0;color:var(--ok)}
  .needpill.done .ti{font-size:17px}
  /* How did you reach them: three equal 64px buttons, glyph over word, NONE
     lit until answered -- the old segmented control always looked answered. */
  .chgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:24px}
  .chgrid button{height:64px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;
    border:none;border-radius:var(--r-control);background:var(--chip-bg);color:var(--ink);
    font-family:inherit;font-size:var(--t-s);font-weight:600;cursor:pointer}
  .chgrid button .ti{font-size:22px}
  .chgrid button.on{background:var(--accent-bg);color:var(--accent)}
  /* The pre-answered row, in the question's own slot so the form does not
     move when the GPS lands. --ok-bg when the fix put the rep at the pin
     (verified, done); neutral when the answer came from the Call / Email
     button, which is a fact about the tap rather than about the place. */
  .chstated{display:flex;align-items:center;gap:10px;min-height:48px;padding:0 0 0 12px;margin-bottom:24px;
    border-radius:var(--r-control);background:var(--chip-bg);font-size:var(--t-s);color:var(--ink)}
  .chstated.at-pin{background:var(--ok-bg)}
  .chstated>.ti{font-size:18px;color:var(--ink2);flex:0 0 auto}
  .chstated.at-pin>.ti{color:var(--ok)}
  .chstated .cs-txt{flex:1;min-width:0}
  .chstated .cs-txt b{font-weight:600}
  .chstated .cs-why{color:var(--ink2)}
  .chstated .cs-change{color:var(--accent);font-weight:600;padding:14px 12px;cursor:pointer;flex:0 0 auto}
  /* Who did you talk to: three equal 48px answers. */
  #log-met-chips{display:flex;gap:8px}
  #log-met-chips .chip{flex:1;height:48px;display:flex;align-items:center;justify-content:center;gap:6px;
    padding:0 6px;color:var(--ink)}
  #log-met-chips .chip .ti{font-size:17px}
  /* What happened: the phrases, 40px pills. */
  #log-note-chips{display:flex;gap:6px;flex-wrap:wrap;margin:0 0 10px}
  .chip.nchip{flex:0 0 auto;height:40px;border-radius:var(--r-pill);padding:0 14px;display:flex;align-items:center}
  /* The note: always the box, with Talk beside it (see the markup's note). */
  .note-row{display:flex;gap:8px;align-items:stretch;margin-bottom:24px}
  #log-notes{min-height:60px;flex:1;margin:0}
  #log-mic{flex:0 0 60px;width:60px;min-height:60px;border-radius:var(--r-control);
    background:var(--accent-bg);color:var(--accent);display:flex;flex-direction:column;
    align-items:center;justify-content:center;gap:2px;padding:0;font-size:var(--t-xs);font-weight:600}
  #log-mic .ti{font-size:20px}
  /* Listening says so in colour as well as in words: a hot mic is RED. */
  #log-mic.on{background:var(--hot-bg);color:var(--hot)}
  #log-mic .lm-long{display:none}
  /* Anything else: a 2x2 grid of quiet buttons -- card, hairline, --ink2
     glyph. min-height, not height: "Shelf count · 12 · 3 priced" wraps in a
     half-width cell on a small phone rather than running out of it. */
  .xgrid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:0 0 24px}
  .xbtn{min-height:48px;padding:6px 14px;display:flex;align-items:center;gap:9px;text-align:left;
    border:1px solid var(--line);border-radius:var(--r-control);background:var(--card);color:var(--ink);
    font-family:inherit;font-size:var(--t-s);font-weight:600;cursor:pointer}
  .xbtn .ti{font-size:18px;color:var(--ink2);flex:0 0 auto}
  /* ==== THE ORDER SCREEN (docs/mockups/order_detail_handoff_2026-09-28) ====
     One column -- hero, lines, details, activity -- gapped by the column
     rather than by `.card`'s own margin, so nothing can touch; the actions in
     a dock the shape of #log-dock, above the tab bar. Tokens only, so the dark
     theme needs nothing of its own beyond the dock's glass. */
  .od-stack{display:flex;flex-direction:column;gap:12px}
  .od-stack .card{margin-bottom:0}
  #od-details:empty{display:none}
  #v-orderdetail.docked{padding-bottom:84px}
  #od-dock{position:absolute;left:0;right:0;bottom:calc(64px + env(safe-area-inset-bottom, 8px));
    display:flex;gap:10px;padding:10px 14px 12px;
    background:rgba(245,244,241,.94);
    background:color-mix(in srgb, var(--bg) 94%, transparent);
    backdrop-filter:blur(10px);border-top:1px solid var(--line);z-index:29}
  :root[data-theme='dark'] #od-dock{background:rgba(23,22,19,.94);
    background:color-mix(in srgb, var(--bg) 94%, transparent)}
  body.ov-open #od-dock{display:none}
  #od-dock .btn{flex:1 1 auto;width:auto;min-width:0}
  #od-dock .btn.od-icon{flex:0 0 48px}
  #od-dock .od-icon .ti{font-size:20px}
  /* The status pill sits on the nav's sub line, before the venue (see the
     markup for why not the right edge). The title keeps the whole row; the
     venue yields first, and at 320px the longest word (`Out for delivery`)
     is what ellipsises -- never the order number. Every other screen keeps
     its wrap. */
  .nav.od>div:has(#navTitle){min-width:0;display:grid;grid-template-columns:auto minmax(0,1fr);
    column-gap:6px;align-items:center}
  .nav.od #navTitle{grid-column:1 / -1}
  .nav.od #navTitle,.nav.od #navSub{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .nav.od #navSub{grid-column:2}
  #navPill{grid-column:1;min-width:0;overflow:hidden;line-height:1}
  #navPill .pill{display:inline-block;max-width:100%;white-space:nowrap;
    overflow:hidden;text-overflow:ellipsis;vertical-align:middle}
  .od-hero{padding:16px 14px 14px;display:flex;flex-direction:column;gap:10px}
  .od-hero.hot{border-color:var(--hot-line)}
  .od-hero.warm{border-color:var(--warm-line)}
  /* THE FIGURE KEEPS ITS SIZE; THE META MOVES. The total is the screen's one
     --t-xxl figure, so where it and the meta cannot share a row the meta drops
     under it and keeps its right edge (.numstrip's answer), and the short-ship's
     `of` drops under the total before it leaves the card. Wraps on content,
     not a breakpoint: beside its meta $612.00 needs 282px (a 320px phone's
     hero has 262), $12,480.00 needs 342 (a 375px phone's has 317), and a
     short-ship 417 (the mock's own 390px phone has 332). */
  .od-top{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:flex-end;gap:4px 12px}
  .od-money{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 8px;min-width:0}
  .od-total{font-size:var(--t-xxl);line-height:var(--lh-xxl);font-weight:600;letter-spacing:-.01em;color:var(--ink)}
  .od-total.void{color:var(--ink3);text-decoration:line-through;text-decoration-thickness:2px;
    text-decoration-color:var(--hot)}
  .od-of{font-size:var(--t-s);color:var(--ink3);white-space:nowrap}
  .od-meta{flex:0 0 auto;margin-left:auto;text-align:right;font-size:var(--t-xs);line-height:1.35;color:var(--ink3)}
  .od-say{font-size:var(--t-l);line-height:var(--lh-l);font-weight:600;color:var(--ink);text-wrap:pretty}
  .od-quote{font-size:var(--t-s);line-height:1.45;color:var(--ink2);text-wrap:pretty}
  .od-notice{display:flex;align-items:center;gap:7px;padding:7px 10px;border-radius:var(--r-control);
    background:var(--warm-bg);border:1px solid var(--warm-line);color:var(--warm);
    font-size:var(--t-xs);line-height:1.35}
  .od-notice .ti{font-size:15px}
  .od-party{font-size:var(--t-xs);line-height:1.45;color:var(--ink3)}
  .od-party b{color:var(--ink2);font-weight:600}
  /* Equal fifths wherever the words fit (from ~370px); at 320px a step takes
     the width its word needs. `min-width:0` let "Invoiced" (58px at 12px
     mono) sit in a 48px column and run into its neighbours. Same rule as
     .dock-extra: the words are the point, and 12px is the floor. */
  .od-rail{display:flex;align-items:flex-start;gap:6px;padding:6px 0 2px}
  .od-step{flex:1 1 0;min-width:min-content;display:flex;flex-direction:column;align-items:center;gap:6px;
    font-family:var(--font-mono);font-size:var(--t-xs);color:var(--ink3)}
  .od-bar{height:3px;width:100%;background:var(--mute-line)}
  .od-step:first-child .od-bar{border-radius:2px 0 0 2px}
  .od-step:last-child .od-bar{border-radius:0 2px 2px 0}
  .od-step.done{color:var(--ok)} .od-step.done .od-bar{background:var(--ok)}
  .od-step.short{color:var(--warm)} .od-step.short .od-bar{background:var(--warm)}
  .od-step.cur{font-weight:600}
  .od-rows{padding:4px 14px}
  .od-rows .row{gap:16px}
  .od-rows .row.od-top-row{align-items:flex-start}
  .od-rows .row .lab{flex:0 0 auto;gap:6px}
  .od-rows .row .lab .ti{font-size:16px;color:var(--ink3)}
  .od-rows .row .val{min-width:0;text-align:right}
  .od-vals{display:flex;flex-direction:column;align-items:flex-end;gap:2px}
  .od-sub{font-size:var(--t-xs);font-weight:400;color:var(--ink3)}
  .od-soft{color:var(--ink3);font-weight:400}
  .od-wrap{text-wrap:pretty}
  .od-link{display:inline-flex;align-items:center;gap:6px;border:none;background:none;padding:0;
    font:inherit;font-weight:600;color:var(--accent);cursor:pointer}
  .od-head{display:flex;justify-content:space-between;align-items:baseline;gap:10px;margin:0 0 6px}
  .od-head .sechead{margin:0}
  .od-ctx{font-family:var(--font-mono);font-size:var(--t-xs);color:var(--ink3);white-space:nowrap}
  .od-line{display:grid;grid-template-columns:38px minmax(0,1fr) auto;gap:2px 10px;align-items:baseline;
    padding:9px 0;border-bottom:1px solid var(--line-soft)}
  .od-line:last-child{border-bottom:none}
  .od-q{font-size:var(--t-s);font-weight:600;text-align:right}
  .od-name{font-size:var(--t-s);font-weight:600;color:var(--ink);overflow-wrap:anywhere}
  .od-amt{font-size:var(--t-s);font-weight:600;color:var(--ink)}
  .od-lmeta{font-size:var(--t-xs);color:var(--ink3)}
  .od-deal{color:var(--ok);font-weight:600}
  .od-disc{font-size:var(--t-xs);color:var(--ok);text-align:right}
  .od-line.short{background:var(--warm-wash);margin:0 -14px;padding:9px 14px}
  .od-line.short .od-q,.od-line.short .od-amt{color:var(--warm)}
  .od-line.short .od-lmeta{color:var(--warm);font-weight:600}
  .od-lines.gone .od-q,.od-lines.gone .od-name,.od-lines.gone .od-amt{color:var(--ink3)}
  .od-foot{border-top:1px solid var(--line2);margin-top:4px;padding-top:9px;display:flex;flex-direction:column;
    gap:4px;font-size:var(--t-s);color:var(--ink2)}
  .od-foot>div{display:flex;justify-content:space-between;gap:10px}
  .od-foot .tot{font-weight:600;color:var(--ink)}
  .od-act{display:flex;gap:10px;padding:6px 0}
  .od-dot{width:8px;height:8px;border-radius:50%;margin-top:6px;flex:0 0 auto;background:var(--mute-line)}
  .od-verb{font-size:var(--t-s);font-weight:500;color:var(--ink)}
  .od-verb span{font-weight:400;color:var(--ink2)}
  .od-when{font-size:var(--t-xs);color:var(--ink3);margin-top:1px}
  .od-morebtn{margin-top:6px;display:inline-flex;white-space:nowrap;border:1px solid var(--line);
    background:var(--card);color:var(--ink2);border-radius:var(--r-control);padding:6px 12px;
    font-family:inherit;font-size:var(--t-xs);font-weight:600;cursor:pointer}
  /* 12a: the full-screen shelf count (a screen, not a section, past ~8 rows)
     and the sheet-up entrance both sheets share. */
  @keyframes sheetUp{from{transform:translateY(24px);opacity:0}to{transform:translateY(0);opacity:1}}
  @media (prefers-reduced-motion:reduce){#count-screen,#filing-sheet .hs-card,#log-receipt>div{animation:none !important}}
  #count-screen{position:absolute;inset:0;background:var(--bg);z-index:45;display:none;
    flex-direction:column;animation:sheetUp .15s ease-out both}
  #count-screen.open{display:flex}
  #count-dock{position:absolute;left:0;right:0;bottom:0;padding:10px 14px calc(14px + env(safe-area-inset-bottom, 8px));
    background:rgba(245,244,241,.95);background:color-mix(in srgb, var(--bg) 95%, transparent);
    backdrop-filter:blur(10px);border-top:1px solid var(--line)}
  #log-receipt{position:absolute;inset:0;background:rgba(20,18,15,.5);z-index:50;display:none;
    align-items:center;justify-content:center;padding:24px}
  #log-receipt.open{display:flex}
  #log-receipt>div{animation:sheetUp .15s ease-out both}
  /* AUTO COLUMNS, not repeat(2,1fr): gating can hide a cell, and an auto flow
     lets the survivor take the whole width instead of leaving a hole where
     its sibling was. Two cells, not three — Email is a header chip now, and
     the freed width is what lets Tasting say "Book a tasting". */
  /* THREE CELLS SINCE 2026-09-28 -- Order moved up beside the tastings -- and
     `minmax(max-content,1fr)` rather than `1fr`: equal thirds wherever the
     words fit (390px and up), and on a 320px phone "Book a tasting" takes the
     width it needs instead of being clipped. The words are the point of this
     row; a clipped one is the icon it replaced. */
  .dock-extra{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(max-content,1fr);gap:8px;margin-bottom:10px}
  /* FILLED, NOT BORDERED (refresh rule 3): a secondary control is a
     --chip-bg block. A border is for a field you type into. */
  .dockbtn{display:flex;align-items:center;justify-content:center;gap:6px;height:44px;padding:0 10px;
    border-radius:var(--r-control);background:var(--chip-bg);border:none;font-size:var(--t-s);font-weight:600;
    color:var(--ink);white-space:nowrap;cursor:pointer;font-family:inherit}
  /* THE ICONS LOSING --accent IS THE POINT: on 10a they were accent glyphs,
     which made a once-a-week decision as loud as the reflex below it. (The row
     carries no icons at all since 2026-09-28; the rule stays for any that
     come back.) */
  .dockbtn .ti{font-size:17px;color:var(--ink2)}
  .dock-main{display:flex;gap:10px}
  /* The ONLY accent-filled element on the screen. */
  #dock-log{flex:1;height:52px;border-radius:var(--r-control);font-size:var(--t-m);width:auto}
  #dock-log .ti{font-size:20px}
  /* The Order cell: the same chip as its two neighbours. Its own class
     because it has the one state they do not -- barred, below. */
  .dock-order{display:flex;align-items:center;justify-content:center;gap:6px;height:44px;padding:0 10px;
    border-radius:var(--r-control);border:none;background:var(--chip-bg);color:var(--ink);white-space:nowrap;
    font-size:var(--t-s);font-weight:600;cursor:pointer;font-family:inherit}
  .dock-order .ti{font-size:17px}
  /* SALES BARRED: the ILCC block stops the action AT the action. Inert, not
     hidden — a rep needs to know the door exists and why they cannot sell. */
  .dock-order.barred{background:var(--hot-bg);color:var(--hot);cursor:default}
  /* ==== FOLLOW-UPS (15a, 2026-08-28) ======================================
     15a gave this screen a header block of its own, #rem-hero -- the title at
     --t-xl and the count line under it -- because the nav could only say
     both at 17px and 12px. The refresh (2026-09-28) made the nav THAT header
     on every screen, so #rem-hero went and the count line is the nav's
     subtitle (`titles.follow[2]`, painted by _fuRenderHero). Follow-ups now
     reads like the other tabs: title and Menu on one row, the count beneath.
     The count line's own rules are below: only the FIGURES go mono, the late
     one in --hot behind a dot -- same rule as #acc-hero .hstat b. */
  .nav .sub .fu-n{font-family:var(--font-mono);font-weight:600;color:var(--ink2)}
  .nav .sub .fu-n.hot{color:var(--hot)}
  .nav .sub .fu-dot{display:inline-block;width:8px;height:8px;border-radius:50%;
    background:var(--hot);margin-right:6px;vertical-align:0}
  /* The ⋯ sheet and the add-form sheet. Same shell as #hours-sheet. */
  #followup-sheet,#newfollowup-sheet{position:fixed;inset:0;background:rgba(20,18,15,.44);
    z-index:120;display:none;align-items:flex-end;justify-content:center}
  #followup-sheet.open,#newfollowup-sheet.open{display:flex}
  #followup-sheet .hs-card,#newfollowup-sheet .hs-card{background:var(--card);
    border-radius:var(--r-card) var(--r-card) 0 0;max-width:480px;width:100%;box-sizing:border-box;
    max-height:88%;overflow-y:auto;box-shadow:0 -10px 40px rgba(0,0,0,.22);
    padding:10px 16px calc(26px + env(safe-area-inset-bottom, 0px));
    animation:sheetUp .15s ease-out both}
  /* Store-hours sheet. Same shell as the other bottom sheets, spec radii. */
  #hours-sheet{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:120;display:none;
    align-items:flex-end;justify-content:center}
  #hours-sheet.open{display:flex}
  #hours-sheet .hs-card{background:var(--card);border-radius:var(--r-card) var(--r-card) 0 0;max-width:480px;width:100%;
    max-height:80vh;overflow-y:auto;box-shadow:0 -10px 40px rgba(0,0,0,.22);
    padding:10px 16px calc(26px + env(safe-area-inset-bottom, 0px));animation:sheetUp .15s ease-out both}
  .hours-grab{width:38px;height:4px;border-radius:var(--r-pill);background:var(--line);margin:0 auto 16px}
  .hours-week{display:grid;grid-template-columns:auto 1fr;gap:0 16px;font-size:var(--t-s)}
  .hours-week .hd{padding:6px 0;color:var(--ink2)}
  .hours-week .hv{padding:6px 0;color:var(--ink);font-family:var(--font-mono);text-align:right}
  .hours-week .today{color:var(--accent);font-weight:600}
  .hours-week .shut{color:var(--ink3)}
  /* ==== BUYERS TAB (11a re-layout, 2026-08-28) =============================
     Person cards, the per-person hours sheet and the person sheet. Same sheet
     shell as #hours-sheet; the two NEW sheets deliberately do NOT register
     with the overlay stack (Drew's decision) — back leaves the account, and
     the sheets close only via Cancel / backdrop / Save / Remove. */
  #buyhours-sheet,#person-sheet{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:120;
    display:none;align-items:flex-end;justify-content:center}
  #buyhours-sheet.open,#person-sheet.open{display:flex}
  #buyhours-sheet .hs-card,#person-sheet .hs-card{background:var(--card);border-radius:var(--r-card) var(--r-card) 0 0;
    max-width:480px;width:100%;box-sizing:border-box;max-height:88%;overflow-y:auto;scrollbar-width:none;
    box-shadow:0 -10px 40px rgba(0,0,0,.22);
    padding:10px 16px calc(26px + env(safe-area-inset-bottom, 0px));animation:sheetUp .15s ease-out both}
  #buyhours-sheet .hs-card::-webkit-scrollbar,#person-sheet .hs-card::-webkit-scrollbar{display:none}
  /* Person card. Spec: radius 14, padding 14, 10px apart. */
  .btc{background:var(--card);border:1px solid var(--line);border-radius:var(--r-card);padding:14px;margin-bottom:10px}
  .btc-head{display:flex;align-items:flex-start;gap:8px;cursor:pointer}
  .btc-name{font-size:var(--t-m);font-weight:600;color:var(--ink)}
  .btc-chip{display:inline-flex;align-items:center;gap:4px;font-size:var(--t-xs);font-weight:600;
    border-radius:var(--r-pill);padding:3px 9px;font-family:var(--font-mono);vertical-align:2px;white-space:nowrap}
  .btc-sub{font-size:var(--t-s);color:var(--ink3);margin-top:2px}
  .btc-edit{margin-left:auto;flex:0 0 auto;font-size:var(--t-s);font-weight:600;color:var(--accent);
    padding:2px 0 2px 10px;cursor:pointer;white-space:nowrap}
  .btc-btns{display:flex;gap:8px;margin-top:12px}
  .btc-btn{display:flex;align-items:center;justify-content:center;gap:6px;flex:1;height:46px;
    border-radius:var(--r-control);border:none;background:var(--chip-bg);color:var(--ink);
    font-size:var(--t-s);font-weight:600;cursor:pointer;font-family:inherit}
  .btc-btn.pref{flex:1.25;background:var(--accent);color:var(--on-fill)}
  .btc-hlab{font-size:var(--t-xs);font-weight:600;color:var(--ink3);margin-top:14px}
  .btc-hrow{display:flex;align-items:center;gap:8px;padding:10px 0;border-bottom:1px solid var(--line-soft)}
  .btc-hrow:last-of-type{border-bottom:none}
  .btc-hrow .w{font-size:var(--t-s);font-weight:500;color:var(--ink)}
  .btc-hrow .p{font-size:var(--t-xs);color:var(--ink3);margin-top:1px}
  /* `Change` for windows, `Edit` for the person — 40px tap area per spec. */
  .btc-change{margin-left:auto;flex:0 0 auto;min-height:40px;display:flex;align-items:center;
    color:var(--accent);font-size:var(--t-s);font-weight:600;cursor:pointer;padding-left:10px}
  .btc-add{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;height:46px;
    margin-top:10px;border-radius:var(--r-control);border:none;background:var(--accent-bg);
    color:var(--accent);font-size:var(--t-s);font-weight:600;cursor:pointer;font-family:inherit}
  .btc-note{display:flex;gap:8px;align-items:flex-start;background:var(--bg);border-radius:var(--r-control);
    padding:10px 12px;margin-top:12px;font-size:var(--t-xs);color:var(--ink2);cursor:pointer}
  .bt-addrow{display:flex;align-items:center;justify-content:center;gap:7px;width:100%;height:48px;
    border-radius:var(--r-control);border:none;background:var(--chip-bg);color:var(--accent);
    font-size:var(--t-s);font-weight:600;cursor:pointer;font-family:inherit;margin-bottom:10px}
  .bt-more{display:flex;align-items:center;gap:10px;background:var(--card);border:1px solid var(--line);
    border-radius:var(--r-card);padding:13px 14px;margin-bottom:10px;cursor:pointer}
  .bt-hide{display:block;width:100%;text-align:center;border:none;background:none;color:var(--accent);
    font-size:var(--t-s);font-weight:600;padding:10px 0;cursor:pointer;font-family:inherit;min-height:44px}
  /* Hours sheet internals. Day chips reuse .dc, sized to spec inside the sheet. */
  #bh-sheet-body .dc{flex:1;min-width:0;height:46px;padding:0;border-radius:var(--r-control)}
  .bhs-days{display:flex;gap:5px}
  .bhs-link{display:inline-block;border:none;background:none;color:var(--accent);font-size:var(--t-s);
    font-weight:600;padding:9px 2px;cursor:pointer;font-family:inherit;min-height:44px}
  .bhs-wrap{display:flex;flex-wrap:wrap;gap:6px}
  .bhs-preset{display:flex;align-items:center;justify-content:center;gap:7px;height:46px;
    flex:1 1 calc(50% - 3px);border-radius:var(--r-control);border:none;background:var(--chip-bg);
    color:var(--ink2);font-size:var(--t-s);font-weight:600;cursor:pointer;font-family:inherit}
  .bhs-preset.full{flex-basis:100%}
  .bhs-preset.on{background:var(--accent-bg);color:var(--accent)}
  .bhs-preset .rng{font-family:var(--font-mono);font-weight:600;font-size:var(--t-xs)}
  .bhs-times{display:flex;gap:8px;margin-top:8px}
  .bhs-time{display:flex;align-items:center;justify-content:space-between;gap:6px;flex:1;height:46px;
    border-radius:var(--r-control);border:1px solid var(--line);background:var(--card);padding:0 12px;
    font-family:var(--font-mono);font-size:var(--t-m);color:var(--ink);cursor:pointer}
  .bhs-time.on{border:1.5px solid var(--accent)}
  .bhs-time i{color:var(--accent)}
  .bhs-gridcard{border:1px solid var(--line);border-radius:var(--r-control);padding:8px;margin:8px 0 10px}
  .bhs-gridhead{font-size:var(--t-xs);color:var(--ink3);margin:2px 2px 6px}
  .bhs-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;max-height:148px;
    overflow-y:auto;scrollbar-width:none}
  .bhs-grid::-webkit-scrollbar{display:none}
  .bhs-grid button{height:42px;border-radius:var(--r-control);border:none;background:var(--chip-bg);
    color:var(--ink);font-family:var(--font-mono);font-size:var(--t-s);font-weight:600;cursor:pointer}
  .bhs-grid button.on{background:var(--accent-bg);color:var(--accent)}
  .bhs-ctx{font-size:var(--t-xs);color:var(--ink3);margin-top:10px}
  .bhs-callout{display:flex;gap:9px;align-items:flex-start;background:var(--accent-bg);
    border-radius:var(--r-control);padding:11px 13px;margin-top:12px}
  .bhs-callout i{font-size:var(--t-m);color:var(--accent);flex:0 0 auto;margin-top:1px}
  .bhs-callout span{font-size:var(--t-s);font-weight:500;color:var(--ink)}
  /* The commit action stays visible as content grows (spec: sticky footer). */
  .bhs-foot{position:sticky;bottom:-26px;background:var(--card);padding:10px 0 26px;margin-bottom:-26px}
  .bhs-footrow{display:flex;gap:8px}
  .bhs-footrow .btn{height:50px;border-radius:var(--r-control)}
  .bhs-footrow .cancel{flex:0 0 96px;background:var(--chip-bg);color:var(--ink2)}
  /* Person sheet: the main-buyer checkbox row. */
  .ps-primary{display:flex;gap:10px;align-items:flex-start;border:1px solid var(--line);
    border-radius:var(--r-control);padding:11px 12px;margin-bottom:12px;cursor:pointer}
  .ps-primary .tickbox{margin-top:1px}
  .ps-primary b{display:block;font-size:var(--t-s);font-weight:600;color:var(--ink)}
  .ps-primary span{font-size:var(--t-xs);color:var(--ink3)}
  /* ==== /buyers tab ======================================================== */

  /* Nav offline chip: only when the outbox holds something (10a). */
  #navOffline{display:none;align-items:center;gap:5px;font-size:var(--t-xs);font-weight:600;padding:5px 10px;
    border-radius:var(--r-pill);background:var(--warm-bg);color:var(--warm);white-space:nowrap}
  @keyframes briefIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
  @keyframes sheetUp{from{transform:translateY(24px);opacity:.6}to{transform:none;opacity:1}}
  @media (prefers-reduced-motion:reduce){.outblock,#hours-sheet .hs-card,
    #buyhours-sheet .hs-card,#person-sheet .hs-card{animation:none}}
  /* ==== /account screen ==================================================== */

  /* ==== ACCOUNTS LIST (13a, 2026-08-28) ====================================
   * The screen a rep opens twenty times a day to find ONE door. It answers, in
   * order: WHICH DOOR, IS ANYTHING WRONG WITH IT, HOW FAR. Everything here is
   * in service of narrowing 42 rows to 1.
   *
   * What the cards cost, and why they are gone: a 16px card border drew 42
   * rectangles and grouped nothing, while `padding:14px` + `margin-bottom:12px`
   * spent the space that should have SEPARATED rows on outlining them. One
   * sheet with hairline dividers gives the space back to the separation. */

  /* THE HEADER'S SECOND HALF (2026-09-28): the view selects and the search
     row on the nav's own --card ground, full-bleed out of .body's 14px gutter
     (the full-bleed move 15a's #rem-hero made), with the header's one hairline under it. The nav
     drops its rule and bottom padding on this screen (`.nav.acctlist`) so the
     title, the count and the search read as one block. */
  .al-headblock{background:var(--card);border-bottom:1px solid var(--line);
    margin:-14px -14px 0;padding:14px 16px 12px}
  .nav.acctlist{border-bottom:none;padding-bottom:0}
  /* The count in the subtitle -- the one figure, so the one mono thing. */
  .nav .sub .al-n{font-family:var(--font-mono);font-weight:600;color:var(--ink2)}
  /* The log's subtitle says where the rep is against the pin (2026-09-28):
     at it in --ok (verified is done), away from it in --ink3 -- a fact, not a
     warning, because phoning a buyer from the car is not a mistake. */
  .nav .sub .ls-pin{color:var(--ok);font-weight:600}
  .nav .sub .ls-far{color:var(--ink3)}
  .nav .sub .ls-pin .ti,.nav .sub .ls-far .ti{font-size:14px;vertical-align:-1px}
  /* Search row: the field, then the filter button that owns every facet. */
  .al-searchrow{display:flex;gap:8px;margin:0 0 10px}
  .al-headblock .al-searchrow{margin:0}
  /* FILLED WITH THE GROUND, NOT BORDERED (2026-09-28). On the header's --card
     a --bg field is visibly a well to type into, without a border to say so --
     the refresh keeps bordered white for fields on the ground, and this one
     sits on the header. 44px, the tap floor, and the control radius. */
  .al-searchrow .search{flex:1;height:44px;margin-bottom:0;padding:0 12px;
    background:var(--bg);border:none;border-radius:var(--r-control)}
  .al-searchrow .search .ti{font-size:17px}
  /* LABELLED (2026-09-02): a bare `ti-list` in a square is the glyph a list
     uses for "view", and a rep who does not know it narrows the book does not
     tap it. The same well as the field beside it since 2026-09-28 -- filled,
     not bordered -- so the pair reads as one row of controls. */
  .al-filt{position:relative;flex:0 0 auto;height:44px;border-radius:var(--r-control);padding:0 13px;gap:6px;
    border:none;background:var(--bg);color:var(--ink2);
    display:flex;align-items:center;justify-content:center;cursor:pointer;font-family:inherit;
    font-size:var(--t-s);font-weight:600;white-space:nowrap}
  .al-filt .ti{font-size:18px}
  /* FILTERING is a different object, not the same object greyed: the accent
     tint, the one "this is ON" treatment the refresh keeps for lit controls. */
  .al-filt.on{background:var(--accent-bg);color:var(--accent)}
  /* The badge counts NARROWING FACETS, never values — see narrowingFacets().
     The 2px card-coloured ring is what makes it read as a badge stuck to the
     button rather than a glyph inside it (the button sits on --card now). */
  .al-badge{position:absolute;top:-7px;right:-7px;min-width:18px;height:18px;border-radius:var(--r-pill);
    background:var(--accent);color:var(--on-fill);border:2px solid var(--card);
    font-family:var(--font-mono);font-size:var(--t-xs);font-weight:600;line-height:14px;
    display:none;align-items:center;justify-content:center;padding:0 4px}
  .al-filt.on .al-badge{display:flex}

  /* Active-filter chips. Rendered ONLY while something narrows, so a list that
     is not narrowed spends no vertical space saying so. */
  .al-chips{display:flex;gap:6px;margin:12px 0 0;overflow-x:auto;scrollbar-width:none;
    flex-wrap:nowrap;-webkit-overflow-scrolling:touch}
  .al-chips::-webkit-scrollbar{display:none}
  .al-chip{display:inline-flex;align-items:center;gap:5px;flex:0 0 auto;height:32px;border-radius:var(--r-pill);
    background:var(--accent-bg);color:var(--accent);font-size:var(--t-s);font-weight:600;
    padding:0 8px 0 11px;border:none;cursor:pointer;font-family:inherit;white-space:nowrap}
  .al-chip .ti{font-size:15px}
  .al-chip.clear{background:transparent;color:var(--ink3);padding:0 10px}

  /* THE SORT ROW (2026-09-28): three chips on the left, the narrowed count on
     the right. The live chip is INK on the ground -- the strongest pair short
     of the accent, which stays reserved for the one thing to press -- and the
     others are --chip-bg: a choice that always has a value reads as a set of
     options with one taken, not as a label to decode. */
  .al-head{display:flex;justify-content:space-between;align-items:center;gap:8px;margin:10px 2px 8px}
  .al-head .cnt{font-size:var(--t-xs);color:var(--ink3);white-space:nowrap}
  .al-sorts{display:flex;gap:6px}
  .al-sortchip{height:32px;padding:0 12px;border:none;border-radius:var(--r-pill);
    background:var(--chip-bg);color:var(--ink2);font-family:inherit;font-size:var(--t-xs);
    font-weight:600;white-space:nowrap;cursor:pointer}
  .al-sortchip.on{background:var(--ink);color:var(--bg)}

  /* THE SHEET. One background, hairline dividers, no per-row rectangle --
     full-bleed since 2026-09-28, so the rows' own 16px is the gutter and the
     hairlines run edge to edge, as the header's does above them. */
  .al-sheet{background:var(--card);border-top:1px solid var(--line);
    border-bottom:1px solid var(--line);margin:0 -14px 12px}
  /* 72px, EXACT, and `_renderAccountWindow`'s `est` says so: 13 + 22 name +
     3 + 20 address line + 13, plus the rule. Every line height below is FIXED
     for that reason -- a row that grows by a pixel when it carries a pill is
     the instability 13a's reserved pill line existed to prevent. */
  .al-row{display:flex;align-items:center;gap:12px;padding:13px 16px;cursor:pointer;
    border-bottom:1px solid var(--line-soft)}
  .al-row:last-child{border-bottom:none}
  /* WARMTH, AS A DOT IN ITS OWN COLOUR (2026-09-28), for every reading: hot,
     warm, neutral on --mute-line, cold, and an unrated door on --line2 so the
     column never has a hole. 13a drew a 3px edge for hot and warm only; the
     10px dot before that was retired as "unreachable on touch" -- it was the
     only LABEL then, a `title`, and a dot that labels nothing owes no touch
     target. The filter sheet's warmth chips are where the colours are named. */
  .al-dot{flex:0 0 auto;width:8px;height:8px;border-radius:50%;background:var(--line2)}
  .al-dot.hot{background:var(--hot)}
  .al-dot.warm{background:var(--warm)}
  .al-dot.neutral{background:var(--mute-line)}
  .al-dot.cold{background:var(--cold)}
  .al-mid{flex:1 1 auto;min-width:0}
  .al-name{font-size:var(--t-m);line-height:22px;font-weight:600;color:var(--ink);
    letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  /* A door nobody has been to is not a finished one — it is quieter, not gone. */
  .al-name.never{color:var(--ink3)}
  /* THE ADDRESS LINE CARRIES THE PILL (2026-09-28): after the address, never
     wrapped among it -- `.txt` is the only thing that shrinks, so the pill
     lands at the end of the address and the address ellipsises before it.
     Brand is IDENTITY, not state, so brand chips ride here too, after the
     pill: under a cap of two, state pills would otherwise evict the chips a
     multi-brand rep depends on. */
  .al-ctx{display:flex;align-items:center;gap:8px;height:20px;margin-top:3px;font-size:var(--t-s);
    line-height:20px;color:var(--ink3);min-width:0}
  .al-ctx .txt{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .al-ctx .cochip{flex:0 0 auto;font-size:var(--t-xs);font-weight:600;padding:0 7px;line-height:20px;
    border-radius:var(--r-pill);white-space:nowrap}
  /* THE STATUS PILL (refresh rule 5): sentence case, sans, 12px/600, a 6px
     dot in the pill's own colour, 20px tall so it rides the address line
     without changing its height. The mono capitals it replaces were the
     second-loudest type on the row, for its least urgent fact. */
  .al-pill{flex:0 0 auto;display:inline-flex;align-items:center;gap:5px;font-size:var(--t-xs);font-weight:600;
    line-height:16px;padding:2px 8px;border-radius:var(--r-pill);white-space:nowrap}
  .al-pill::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:0 0 auto}
  /* MONEY IS FILLED, the others are tinted (2026-08-28). This is the only
     pill on the row that reports something ILLEGAL rather than something
     interesting, and as a tint it carried the same weight as ACTIVE sitting
     beside it -- a rep scanning a long book read past it. `--hot` behind
     `--on-fill` is the loudest pair the palette has, and `--on-fill` flips
     with the theme (white on the light red, the ground on the dark one), so
     the fill stays legible where a literal white would not. */
  .al-pill.money{background:var(--hot);color:var(--on-fill)}
  .al-pill.active{background:var(--ok-bg);color:var(--ok)}
  .al-pill.prospect{background:var(--chip-bg);color:var(--ink2)}
  /* A door that went cold (Inactive, 2026-09-26): the palette's own word for it. */
  .al-pill.inactive{background:var(--cold-bg);color:var(--cold)}
  /* THE RIGHT COLUMN (2026-09-28): how far, then how long since. The two
     figures a rep compares DOWN the list, so they line up down the list. */
  .al-end{flex:0 0 auto;text-align:right}
  .al-dist{font-family:var(--font-mono);font-size:var(--t-s);line-height:20px;font-weight:600;
    color:var(--ink);white-space:nowrap}
  .al-rec{font-size:var(--t-xs);line-height:16px;color:var(--ink3);margin-top:3px;white-space:nowrap}

  /* ---- The filter sheet ---------------------------------------------------
   * ONE sheet for narrowing. The drawer it replaces cost vertical space
   * permanently and still never said what was on. */
  #filter-sheet,#more-sheet,#closed-sheet,#opento-sheet,#status-sheet{position:fixed;inset:0;background:rgba(20,18,15,.44);
    z-index:120;display:none;align-items:flex-end;justify-content:center}
  #filter-sheet.open,#more-sheet.open,#closed-sheet.open,#opento-sheet.open,
  #status-sheet.open{display:flex}
  /* ONE STEP ABOVE #detail-sheet (2026-09-27): Plan my day's picker opens this
     same sheet over itself, and at an equal z-index the later element -- the
     plan sheet -- painted over it (found in the browser). */
  #filter-sheet{z-index:121}
  #filter-sheet .hs-card,#more-sheet .hs-card,#closed-sheet .hs-card,#opento-sheet .hs-card,
  #status-sheet .hs-card{
    background:var(--card);border-radius:var(--r-card) var(--r-card) 0 0;max-width:480px;width:100%;
    box-sizing:border-box;max-height:88%;overflow-y:auto;scrollbar-width:none;
    box-shadow:0 -10px 40px rgba(0,0,0,.22);
    padding:10px 16px calc(26px + env(safe-area-inset-bottom, 0px));
    animation:sheetUp .15s ease-out both}
  #filter-sheet .hs-card::-webkit-scrollbar,
  #more-sheet .hs-card::-webkit-scrollbar,#closed-sheet .hs-card::-webkit-scrollbar,
  #status-sheet .hs-card::-webkit-scrollbar,
  #opento-sheet .hs-card::-webkit-scrollbar{display:none}
  .fs-grab{width:38px;height:4px;border-radius:var(--r-pill);background:var(--line);margin:0 auto 16px}
  .fs-title{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:16px}
  .fs-title b{font-size:var(--t-m);font-weight:600;color:var(--ink)}
  .fs-reset{border:none;background:none;color:var(--accent);font-size:var(--t-s);font-weight:600;
    cursor:pointer;font-family:inherit;padding:4px 0 4px 10px}
  .fs-lab{font-family:var(--font-mono);font-size:var(--t-xs);font-weight:600;letter-spacing:.1em;
    color:var(--ink3);text-transform:uppercase;margin:0 0 8px}
  .fs-row{display:flex;gap:6px;margin-bottom:6px}
  .fs-opt{flex:1;min-width:0;height:44px;border-radius:var(--r-control);border:none;
    background:var(--chip-bg);color:var(--ink2);font-size:var(--t-s);font-weight:600;
    cursor:pointer;font-family:inherit;padding:0 6px;white-space:nowrap;overflow:hidden;
    text-overflow:ellipsis}
  .fs-opt.on{background:var(--accent-bg);color:var(--accent)}
  /* A lit warmth chip takes its OWN warmth colour; unlit carries the colour on
     the label only, so the row teaches the palette either way. */
  .fs-opt.w-hot{color:var(--hot)} .fs-opt.w-warm{color:var(--warm)}
  .fs-opt.w-neutral{color:var(--ink2)} .fs-opt.w-cold{color:var(--cold)}
  .fs-opt.w-none{color:var(--ink3)}
  .fs-opt.w-hot.on{background:var(--hot-bg);color:var(--hot)}
  .fs-opt.w-warm.on{background:var(--warm-bg);color:var(--warm)}
  .fs-opt.w-neutral.on{background:var(--line2);color:var(--ink)}
  .fs-opt.w-cold.on{background:var(--cold-bg);color:var(--cold)}
  .fs-opt.w-none.on{background:var(--line2);color:var(--ink2)}
  .fs-cap{font-size:var(--t-xs);color:var(--ink3);line-height:1.45;margin:2px 0 18px}
  .fs-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px}
  /* 52x31 switch, per spec. */
  .fs-sw{position:relative;flex:0 0 52px;width:52px;height:31px;border-radius:var(--r-pill);
    background:var(--line2);border:none;cursor:pointer;transition:background .16s;padding:0}
  .fs-sw:after{content:"";position:absolute;top:3px;left:3px;width:25px;height:25px;border-radius:50%;
    background:var(--card);transition:transform .16s;box-shadow:0 1px 3px rgba(0,0,0,.2)}
  .fs-sw.on{background:var(--accent)}
  .fs-sw.on:after{transform:translateX(21px)}
  .fs-preset{flex:1;height:40px;border-radius:var(--r-control);border:none;background:var(--chip-bg);
    color:var(--ink2);font-family:var(--font-mono);font-size:var(--t-s);font-weight:600;
    cursor:pointer;font-family:var(--font-mono)}
  .fs-preset.on{background:var(--accent-bg);color:var(--accent)}
  .fs-step{display:flex;align-items:center;gap:4px;background:var(--chip-bg);border-radius:var(--r-control);
    padding:4px;margin-top:8px}
  .fs-step button{flex:0 0 46px;width:46px;height:44px;border-radius:calc(var(--r-control) - 4px);border:none;
    background:var(--card);color:var(--accent);display:flex;align-items:center;
    justify-content:center;cursor:pointer;font-family:inherit}
  .fs-step button .ti{font-size:20px}
  .fs-step button:disabled{color:var(--mute-line);cursor:default}
  .fs-stepv{flex:1;text-align:center;font-family:var(--font-mono);font-size:var(--t-l);
    font-weight:600;color:var(--ink)}
  .fs-stepv span{font-family:var(--font-sans);font-size:var(--t-s);font-weight:500;color:var(--ink3)}
  .fs-dim{opacity:.4;pointer-events:none}
  /* The outcome, stated: a rep cannot picture 40 days but can picture 24 doors. */
  .fs-foot{position:sticky;bottom:-26px;background:var(--card);padding:12px 0 26px;margin-bottom:-26px}
  .fs-foot .btn{height:52px;border-radius:var(--r-control);font-size:var(--t-m)}
  .fs-foot .btn:disabled{background:var(--chip-bg);color:var(--ink3);cursor:default}
  /* Sort + overflow sheets: plain rows, 52px, nothing clever. */
  .fs-pick{display:flex;align-items:center;gap:11px;width:100%;min-height:52px;border:none;
    background:none;color:var(--ink);font-size:var(--t-s);font-weight:500;cursor:pointer;
    font-family:inherit;text-align:left;padding:0;border-bottom:1px solid var(--line-soft)}
  .fs-pick:last-of-type{border-bottom:none}
  .fs-pick .ti{font-size:19px;color:var(--ink3);flex:0 0 auto;width:22px}
  .fs-pick.on{color:var(--accent);font-weight:600}
  .fs-pick.on .ti{color:var(--accent)}
  .fs-pick .tick.ti{margin-left:auto;color:var(--accent);font-size:17px}
  /* Two-line pick row (Opens to). `box-sizing` is load-bearing: without it
     min-height resolves against the CONTENT box and the padding inflates the
     row half again. */
  .fs-pick2{align-items:flex-start;min-height:56px;box-sizing:border-box;padding:9px 2px}
  .fs-pick2 .ti{margin-top:1px}
  .fs-pick2 .sub{display:block;font-size:var(--t-xs);color:var(--ink3);font-weight:400;margin-top:2px}
  /* A row that LEAVES (the Admin panel row, mock 1e) says so with a chevron
     where a picked row shows its tick. */
  .fs-pick .chev.ti{margin-left:auto;color:var(--ink3);font-size:17px;width:auto}
  /* Row icons on the two pushed settings screens only. Scoped on purpose --
     `.row .lab` carries icons all over the app at the label's own size, and
     a global rule here would resize every one of them. */
  #v-settings .row .lab>.ti,#v-profile .row .lab>.ti{font-size:17px;color:var(--ink3);
    flex:0 0 auto;width:20px;text-align:center}
  .avatar{border-radius:50%;background:var(--accent-bg);color:var(--accent);flex:0 0 auto;
    display:flex;align-items:center;justify-content:center;font-weight:600}
  @media (prefers-reduced-motion:reduce){#filter-sheet .hs-card,
    #more-sheet .hs-card,#status-sheet .hs-card{animation:none}}
  /* ==== /accounts list ===================================================== */

  /* ---- Dark theme (rep app). Auto via prefers-color-scheme; the nav toggle
     overrides + persists. data-theme is stamped on <html> by an early inline
     script (below) so there's no flash of light. Most surfaces already use the
     CSS variables and adapt for free; only the handful of hardcoded structural
     colors are re-stated here. The admin panel stays light. ---- */
  /* The dark VALUES live in vendor/tokens.css with every other token. What
     stays here is the one thing that is not a token: `color-scheme` tells the
     browser to draw its own controls -- select popups, date pickers,
     scrollbars -- dark, and it belongs to the page rather than to the palette
     (the panel deliberately does not set it). */
  :root[data-theme='dark']{ color-scheme:dark }
  /* Bare <select>/<input> (no .txt class) default to white — theme them too. */
  :root[data-theme='dark'] select,
  :root[data-theme='dark'] input,
  :root[data-theme='dark'] textarea{background-color:var(--card);color:var(--ink);border-color:var(--line)}
  /* Rule 4: the warm cast carries across. This was #0c0e11, a cool near-black.
     Rule 3 as well -- the shell sits BELOW the ground, so it is darker by
     lightness rather than by a shadow. */
  :root[data-theme='dark'] body{background:#100f0d}
  :root[data-theme='dark'] .screen{box-shadow:none;border-color:var(--line)}
  :root[data-theme='dark'] .tabbar{background:rgba(32,31,27,.92)}
  :root[data-theme='dark'] .skel{background:var(--chip-bg)}
  :root[data-theme='dark'] .skel::after{background:linear-gradient(90deg,transparent,rgba(255,255,255,.06),transparent)}
  :root[data-theme='dark'] .seg{background:var(--chip-bg)}
  :root[data-theme='dark'] .seg button.on{background:var(--card)}
  :root[data-theme='dark'] .li .qty,:root[data-theme='dark'] .stepper{background:var(--chip-bg)}
  :root[data-theme='dark'] .stepper button{background:#35322b;color:var(--ink)}
  :root[data-theme='dark'] .dedupe{border-color:var(--warm-line)}
  :root[data-theme='dark'] .tag.beer{background:#352a16;color:#e9a53c}
  :root[data-theme='dark'] .tag.wine{background:#3a2130;color:#e78bab}
  :root[data-theme='dark'] .tag.spirits{background:#262147;color:#a99cf0}
  /* Status pills/chips hardcode dark text for light tints — swap to the brighter
     dark-theme tokens so they stay legible on the dark backgrounds. */
  :root[data-theme='dark'] .pill.warm,:root[data-theme='dark'] .chip.warm.on{color:var(--warm)}
  :root[data-theme='dark'] .pill.cool,:root[data-theme='dark'] .chip.cool.on{color:var(--cool)}
  :root[data-theme='dark'] .ico.o{color:var(--warm)}

  /* Numbers in the mono, and the 11px uppercase labels with them -- see the
     panel's block for the reasoning. `.sechead` is covered by the shared rule
     in vendor/tokens.css; `.pill` is too, and the phone opts back out of it
     (the `.pill` rule above, 2026-09-28): a status pill says a word. */
  .stat .n,.stat .l,.bdow,.bd,.fr-step span,#detail-sheet .dl-v{
    font-family:var(--font-mono);font-variant-numeric:tabular-nums}
  /* ==== PRESSED STATES (APP_SPEED_2026-09-29.md) ============================
   * `-webkit-tap-highlight-color:transparent` on `*` (top of this sheet) took
   * away the browser's own tap feedback, and nothing replaced it: a tap on a
   * slow action looked exactly like a tap that missed. This is the answer to
   * "did it hear me?" and it lands on the finger-down frame, before any
   * handler runs, so it cannot be slowed down by the work the tap starts.
   *
   * THE CLASSES ARE LISTED, NOT INFERRED. `[data-act]:active` would be
   * shorter and wrong: every sheet's backdrop carries a data-act (or an
   * onclick guarded by `event.target===this`), and a press anywhere inside
   * the sheet makes the backdrop :active too -- the whole sheet would dim on
   * every tap. A new tappable component joins one of these two lists.
   *
   * The wash is an INSET SHADOW of the ink colour, so it darkens a light
   * surface and lightens a dark one without replacing any background, and
   * follows border-radius. `app.js` adds a passive document touchstart
   * listener, without which iOS Safari never applies :active at all. */
  :root{--press:color-mix(in srgb, var(--ink) 9%, transparent)}
  button:not(:disabled):active,.btn:active,.chip:active,.nchip:active,.mschip:active,
  .hchip:active,.al-chip:active,.pill:active,.mini:active,.xbtn:active,.dockbtn:active,
  .tab:active,.fab:active,.seg button:active{
    box-shadow:inset 0 0 0 100vmax var(--press);transform:scale(.97)}
  .al-row:active,.acctrow:active,.row2:active,.hrow.tap:active,.tap:active,.hubrow:active,
  .dl-row:active,.pickrow:active,.outrow:active,.tickrow:active,.bs-row:active,.stop:active,
  .dc:active,.olink:active,.card[data-act]:active,.card[onclick]:active{
    box-shadow:inset 0 0 0 100vmax var(--press)}
  /* A row holding its own button: the press belongs to the button. Its own
     rule, because a browser without :has() drops the whole rule it sits in. */
  :is(.al-row,.acctrow,.row2,.tap,.hubrow,.card):has(button:active,.btn:active,.chip:active,.mini:active){
    box-shadow:none}
  @media (prefers-reduced-motion:reduce){
    button:active,.btn:active,.chip:active,.tab:active,.fab:active,.mini:active{transform:none}}
