/* ═══════════════════════════════════════════════════════════════════════════
   QUARTERMASTER — one stylesheet, seven layers, no build step.

   ⚠ READ THE LAYER ORDER BEFORE ADDING ANYTHING.

       @layer reset, tokens, base, layout, components, utilities, overrides;

   Order of DECLARATION decides who wins, regardless of specificity. That single
   line retires the class of bug this file was full of: `.hint` was defined twice
   and the second one won by being further down, which is not a system, it is a
   race. Put a rule in the layer it belongs to and stop counting selectors.

   ⚠ TOKENS ARE TWO LAYERS AND COMPONENTS MAY ONLY USE THE SECOND.
   Primitives hold VALUES (--orange-500). Semantics hold ROLES (--brand). A
   component that reaches past --brand to a hex, or to a primitive, cannot be
   re-themed and will be wrong in one of the three themes. There is no exception.

   ⚠ THE LIGHT THEME IS WARM SEPIA AND STAYS THAT WAY.
   Matt: "keep the nice sepia background (my eyes fair well)". Paper-white at full
   brightness is the commonest cause of eye strain in a tool somebody stares at
   all day. That is an accessibility decision, not a style one.

   ⚠ THREE THEMES, NOT TWO. `prefers-color-scheme` alone cannot be overridden by
   a person, so a laptop set to dark could never show the sepia and vice versa.
   Every palette is written for: the bare :root (light), the media query guarded
   with :not([data-theme="light"]), and the explicit [data-theme=…] stamp.
   ═══════════════════════════════════════════════════════════════════════════ */

@layer reset, tokens, base, layout, components, utilities, overrides;

/* ═══ TOKENS ═══════════════════════════════════════════════════════════════ */
@layer tokens {

  /* ── primitives: values, named after what they ARE ──────────────────────
     Nothing outside this block should ever name a colour literal. */
  :root{
    --orange-400:#ffa63c; --orange-500:#f7941e; --orange-600:#e07c0a; --orange-700:#b45309;
    --orange-050:#fef3e6;

    --warm-000:#ffffff; --warm-050:#faf7f3; --warm-100:#f3ede5; --warm-200:#e6ddd3;
    --warm-600:#6b625a;  --warm-900:#1f1a14;  --warm-950:#1a1206;

    --green-050:#eaf3de; --green-700:#3b6d11;
    --amber-050:#faeeda; --amber-700:#854f0b;
    --red-050:#fcebeb;   --red-700:#a32d2d;

    /* spacing + radius — one scale, so nothing is invented per component */
    --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:22px; --s6:30px; --s7:44px;
    --r-sm:8px; --r-md:11px; --r-lg:14px; --r-pill:999px;

    /* type */
    --font:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,Roboto,sans-serif;
    --mono:ui-monospace,SFMono-Regular,Menlo,monospace;

    /* the gloved minimum this project is built around — MIL-STD-1472H's
       20x20mm, roughly 76px at phone density. WCAG 2.2 AA asks 24px and AAA
       asks 44px; a data hall asks more than either. */
    --tap-lg:76px; --tap-md:64px; --tap-sm:46px;
    --tabbar:0px;              /* phone tab bar height; set in the layout layer */
  }

  /* ── semantics: roles, named after what they DO ─────────────────────────
     LIGHT / SEPIA — the default. */
  :root{
    --brand:var(--orange-500); --brand-hi:var(--orange-600); --bink:var(--orange-700);
    --soft:var(--orange-050);  --onbrand:var(--warm-950);
    --ink:var(--warm-900); --mut:var(--warm-600); --line:var(--warm-200);
    /* ⚠ A CONTROL EDGE IS NOT A TABLE HAIRLINE, and conflating the two is
       why every ghost button looked borderless. --line is a divider at
       1.34:1 — right for separating rows, WRONG for the boundary of
       something clickable, which WCAG 2.2 SC 1.4.11 wants at 3:1. No
       single value clears 3:1 in BOTH themes, so it is its own token. */
    --edge:var(--warm-600);   /* 5.97:1 on the sepia card */
    --panel:var(--warm-050); --card:var(--warm-000); --sunk:var(--warm-100);
    --good-bg:var(--green-050); --good-tx:var(--green-700);
    --warn-bg:var(--amber-050); --warn-tx:var(--amber-700);
    --bad-bg:var(--red-050);    --bad-tx:var(--red-700);
    --bar:var(--warm-950); --bar-tx:rgba(255,255,255,.72);
    --shadow:0 1px 2px rgba(31,26,20,.05), 0 8px 24px rgba(31,26,20,.06);
    /* ⚠ MUST CLEAR 3:1 AGAINST BOTH THE CONTROL AND THE PAGE, in every theme.
       The old "focus ring" was --soft on --panel: #fef3e6 on #faf7f3, about
       1.03:1. That is not a weak indicator, it is the absence of one. */
    --focus:#8a4a00;
  }

  /* DARK — a lifted surface against a darker ground, which is the opposite of
     light mode's white-card-on-tinted-page. The brand also has to move: the
     #f7941e orange on near-black is ~10:1 and glares, so dark gets a softer
     tone and uses it for accents, never large fills. */
  @media (prefers-color-scheme:dark){
    :root:not([data-theme="light"]):not([data-theme="contrast"]){
      --brand:#f0a44e; --brand-hi:#e08c2a; --bink:#f5b96e;
      --soft:#2b2015;  --onbrand:#17120c;
      --ink:#f0eae2; --mut:#a79c90; --line:#37302a; --edge:#8a7e73;  /* 4.46:1 on dark */
      --panel:#12100e; --card:#1c1815; --sunk:#171310;
      --good-bg:#1c2a12; --good-tx:#9ccb5e;
      --warn-bg:#38290f; --warn-tx:#f7c877;
      --bad-bg:#341717;  --bad-tx:#f29a9a;
      --bar:#0d0b09; --bar-tx:rgba(255,255,255,.7);
      --shadow:0 1px 2px rgba(0,0,0,.5), 0 10px 28px rgba(0,0,0,.45);
      --focus:#ffc477;
    }
  }
  :root[data-theme="dark"]{
    --brand:#f0a44e; --brand-hi:#e08c2a; --bink:#f5b96e;
    --soft:#2b2015;  --onbrand:#17120c;
    --ink:#f0eae2; --mut:#a79c90; --line:#37302a; --edge:#8a7e73;  /* 4.46:1 on dark */
    --panel:#12100e; --card:#1c1815; --sunk:#171310;
    --good-bg:#1c2a12; --good-tx:#9ccb5e;
    --warn-bg:#38290f; --warn-tx:#f7c877;
    --bad-bg:#341717;  --bad-tx:#f29a9a;
    --bar:#0d0b09; --bar-tx:rgba(255,255,255,.7);
    --shadow:0 1px 2px rgba(0,0,0,.5), 0 10px 28px rgba(0,0,0,.45);
    --focus:#ffc477;
  }

  /* HIGH CONTRAST — the third theme exists to PROVE the indirection. Two
     themes can be faked with a handful of overrides; three cannot. It also
     earns its keep on its own: a data hall is lit for machines, not eyes. */
  @media (prefers-contrast:more){
    :root:not([data-theme]){
      --mut:#3a332b; --line:#1f1a14; --edge:#1f1a14; --bink:#7a3800;
      --card:#ffffff; --panel:#ffffff; --sunk:#f2efea;
      --shadow:none; --focus:#000000;
    }
  }
  :root[data-theme="contrast"]{
    --mut:#3a332b; --line:#1f1a14; --edge:#1f1a14; --bink:#7a3800;
    --card:#ffffff; --panel:#ffffff; --sunk:#f2efea;
    --shadow:none; --focus:#000000;
  }
}

/* ═══ RESET + BASE ═════════════════════════════════════════════════════════ */
@layer reset {
  *{box-sizing:border-box}
  body{margin:0}
  img{max-width:100%}
}

@layer base {
  body{
    background:var(--panel); color:var(--ink);
    font-family:var(--font); line-height:1.55; -webkit-font-smoothing:antialiased;
  }
  a{color:var(--bink); text-decoration:none}
  a:hover{text-decoration:underline}

  /* ⚠ ONE FOCUS RULE FOR EVERYTHING. Before this, :focus-visible existed on
     exactly one selector in the file and form fields set outline:0 outright —
     so most of the app had no keyboard indicator at all. WCAG 2.2 SC 2.4.7. */
  :focus-visible{outline:3px solid var(--focus); outline-offset:2px; border-radius:3px}

  /* SC 2.4.11 Focus Not Obscured — the fixed phone tab bar covers whatever you
     tab to underneath it. Reserving scroll padding is the whole fix. */
  html{scroll-padding-bottom:calc(var(--tabbar) + var(--s5))}

  @media (prefers-reduced-motion:reduce){
    *,*::before,*::after{
      animation-duration:.01ms!important; animation-iteration-count:1!important;
      transition-duration:.01ms!important; scroll-behavior:auto!important;
    }
  }

  /* ⚠ WINDOWS CONTRAST THEMES STRIP AUTHOR COLOURS ENTIRELY, so anything whose
     boundary is conveyed by background alone becomes invisible. A transparent
     border is the documented fix — it gets repainted by the OS. */
  @media (forced-colors:active){
    .tap-primary,.tap-second,.tap-action,.btn-primary,.btn-second,.pill,.chip{
      border:1px solid transparent;
    }
  }

  h1{font-size:24px; font-weight:600; letter-spacing:-.02em; margin:0 0 var(--s1)}
  h2{
    font-size:12px; letter-spacing:.12em; text-transform:uppercase; color:var(--bink);
    font-weight:600; margin:var(--s6) 0 var(--s3); padding-bottom:7px;
    border-bottom:1px solid var(--line);
  }
  .sub{color:var(--mut); font-size:14.5px; margin:0 0 var(--s5)}
  /* Read by a screen reader, not shown (U15). */
  .sr-only{position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
           clip:rect(0,0,0,0); white-space:nowrap; border:0}
  .cap-h1{font:inherit; margin:0; display:inline}
  .lbl{
    display:block;
    font-size:10.5px; letter-spacing:.11em; text-transform:uppercase; color:var(--mut);
    font-weight:600; margin:0 0 7px;
  }
  .hint{font-size:13.5px; color:var(--mut); line-height:1.5}
  .mono,td.mono{font-family:var(--mono); font-size:13px}
  .prose{max-width:70ch}
  .prose p{margin:0 0 var(--s3)}
}

/* ═══ LAYOUT ═══════════════════════════════════════════════════════════════ */
@layer layout {
  .wrap{max-width:1180px; margin:0 auto; padding:var(--s6) var(--s5) 60px;
        /* iPhone in landscape gets the desk layout; keep content off the island (U7). */
        padding-left:max(var(--s5), env(safe-area-inset-left));
        padding-right:max(var(--s5), env(safe-area-inset-right))}

  /* ── the bar ──────────────────────────────────────────────────────────── */
  .topbar{
    background:var(--bar); border-bottom:3px solid var(--brand);
    display:flex; align-items:center; gap:var(--s5);
    padding:11px var(--s5); position:sticky; top:0; z-index:40;
    /* ⚠ THE STATUS BAR. base.html asks iOS for `black-translucent` and
       `viewport-fit=cover`, so an app opened from the home screen draws UNDER
       the clock and the Dynamic Island. Without this the bar's buttons sat
       beneath them and could not be tapped (Matt, iPhone 18 Pro Max,
       2026-09-30). The bar's own colour fills the gap, which is the look the
       translucent style was chosen for. Sides too, for landscape. */
    padding-top:calc(11px + env(safe-area-inset-top));
    padding-left:max(var(--s5), env(safe-area-inset-left));
    padding-right:max(var(--s5), env(safe-area-inset-right));
  }
  .brand{display:flex; align-items:center; gap:11px; color:#fff; flex:none}
  .brand:hover{text-decoration:none}
  .brand img{width:82px; height:27px; display:block}
  .brand span{font-size:15px; font-weight:600; letter-spacing:-.01em}

  /* ⚠ THE BAR NEVER PUSHES THE PAGE SIDEWAYS (REVIEW-2026-09-30, U7). With the
     Find box added it measured 1130px wide at 1024px and below, Sign out off the
     right edge. The nav may shrink and scroll inside itself; the rest condenses. */
  .nav{display:flex; gap:3px; flex:1; align-items:center; min-width:0; overflow-x:auto;
       scrollbar-width:none}
  .nav::-webkit-scrollbar{display:none}
  .nav a,.nav summary{
    color:var(--bar-tx); padding:var(--s2) 14px; border-radius:9px;
    font-size:14px; font-weight:500; white-space:nowrap;
  }
  .nav a:hover,.nav summary:hover{background:rgba(255,255,255,.10); color:#fff; text-decoration:none}
  .nav a[aria-current="page"],.nav a.on{background:var(--brand); color:var(--onbrand); font-weight:600}

  .who{display:flex; align-items:center; gap:9px; flex:none}
  /* the one search box, in the bar — desk only; the phone has the Find tab.
     ⚠ ONLY ITS BOX IS HERE. The INPUT is styled in the components layer, and
     that is not a preference — see the note beside it. */
  .barfind{margin:0 0 0 auto; flex:none}
  .who .user{color:rgba(255,255,255,.72); font-size:12px; text-decoration:none}
  .who a.user:hover{color:#fff; text-decoration:underline}
  .who form{margin:0}

  .tabbar{display:none}

  @media(max-width:1240px) and (min-width:821px){
    .topbar{gap:var(--s3)}
    .brand span{display:none}
    .nav a,.nav summary{padding:8px 10px}
    .barfind input[type=search]{width:150px}
  }
  @media(max-width:1000px) and (min-width:821px){
    .who .user{display:none}
  }
  /* Focus on the dark bars: the page's --focus measured 2.70:1 there (U12). */
  .topbar,.tabbar{--focus:#ffc477}

  @media(max-width:820px){
    /* On a phone the top bar stops being navigation and becomes identity. The
       top of a phone is unreachable one-handed and this app is used standing at
       a rack holding an optic. */
    .topbar{gap:var(--s3); padding:calc(9px + env(safe-area-inset-top))
            max(14px, env(safe-area-inset-right)) 9px max(14px, env(safe-area-inset-left))}
    .brand img{width:70px; height:23px}
    .nav,.who .user,.barfind{display:none}
    /* The app's name stays on the phone (UX-REVIEW-2026-09-08). */
    .brand span{font-size:13px}
    /* One theme button on the phone: the current one; pressing it moves on. */
    .topbar .theme button[aria-pressed="false"]{display:none}
    .wrap{padding:18px 14px var(--s6)}

    :root{--tabbar:57px}  /* 56px targets + 1px rule — measured, 2026-09-30 (U8) */
    body{padding-bottom:calc(var(--tabbar) + env(safe-area-inset-bottom))}

    .tabbar{
      display:grid; grid-template-columns:repeat(5,1fr);
      position:fixed; left:0; right:0; bottom:0; z-index:50;
      background:var(--bar); border-top:1px solid rgba(255,255,255,.10);
      padding-bottom:env(safe-area-inset-bottom);
    }
    .tabbar a{
      display:flex; flex-direction:column; align-items:center; justify-content:center;
      gap:3px; min-height:56px; color:var(--bar-tx); font-size:10.5px; font-weight:600;
      list-style:none;
    }
    /* ⚠ SUMMARY IS NOT A FLEX CONTAINER, ON PURPOSE. Safari refuses to toggle a
       <details> whose <summary> is display:flex — the tap does nothing at all.
       The summary stays a list-item and an inner span does the stacking. */
    .tabbar summary{
      display:list-item; list-style:none; cursor:pointer;
      color:var(--bar-tx); font-size:10.5px; font-weight:600;
    }
    .tabbar summary>.mi{
      display:flex; flex-direction:column; align-items:center; justify-content:center;
      gap:3px; min-height:56px;
    }
    .tabbar a:hover,.tabbar a:active{text-decoration:none; background:rgba(255,255,255,.07)}
    .tabbar .ic{font-size:19px; line-height:1}
    .tabbar a[aria-current="page"],.tabbar a.on{color:var(--brand)}
    /* (The phone "More" menu and its panel were removed with the menu itself —
       2026-09-14; their CSS went 2026-09-30.) */
  }
}

/* ═══ COMPONENTS ═══════════════════════════════════════════════════════════ */
@layer components {

  /* ── surfaces ─────────────────────────────────────────────────────────── */
  .card{background:var(--card); border:1px solid var(--line); border-radius:var(--r-md); overflow:hidden}

  /* ── section tabs — what used to be rows in the More menu ─────────────────
     2026-09-14. A section owns its own views: Stock holds Counted, Cables and
     Reports; Jobs holds Open, All and Closed; Find holds the two engines. This
     is where a new view goes. It is NOT a place for actions — Apple's rule for
     the same reason: a bar of destinations that sometimes does something to the
     page is a bar nobody trusts to be safe to press.

     ⚠ IT SCROLLS SIDEWAYS ON ITS OWN, and that is §8 of project_capture_ux:
     at 390px four tabs do not fit, and the alternative to scrolling here is
     the page scrolling sideways, which is the bug that started that section. */
  .subnav{
    display:flex; gap:2px; margin:0 0 var(--s4);
    overflow-x:auto; scrollbar-width:none; -webkit-overflow-scrolling:touch;
    border-bottom:1px solid var(--line);
  }
  .subnav::-webkit-scrollbar{display:none}
  .subnav a{
    flex:none; padding:10px 14px; font-size:14px; font-weight:500; color:var(--mut);
    border-bottom:2px solid transparent; margin-bottom:-1px; white-space:nowrap;
    min-height:var(--tap-sm); display:flex; align-items:center; gap:7px;
  }
  .subnav a:hover{color:var(--ink); text-decoration:none; background:var(--sunk)}
  .subnav a[aria-current="page"]{color:var(--ink); font-weight:600; border-bottom-color:var(--brand)}
  .subnav .n{font-family:var(--mono); font-size:11.5px; color:var(--mut);
             background:var(--sunk); border-radius:var(--r-pill); padding:1px 7px}
  .subnav a[aria-current="page"] .n{background:var(--soft); color:var(--bink)}

  /* ── setup: seven screens that used to be seven menu rows ─────────────── */
  .deck{display:grid; gap:var(--s3); grid-template-columns:repeat(auto-fill,minmax(min(100%,235px),1fr))}
  .deck a{
    display:block; background:var(--card); border:1px solid var(--line);
    border-radius:var(--r-md); padding:var(--s4); min-height:var(--tap-lg);
  }
  .deck a:hover{border-color:var(--edge); text-decoration:none; box-shadow:var(--shadow)}
  .deck a b{display:block; font-size:15px; font-weight:600; color:var(--ink); margin-bottom:3px}
  .deck a span{display:block; font-size:13px; color:var(--mut); line-height:1.45}
  .deck .grp{grid-column:1/-1; font-size:11px; letter-spacing:.11em; text-transform:uppercase;
             color:var(--mut); font-weight:700; margin:var(--s3) 0 0}
  .deck .grp:first-child{margin-top:0}

  /* ── the day bar and the one question ─────────────────────────────────────
     The landing screen used to open with six metric cards and four tables —
     ten regions before you had decided anything. It now opens by naming what
     needs you. A metric is context; a job that cannot close is work. */
  .daybar{display:flex; justify-content:space-between; align-items:flex-start;
          gap:var(--s4); flex-wrap:wrap; margin-bottom:var(--s4)}
  .hero{
    background:var(--card); border:1px solid var(--line); border-left:4px solid var(--brand);
    border-radius:var(--r-md); padding:var(--s3) var(--s4); margin-bottom:var(--s4);
  }
  .hero .q{font-size:17px; font-weight:600; letter-spacing:-.015em; margin:0 0 2px}
  .hero .a{font-size:13.5px; color:var(--mut); margin:0}

  /* ── metric strip — six cards became one row ──────────────────────────── */
  .strip{
    display:flex; gap:1px; background:var(--line); border:1px solid var(--line);
    border-radius:var(--r-md); overflow:hidden; margin-bottom:var(--s5);
  }
  .strip a{
    flex:1; background:var(--card); padding:11px var(--s2); text-align:center;
    color:inherit; min-width:0; min-height:var(--tap-sm);
  }
  .strip a:hover{background:var(--soft); text-decoration:none}
  .strip .v{
    font-size:21px; font-weight:600; letter-spacing:-.02em; display:block;
    line-height:1.15; font-variant-numeric:tabular-nums;
  }
  .strip .k{font-size:10.5px; color:var(--mut); display:block; margin-top:1px;
            overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
  .strip a.alert .v{color:var(--bink)}
  .strip a.zero{opacity:.55}
  .strip a.zero:hover{opacity:1}

  /* ── action row — the queue that replaced four tables ─────────────────── */
  .queue{display:flex; flex-direction:column; gap:var(--s2); margin-bottom:var(--s5)}
  .qrow{
    display:flex; align-items:center; gap:var(--s3); background:var(--card);
    border:1px solid var(--line); border-radius:var(--r-md);
    padding:13px var(--s3); color:inherit; min-height:var(--tap-md);
  }
  .qrow:hover{border-color:var(--brand); text-decoration:none; box-shadow:var(--shadow)}
  .qrow .stripe{width:4px; align-self:stretch; border-radius:3px; background:var(--mut); flex:none}
  .qrow.hi .stripe{background:var(--bad-tx)}
  .qrow.md .stripe{background:var(--brand)}
  .qrow .grow{min-width:0; flex:1}
  .qrow .t{font-weight:600; font-size:14.5px; display:block}
  .qrow .s{font-size:12.5px; color:var(--mut); display:block}
  .qrow .go{color:var(--mut); font-size:17px; flex:none}
  .qrow:hover .go{color:var(--bink)}

  /* ── tiles (kept: stock and other screens still use them) ─────────────── */
  /* ── the reach grid on a stock report ─────────────────────────────────────
     ⚠ IT IS A TABLE, SO capture_ux §8 APPLIES: it lives in a card and scrolls
     sideways inside it. Reach down the side, form factor across, counts in the
     cells — the answer to "have we got something that will do this link" is one
     cell rather than forty-nine rows to read.

     The unrecorded row carries the warning colour and sorts last, because a
     third of a shelf QM cannot describe is the most useful line on the page and
     must not read as just another reach. */
  table.grid td.num{text-align:right; font-variant-numeric:tabular-nums;
                    font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
  table.grid td.num a{font-weight:600}
  table.grid td.tot{border-left:1px solid var(--line); font-weight:600}
  table.grid .zero{color:var(--line)}
  table.grid tr.unk td{background:var(--warn-bg)}
  table.grid tr.unk td strong{color:var(--warn-tx)}

  /* Serial chips. They wrap, so this is one of the few places §8's nowrap rule
     is deliberately not wanted — a chip list that scrolls sideways hides stock. */
  .serchips{display:flex; flex-wrap:wrap; gap:5px; margin:6px 0 0}
  .serchips .ser{font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:12px;
                 background:var(--panel); border:1px solid var(--line); border-radius:5px;
                 padding:3px 8px; text-decoration:none; color:var(--ink); min-height:0}
  .serchips .ser:hover{border-color:var(--brand); text-decoration:none}

  .tiles{display:grid; grid-template-columns:repeat(auto-fit,minmax(172px,1fr)); gap:13px}
  /* The same auto-fit grid, written inline in five templates (R11). */
  .grid-fit{display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); gap:14px}
  .tile{
    display:block; background:var(--card); border:1px solid var(--line);
    border-radius:var(--r-md); padding:15px 17px; color:inherit; position:relative;
    min-height:96px; transition:border-color .12s, transform .12s, box-shadow .12s;
  }
  /* Only a tile that GOES somewhere lifts on hover. A figure in a <div> must not
     look pressable — _components/tile's own rule (REVIEW-2026-09-30, R11). */
  a.tile:hover{text-decoration:none; border-color:var(--brand); box-shadow:var(--shadow); transform:translateY(-1px)}
  a.tile:active{transform:translateY(0)}
  .tile .k{font-size:11.5px; letter-spacing:.07em; text-transform:uppercase; color:var(--mut); font-weight:600}
  .tile .v{font-size:27px; font-weight:600; letter-spacing:-.02em; margin-top:5px; line-height:1.15; color:var(--ink)}
  .tile .n{font-size:12px; color:var(--mut); margin-top:3px}
  .tile .go{position:absolute; top:14px; right:14px; color:var(--mut); font-size:15px; opacity:.45}
  a.tile:hover .go{opacity:1; color:var(--bink)}
  .tile.alert{border-color:var(--brand); background:var(--soft)}
  .tile.alert .v{color:var(--bink)}
  .tile.zero{opacity:.62}
  .tile.zero:hover{opacity:1}

  /* ── tables ───────────────────────────────────────────────────────────── */
  table{width:100%; border-collapse:collapse; font-size:14.5px}
  thead th{
    text-align:left; font-size:11px; letter-spacing:.08em; text-transform:uppercase;
    color:var(--mut); font-weight:600; padding:11px 15px; background:var(--sunk);
    border-bottom:1px solid var(--line); white-space:nowrap;
  }
  tbody td{padding:12px 15px; border-bottom:1px solid var(--line); vertical-align:middle}
  tbody tr:last-child td{border-bottom:0}
  tbody tr:hover{background:var(--soft)}
  td.num{text-align:right; font-variant-numeric:tabular-nums}

  /* ⚠ A TABLE INSIDE A CARD SCROLLS SIDEWAYS; THE PAGE NEVER DOES.
     Matt, 2026-09-03, on the phone: "things slew off to the right, you cannot
     click on things like taking in to see that receipt." Every list here is a
     7-to-11-column table at width:100% inside `.card{overflow:hidden}`. On a
     375px screen the table was wider than the card, the card CLIPPED it, and
     the "Open ›" button in the last column was cut off — present, unreachable.
     Where a table sat outside a card it pushed the whole page sideways instead.
     The card is now the scroll container for its table, and the body refuses
     to scroll sideways at all, so nothing can slew. Rows are also tappable on
     a phone (qm.js) so the last column need not be reached to open a row. */
  .card:has(> table),.card:has(> form > table){overflow-x:auto; -webkit-overflow-scrolling:touch}
  .card > table,.card > form > table{min-width:100%}
  @media(max-width:820px){
    body{overflow-x:clip}
    table{font-size:13.5px}
    thead th{padding:9px 11px}
    /* ⚠ ONE LINE PER ROW. Wrapping eleven columns into 390px turned every row
       into a three-line shred ("FLEX / OPTI / X") and a stock list into a
       scroll of a thousand pixels. A row stays one line and the CARD scrolls
       sideways; long cells are cut with an ellipsis, and the row opens on tap
       so nothing is lost behind the cut. `td.wrap` opts a cell back out. */
    tbody td{padding:10px 11px}
    /* List tables only — a key/value table (no <thead>) holds prose and wraps. */
    table:has(> thead) tbody td{white-space:nowrap; max-width:220px; overflow:hidden; text-overflow:ellipsis}
    table:has(> thead) tbody td.wrap{white-space:normal; max-width:none}
    tbody tr.rowlink{cursor:pointer}
    tbody tr.rowlink:active{background:var(--soft)}
  }

  /* ⚠ THE iPAD MINI (Matt, 2026-09-30: "I WILL be using a mini a lot").
     A mini is 744px wide held upright and 1133px on its side. Upright it gets
     the phone's chrome (tab bar, 56px targets, which suit a hand-held in a
     hall); on its side it gets the desk. Measured with the real stylesheet on
     30 Sep: upright, nine list tables ran past their card (Stock by 550px);
     on its side, Stock still did.

     So, from 600px (bigger than any phone) to 1180px:
     1. Cells wrap between words. The phone's one-line rule exists because
        390px shreds a word per line. At 600px and up a part number wraps
        whole, and a two-line cell beats a row you have to scroll sideways.
     2. From 600 to 1000px, the widest lists (`table.tworow`: Stock, Jobs,
        Cables, Receipts, Parts) become two-line rows: what it is on the first
        line, the facts, each with its column's name, on the second. Nothing
        is dropped and nothing scrolls sideways. The row still opens on tap
        (qm.js), so the "Open ›" column goes. qm.js copies each header onto
        its cells as data-label. A header marked data-nolabel is self-evident
        (a serial, a pill), and data-go marks the Open column.
     The phone keeps the documented one-line rows. */
  td.brk{display:none}
  @media (min-width:600px) and (max-width:1180px){
    table{font-size:13.5px}
    thead th{padding:9px 9px; white-space:normal; line-height:1.25}
    tbody td{padding:9px 9px}
    table:has(> thead) tbody td{white-space:normal; max-width:none; overflow:visible; text-overflow:clip}
    table:has(> thead) tbody td.num{white-space:nowrap}
    td.cellone{white-space:normal; max-width:none}
    /* The row opens on tap and its first link opens it too; the "Open ›"
       column is the one a 1133px-wide Stock list could not spare. */
    table.tworow th[data-go], table.tworow td.go{display:none}
  }
  @media (min-width:600px) and (max-width:1000px){
    table.tworow{display:block}
    .card > table.tworow{min-width:0}
    table.tworow thead{display:none}
    table.tworow tbody{display:block}
    table.tworow tbody tr{display:flex; flex-wrap:wrap; align-items:baseline;
                         column-gap:16px; row-gap:6px; padding:11px 14px;
                         border-bottom:1px solid var(--line)}
    table.tworow tbody tr:last-child{border-bottom:0}
    table.tworow tbody td{display:block; padding:0; border:0; white-space:normal;
                         min-width:0; max-width:100%; overflow:visible}
    table.tworow tbody td:not([data-label]){font-size:15px}
    table.tworow tbody td[data-label]::before{
      content:attr(data-label); font-family:var(--font); font-size:10px; letter-spacing:.07em;
      text-transform:uppercase; color:var(--mut); font-weight:600; margin-right:6px}
    table.tworow tbody td.brk{display:block; flex-basis:100%; height:0}
    table.tworow tbody td.go{display:none}
    table.tworow tbody td br{display:none}
    table.tworow tbody td:empty:not(.brk){display:none}
  }

  /* ── status ───────────────────────────────────────────────────────────── */
  .pill{display:inline-block; border-radius:var(--r-pill); padding:3px 10px;
        font-size:11.5px; font-weight:600; white-space:nowrap}
  .p-good{background:var(--good-bg); color:var(--good-tx)}
  .p-warn{background:var(--warn-bg); color:var(--warn-tx)}
  .p-bad{background:var(--bad-bg);  color:var(--bad-tx)}
  .p-mut{background:var(--sunk);    color:var(--mut)}

  /* ⚠ THE LEVEL DRIVES THE COLOUR. Every template used to render every message
     through one hardcoded green class, so "RuntimeError: key is not set"
     arrived looking like a success. Colour is the part of a page people trust
     WITHOUT reading; green for a failure tells the reader to stop paying
     attention exactly when they need to start. There is now one partial —
     _components/messages.html — and this is its only styling. */
  .flash{background:var(--good-bg); color:var(--good-tx); border-radius:9px;
         padding:10px 13px; margin-bottom:14px; font-size:13.5px; font-weight:500}
  .flash.f-warn{background:var(--warn-bg); color:var(--warn-tx)}
  .flash.f-bad{background:var(--bad-bg); color:var(--bad-tx); border-left:4px solid var(--bad-tx)}
  .lnk{background:none; border:0; padding:0; font:inherit; color:inherit;
       text-decoration:underline; cursor:pointer; font-weight:600}
  /* "correct" / "retract" on the desk job page — the phone's "Fix a time" leads
     there, so they need a finger-sized target too (U13). */
  summary.sumbtn{cursor:pointer; display:inline-flex; align-items:center; min-height:36px;
                 padding:4px 12px; list-style:none}
  summary.sumbtn::-webkit-details-marker{display:none}
  @media(max-width:820px){ summary.sumbtn{min-height:44px} }
  /* A button styled as a link still needs a finger-sized target (U13). */
  button.lnk{min-height:44px; padding:0 6px}
  @media(max-width:820px){
    input[type=checkbox],input[type=radio]{width:24px; height:24px}
    .tabbar{padding-left:env(safe-area-inset-left); padding-right:env(safe-area-inset-right)}
  }

  .warn{display:flex; gap:10px; align-items:center; background:var(--warn-bg);
        border:2px solid var(--brand); border-radius:10px; padding:11px 13px;
        margin-top:14px; color:var(--warn-tx); font-size:13.5px; font-weight:500}
  .warn .ico{font-size:16px}

  .viewbar{
    display:flex; align-items:center; gap:var(--s3); flex-wrap:wrap;
    background:var(--soft); border:1px solid var(--line); border-left:4px solid var(--brand);
    border-radius:10px; padding:10px 14px; margin-bottom:var(--s4); font-size:13.5px;
  }
  .viewbar .n{color:var(--mut)}
  .viewbar a{margin-left:auto; font-weight:600}

  .empty{padding:40px 26px; text-align:center; color:var(--mut)}
  .empty .big{font-size:15px; color:var(--ink); font-weight:500; margin-bottom:5px}
  .empty p{margin:0 auto; max-width:46ch; font-size:13.5px}


  /* ── buttons ──────────────────────────────────────────────────────────── */
  /* ⚠ THIS WAS WHITE-ON-WHITE FOR EVERY USE OUTSIDE THE TOP BAR, AND THAT IS
     THE WHOLE OF "I cannot edit them".

     `.ghost` was written for the dark bar, where white at 82% measures 12.54:1.
     I then reused it for "Edit >", "Open >", the contact Save buttons and the
     Retire and Merge buttons — all of which sit on a cream card, where the same
     rule measures 1.06:1 for the text and 1.02:1 for the border. Not dim.
     INVISIBLE. The controls were present, correctly sized and keyboard
     reachable; nothing was on screen. Matt reported it twice as a missing
     feature, which is exactly what an invisible control is.

     So the DEFAULT is now the content case, built from theme tokens so it
     follows sepia and dark alike, and the dark bar takes the override. Defaults
     should suit the common case; the bar is the exception, not the rule. */
  .ghost{
    background:transparent; border:1px solid var(--edge);
    color:var(--ink); border-radius:8px; padding:6px 12px;
    font-size:12.5px; font-family:inherit; cursor:pointer; display:inline-block;
    min-height:38px; line-height:24px;
  }
  .ghost:hover{background:var(--soft); border-color:var(--brand);
               color:var(--ink-strong, var(--ink)); text-decoration:none}

  /* The dark bar, where the original white treatment belongs. */
  .topbar .ghost,.tabbar .ghost{
    border-color:rgba(255,255,255,.22); color:rgba(255,255,255,.82);
  }
  .topbar .ghost:hover,.tabbar .ghost:hover{
    background:rgba(255,255,255,.1); color:#fff;
  }

  /* ⚠ 44px MINIMUM, NOT 25px. These were min-width:30px with no min-height,
     three of them adjacent — failing WCAG 2.2 SC 2.5.8 both on size and on the
     spacing exception. */
  /* ⚠ SCOPED TO THE BAR ON PURPOSE. These are white-on-transparent, which is
     correct on the dark bar and invisible anywhere else — the exact mistake
     `.ghost` made. Scoping the SELECTOR makes the constraint real instead of a
     convention, so tools/contrast_check.py can trust it rather than allowlist
     it. Move the toggle out of the bar and it will lose its colours loudly. */
  .topbar .theme{display:flex; gap:2px; background:rgba(255,255,255,.09); border-radius:9px; padding:2px}
  .topbar .theme button{
    background:none; border:0; color:rgba(255,255,255,.72); font:inherit; font-size:14px;
    line-height:1; padding:0 10px; border-radius:7px; cursor:pointer;
    min-width:var(--tap-sm); min-height:var(--tap-sm);
  }
  .topbar .theme button:hover{color:#fff; background:rgba(255,255,255,.12)}
  .topbar .theme button[aria-pressed="true"]{background:var(--brand); color:var(--onbrand)}

  .btn-primary,.btn-second{
    display:inline-block; border:0; border-radius:10px; cursor:pointer;
    font-family:inherit; font-size:15px; font-weight:600; padding:11px 18px;
    text-align:center; min-height:var(--tap-sm); line-height:24px;
  }
  .btn-primary{background:var(--brand); color:var(--onbrand)}
  .btn-primary:hover{background:var(--brand-hi); text-decoration:none; color:var(--onbrand)}
  /* Controls are outlined with --edge, not the divider colour (U12): --line
     measured 1.34:1, so a white button on the sepia page barely read as one. */
  .btn-second{background:var(--card); color:var(--ink); border:1px solid var(--edge)}
  .btn-second:hover{background:var(--soft); border-color:var(--brand); text-decoration:none}

  /* ── capture: the screen used standing in a cold aisle ─────────────────
     Targets are sized to MIL-STD-1472H's gloved minimum, which is stricter
     than WCAG's. Primary actions are bottom-anchored ON A PHONE because the
     top of a phone is unreachable one-handed at the top of a rack. */
  .cap{max-width:560px; margin:0 auto}
  .cap-head{margin-bottom:var(--s4)}
  .cap-head .ref{font-family:var(--mono); font-size:12.5px; color:var(--mut)}
  .cap-head .who2{display:flex; align-items:baseline; gap:10px; flex-wrap:wrap; margin-top:2px}
  .cap-head .who2 strong,.cap-head .who2 .cap-h1{font-size:22px; font-weight:600; letter-spacing:-.02em}
  .cap-head .who2 span{font-size:14px; color:var(--mut)}
  .cap-head .who3{display:flex; gap:10px; flex-wrap:wrap; font-size:13px; color:var(--mut); margin-top:3px}

  .timeline{padding:2px 15px}
  .ev{display:flex; align-items:center; gap:var(--s3); padding:var(--s3) 0;
      border-bottom:1px solid var(--line)}
  .ev:last-child{border-bottom:0}
  .ev.muted .evn,.ev.muted .evt{color:var(--mut)}
  .dot{width:9px; height:9px; border-radius:50%; background:var(--brand); flex:none}
  .dot.o{background:transparent; border:2px solid var(--line)}
  .evn{flex:1; font-size:15px; font-weight:500; min-width:0}
  .evs{display:block; font-size:11.5px; color:var(--mut); font-weight:400}
  .evt{font-size:15px; font-weight:600; font-variant-numeric:tabular-nums; font-family:var(--mono)}
  .undo{margin:0}
  .undo button{
    background:none; border:0; color:var(--mut); font-size:20px; line-height:1;
    cursor:pointer; border-radius:6px; font-family:inherit;
    min-width:var(--tap-sm); min-height:var(--tap-sm);
  }
  .undo button:hover{background:var(--bad-bg); color:var(--bad-tx)}

  /* ⚠ THE STICKY SLAB IS A PHONE PATTERN AND ONLY EXISTS ON A PHONE.
     Left switched on at 1400px it is neither where a mouse expects an action
     nor anywhere near the form it submits. */
  .cap-foot{
    background:var(--panel); border-top:1px solid var(--line);
    padding:var(--s4) 0 0; margin:var(--s5) auto 0; max-width:560px;
  }
  .cap-foot form{margin:0}
  @media(max-width:820px){
    .cap{padding-bottom:var(--s5)}
    .cap-foot{
      position:sticky; bottom:calc(var(--tabbar) + env(safe-area-inset-bottom));
      margin:0 -14px -24px; max-width:none;
      /* The inset is already in `bottom`; adding it again here left a band of
         empty footer under the buttons (REVIEW-2026-09-30, U8). */
      padding:13px max(14px, env(safe-area-inset-right)) 14px max(14px, env(safe-area-inset-left));
      box-shadow:0 -6px 18px rgba(0,0,0,.06);
    }
  }

  .tap-primary{
    display:block; width:100%; max-width:530px; margin:0 auto; border:0;
    border-radius:var(--r-lg); background:var(--brand); color:var(--onbrand);
    font-family:inherit; font-size:19px; font-weight:600; letter-spacing:-.01em;
    min-height:var(--tap-lg); cursor:pointer; text-align:center;
    line-height:var(--tap-lg);
  }
  .tap-primary:hover{background:var(--brand-hi); text-decoration:none}
  .tap-primary.done{background:var(--good-bg); color:var(--good-tx)}
  /* A label too long for one line at phone width ("Save and close FS-20261022-001")
     wraps to two ordinary lines, centred, instead of two 76px lines with a gap between
     (seen in the 22 Oct press-through). The job number stays whole (.nowrap). */
  .tap-primary.finbtn{display:flex; align-items:center; justify-content:center;
    line-height:1.25; padding:12px 16px}

  .tap-row{display:grid; grid-template-columns:repeat(auto-fit,minmax(88px,1fr));
           gap:var(--s2); max-width:530px; margin:9px auto 0}
  .tap-row.flow{margin-top:var(--s2)}
  .tap-second{
    width:100%; min-height:var(--tap-md); border:1px solid var(--edge);
    border-radius:10px; background:var(--card); color:var(--ink);
    font-family:inherit; font-size:15px; font-weight:500; cursor:pointer;
  }
  .tap-second:hover{background:var(--soft); border-color:var(--brand)}
  a.tap-second{display:block; line-height:var(--tap-md); text-align:center}
  a.tap-second:hover{text-decoration:none}

  /* ⚠ TWO KINDS OF TAP, TWO AFFORDANCES, AND THE DIFFERENCE IS NOT COSMETIC.
     A ONE-TIME milestone (left office, arrived, left facility, back at office)
     bounds the visit — two of them bound the INVOICE — so a second press is
     almost certainly a mistake: it gets a tick and dims.
     A REPEATABLE event (at rack, locked up, waiting, verified) can legitimately
     happen again — you go to the rack, leave for the loading dock, come back —
     so it gets a COUNT. A tick there would be actively wrong: it says "done"
     about something that has no done. */
  .tap-second.is-done{opacity:.8; border-style:dashed; color:var(--mut)}
  .tap-second.is-done:hover{opacity:1}
  .tap-second .tick{color:var(--good-tx); font-weight:700}
  .tap-second .count{
    font-family:var(--mono); font-size:11px; background:var(--soft); color:var(--bink);
    padding:1px 6px; border-radius:var(--r-pill); margin-left:5px;
  }

  /* The camera earns the primary row once you are on site — Jobber's pattern,
     where the "+" becomes a Camera button on a visit screen. Photographing a
     label is the highest-frequency action of a visit. */
  .tap-second.cam{background:var(--onbrand); color:#fff; border-color:var(--onbrand)}
  .tap-second.cam:hover{background:#000; border-color:#000}

  .tap-action{
    display:flex; align-items:center; gap:var(--s3); width:100%; min-height:84px;
    padding:0 var(--s4); border:2px solid var(--brand); border-radius:var(--r-lg);
    background:var(--soft); color:var(--ink); font-family:inherit; cursor:pointer;
    text-align:left; margin-bottom:var(--s2);
  }
  .tap-action:hover{background:var(--card); text-decoration:none}
  .tap-action .ta-ico{
    flex:none; width:44px; height:44px; border-radius:11px; display:grid;
    place-items:center; background:var(--brand); color:var(--onbrand); font-size:20px;
  }
  .tap-action .ta-txt{font-size:16px; font-weight:600; line-height:1.25}
  .tap-action .ta-sub{display:block; font-size:12.5px; font-weight:400; color:var(--mut)}

  /* ── the three doors on Overview ──────────────────────────────────────────
     Start a job · Take something in · Find a thing. Built on .tap-action so
     they are the same shape as the capture screen's actions — a new hire
     learns one shape. Three across on a desk, one column on a phone. */
  .doors{display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:var(--s3); margin:var(--s4) 0 0}
  .doors .door{margin:0; min-height:96px; align-items:flex-start; padding-top:14px; padding-bottom:14px}
  .doors .door .ta-txt{min-width:0}
  .doors .door .ta-sub{margin-top:3px}
  .door-find{cursor:default}
  .door-find input[type=search]{
    display:block; width:100%; margin-top:9px; min-height:var(--tap-sm);
    font-size:14px; font-family:var(--mono); border-color:var(--edge);
  }
  @media(max-width:820px){ .doors{grid-template-columns:minmax(0,1fr)} .door-find input[type=search]{font-size:16px} }

  /* a folded section whose summary reads as an h2 — the by-hand add form on Stock */
  .fold>summary.h2-summary{
    list-style:none; cursor:pointer; font-size:12px; letter-spacing:.12em; text-transform:uppercase;
    color:var(--bink); font-weight:600; margin:0 0 var(--s3); padding:7px 0; min-height:var(--tap-sm);
    border-bottom:1px solid var(--line); display:flex; align-items:center; gap:8px;
  }
  .fold>summary.h2-summary::-webkit-details-marker{display:none}
  .fold>summary.h2-summary::before{content:"\25B8"; font-size:12px; transition:transform .12s}
  .fold[open]>summary.h2-summary::before{transform:rotate(90deg)}

  /* A link: display it as a block or min-height does nothing — it measured 19px
     tall as the primary action of the phone Jobs tab (U13). */
  .bigcta{display:flex; align-items:center; justify-content:center; font-size:17px;
          font-weight:600; min-height:var(--tap-lg); margin:var(--s3) 0}
  .row2{display:grid; grid-template-columns:1fr 1fr; gap:var(--s2); max-width:530px; margin:0 auto}

  .pickrow{
    display:flex; align-items:center; gap:var(--s3); min-height:var(--tap-md);
    padding:10px 0; border-bottom:1px solid var(--line); color:inherit; cursor:pointer;
  }
  .pickrow:last-child{border-bottom:0}
  /* Install / Remove: the stock list folds behind a real button (22 Oct 2026; Matt:
     every button must look like a button), and the label-read panel. */
  .stockpick > summary{list-style:none; display:flex; align-items:center; justify-content:center;
    text-align:center; line-height:1.25; padding:10px 14px; cursor:pointer}
  .stockpick > summary::-webkit-details-marker{display:none}
  .labelread{border:1px solid var(--line); border-radius:var(--r-lg); padding:12px 14px; margin-top:10px}
  .labelread.ok{border-color:var(--brand); background:var(--soft, transparent)}
  .labelread p{margin:0 0 6px}
  .labelread .pickrow{width:100%; text-align:left; background:none; border:0;
    border-bottom:1px solid var(--line); min-height:56px}
  .labelread .pr-serial{font-size:20px; letter-spacing:.02em}
  .pickrow:hover{text-decoration:none}
  .pr-main{flex:1; min-width:0}
  .pr-serial{display:block; font-family:var(--mono); font-size:14px; font-weight:500}
  .pr-sub{display:block; font-size:12px; color:var(--mut)}

  /* ── forms ────────────────────────────────────────────────────────────── */
  .fld{margin-bottom:var(--s4)}
  .fld label{
    display:block; font-size:11.5px; letter-spacing:.06em; text-transform:uppercase;
    color:var(--mut); font-weight:600; margin-bottom:5px;
  }
  input[type=text],input[type=search],input[type=number],input[type=password],
  input[type=email],input[type=tel],input[type=url],
  input[type=datetime-local],input[type=date],select,textarea{
    width:100%; font:inherit; font-size:15px; padding:11px 12px;
    min-height:var(--tap-sm); border:1px solid var(--edge); border-radius:9px;
    background:var(--card); color:var(--ink);
  }
  textarea{min-height:96px; resize:vertical}
  /* ⚠ 16px ON A PHONE, OR iOS ZOOMS THE PAGE ON EVERY FIELD (REVIEW-2026-09-30,
     U6). Safari zooms into any input under 16px when it is focused and does
     not zoom back out — every form a phone used (start a job, Find, Take in,
     passwords) jumped sideways under the thumb. */
  @media(max-width:820px){
    input[type=text],input[type=search],input[type=number],input[type=password],
    input[type=email],input[type=tel],input[type=url],input[type=datetime-local],
    input[type=date],select,textarea{font-size:16px}
  }
  /* A submitted form's button while the request is on its way (U5). */
  .is-busy{opacity:.6; cursor:progress; pointer-events:none}
  input:focus,select:focus,textarea:focus{border-color:var(--brand)}

  /* ── the search box in the dark bar ───────────────────────────────────────
     ⚠ THIS LIVED IN THE LAYOUT LAYER AND WAS SILENTLY LOSING, AND THE RESULT
     WAS ON SCREEN FOR WEEKS: a big WHITE box in the middle of the dark bar,
     looking like an unstyled control. The generic `input[type=search]` rule
     directly above sets width:100% and background:var(--card), and it sits in
     COMPONENTS while `.barfind input` sat in LAYOUT — and the layer order is
     `layout, components`, so the generic rule won no matter how much more
     specific the other one was.

     ⚠ THIS IS THE SECOND TIME. The phone menu panel was lost the same way and
     left a comment in the layout layer saying so. A scar is not a control: the
     rule is that a component's styling lives WITH the component, in the layer
     where its neighbours are, and the way to check is to look at the LAYER
     first and the selector second. */
  .barfind input[type=search]{
    width:230px; min-height:38px; padding:7px 12px; font-size:13.5px; border-radius:9px;
    background:rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.22); color:#fff;
  }
  .barfind input[type=search]::placeholder{color:rgba(255,255,255,.55)}
  .barfind input[type=search]:focus{background:rgba(255,255,255,.14); border-color:var(--brand)}
  @media(max-width:1100px){ .barfind input[type=search]{width:170px} }
  .bigsel{font-size:17px; min-height:var(--tap-md); padding:14px 13px}

  .filters{display:flex; gap:var(--s2); flex-wrap:wrap; align-items:center; margin-bottom:var(--s4)}
  .chip{
    display:inline-block; padding:8px 13px; border-radius:var(--r-pill);
    border:1px solid var(--edge); background:var(--card); color:var(--ink);
    font-size:13px; font-weight:500; min-height:44px;
  }
  .chip:hover{border-color:var(--brand); text-decoration:none}
  .chip.on{background:var(--brand); color:var(--onbrand); border-color:var(--brand); font-weight:600}
  .searchbox{margin-left:auto; min-width:230px}
  .searchbox input{min-height:var(--tap-sm)}

  /* ── the filter rows on Stock ─────────────────────────────────────────────
     One wrapped row of fourteen identical chips became one row per question.
     ⚠ EACH ROW SCROLLS SIDEWAYS RATHER THAN WRAPPING, and that is the whole
     point of the change: wrapping cost four rows of filters at 375px before any
     stock was on screen, which is the one width where it matters. Same device
     as .subnav, same reason — §8 of project_capture_ux: the page must never
     scroll sideways, so something inside it has to. */
  .filterbar{display:grid; gap:var(--s2); margin-bottom:var(--s4)}
  .fset{display:flex; align-items:center; gap:var(--s2); min-width:0}
  .fk{
    flex:none; width:56px; font-size:10.5px; letter-spacing:.09em;
    text-transform:uppercase; color:var(--mut); font-weight:700;
  }
  .chips{
    display:flex; gap:var(--s2); min-width:0; padding:2px 0;
    overflow-x:auto; scrollbar-width:none; -webkit-overflow-scrolling:touch;
  }
  .chips::-webkit-scrollbar{display:none}
  .chips .chip{flex:none}
  .filterbar .searchbox{margin-left:0; flex:1; min-width:0; max-width:420px}

  /* A cell that must stay on one line at ANY width. The nowrap rule for list
     tables is inside the phone media query, so a long location wrapped to three
     lines at a desk and tripled every row's height. */
  td.cellone{white-space:nowrap; max-width:280px; overflow:hidden; text-overflow:ellipsis}

  .movebar{display:flex; gap:var(--s2); align-items:center; flex-wrap:wrap;
           padding:var(--s3) 15px; margin-top:var(--s3)}
  .movebar select,.movebar input{width:auto; flex:1; min-width:130px}

  /* ── job screen ───────────────────────────────────────────────────────── */
  .jobhead{display:flex; align-items:flex-start; justify-content:space-between;
           gap:18px; flex-wrap:wrap; margin-bottom:var(--s5)}
  .jobhead h1{margin-bottom:2px}
  .jobhead-act{display:flex; align-items:center; gap:10px; flex-wrap:wrap}

  .addgrid{display:grid; grid-template-columns:repeat(auto-fit,minmax(320px,1fr)); gap:14px}
  .addcard{padding:18px 20px}
  .addcard h3{margin:0 0 4px; font-size:16px; font-weight:600; letter-spacing:-.01em}
  .addcard .hint{margin:0 0 var(--s3)}
  .addcard label{
    display:block; font-size:11.5px; letter-spacing:.06em; text-transform:uppercase;
    color:var(--mut); font-weight:600; margin-bottom:5px;
  }
  .addcard input[type=file]{width:100%; font-size:14px}

  /* photograph + what was read from it. minmax(0,1fr), NOT 1fr: a grid track's
     default minimum is its content, and the reading table inside pushed the
     card 230px past a phone's edge (2026-09-03). */
  .photorow{display:grid; grid-template-columns:170px minmax(0,1fr); gap:16px; align-items:start}
  @media(max-width:820px){
    .photorow{grid-template-columns:minmax(0,1fr)}
    .photorow > a{max-width:220px}
  }

  .shots{display:grid; grid-template-columns:repeat(auto-fill,minmax(84px,1fr)); gap:7px}
  .shots img{width:100%; aspect-ratio:1; object-fit:cover; border-radius:9px;
             border:1px solid var(--line); display:block}

  .foot{text-align:center; color:var(--mut); font-size:11.5px; padding:0 0 var(--s6)}
}

/* ═══ UTILITIES ════════════════════════════════════════════════════════════
   ⚠ A SMALL CLOSED SET, and it must stay small. These exist to retire the 327
   inline style="" attributes that were scattered across 23 templates — not to
   become a hand-rolled Tailwind. IF THIS LIST EVER PASSES ABOUT FIFTEEN, the
   answer was a component and somebody got it wrong.
   ═══════════════════════════════════════════════════════════════════════════ */
@layer utilities {
  /* Derived from the ACTUAL distribution of the 327 inline declarations, not
     invented: margin:0 (51), margin-top:10px (21), color:var(--mut) (20),
     margin-top:14px (17), padding:18px 22px (10), margin-top:20px (10). */
  .m-0{margin:0}
  .mt-0{margin-top:0}
  .mt-2{margin-top:var(--s2)}
  .mt-3{margin-top:var(--s3)}
  .mt-4{margin-top:var(--s4)}
  .mt-5{margin-top:var(--s5)}
  .mt-6{margin-top:var(--s6)}
  .mb-0{margin-bottom:0}
  .mb-3{margin-bottom:var(--s3)}
  .mb-4{margin-bottom:var(--s4)}
  .pad{padding:var(--s4) var(--s5)}
  .pad-sm{padding:var(--s3) 15px}
  .muted{color:var(--mut)}
  .right{text-align:right}
  .nowrap{white-space:nowrap}
  .stack{display:flex; flex-direction:column; gap:var(--s2)}
  .row{display:flex; gap:var(--s2); align-items:center; flex-wrap:wrap}
  .grow{flex:1; min-width:0}
  .skip{
    position:absolute; left:-9999px; top:0; z-index:100; background:var(--brand);
    color:var(--onbrand); padding:12px 18px; border-radius:0 0 10px 0; font-weight:600;
  }
  .skip:focus{left:0; text-decoration:none}
}

/* ── test-data banner ──────────────────────────────────────────────────
   On screen for weeks, so it must be unmissable AND unirritating: a thin
   strip, not a modal. Amber rather than red — this is a state, not a fault. */
@layer components {
  .testbar{
    display:flex; align-items:baseline; gap:var(--s2); flex-wrap:wrap;
    background:var(--warn-bg); color:var(--warn-tx);
    border:1px solid var(--brand); border-left:5px solid var(--brand);
    border-radius:9px; padding:8px 14px; margin-bottom:var(--s4);
    font-size:13px;
  }
  .testbar strong{font-weight:700; letter-spacing:.02em}
  .testbar code{font-family:var(--mono); font-size:12px}
  .pill.p-test{background:var(--warn-bg); color:var(--warn-tx)}

  /* ── tickbox ──────────────────────────────────────────────────────────────
     A checkbox whose consequence is money, so it is built as a full-width
     target with its explanation inside the label rather than a bare 13px box
     with text beside it. The whole row is the hit area — a 13px checkbox is
     under a third of the gloved minimum this app is designed around.        */
  .tickbox{display:flex; gap:var(--s3); align-items:flex-start; cursor:pointer;
           padding:var(--s3) var(--s4); border:1px solid var(--line);
           border-radius:var(--r-md); background:var(--panel); min-height:56px}
  .tickbox:hover{border-color:var(--brand)}
  .tickbox input{width:22px; height:22px; margin-top:2px; flex:none; accent-color:var(--brand)}
  .tickbox strong{display:block; font-size:15px}
  .tickbox small{display:block; color:var(--mut); font-size:13px; line-height:1.45; margin-top:2px}
  /* Ticked, it stops looking like an option and starts looking like a state. */
  .tickbox:has(input:checked){border-color:var(--brand); background:var(--soft)}

}

@layer components {
  /* the swap sequence on an entry — a hint, never a gate */
  .steps{margin:var(--s3) 0 0; padding:0 0 0 22px; font-size:13.5px; color:var(--mut); line-height:1.55}
  .steps li{margin:0 0 2px}
  .steps strong{color:var(--ink)}
}

@layer components {
/* ── capture: where am I, and the way back (Matt, on site 2026-10-06) ─────── */
.capnav{display:flex; flex-direction:column; gap:8px; margin-bottom:var(--s4)}
.capnav .capback{display:flex; align-items:center; gap:8px; min-height:52px; padding:0 16px;
  border:1px solid var(--line); border-radius:12px; background:var(--card); color:var(--ink);
  font-size:16px; font-weight:600; text-decoration:none}
.capnav .capback span{font-size:26px; line-height:1; color:var(--brand)}
.capnav .capjob{display:block; font-family:var(--mono); font-size:12.5px; color:var(--mut)}
.capnav .captitle{font-size:24px; font-weight:650; letter-spacing:-.02em; margin:2px 0 0}

/* the entries on a job, each one a row you can go back into */
.entrylist{padding:0}
.entrylist a.erow{display:flex; align-items:center; gap:12px; padding:14px 16px; min-height:64px;
  border-bottom:1px solid var(--line); color:var(--ink); text-decoration:none}
.entrylist a.erow:last-of-type{border-bottom:0}
.entrylist .eno{flex:0 0 auto; min-width:34px; height:34px; border-radius:17px; background:var(--sunk);
  display:flex; align-items:center; justify-content:center; font-weight:700}
.entrylist .etxt{flex:1 1 auto; min-width:0}
.entrylist .etxt strong{display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.entrylist .etxt span{display:block; color:var(--mut); font-size:13px; margin-top:2px}
.entrylist .ego{flex:0 0 auto; color:var(--brand); font-size:22px}
.entrylist form{padding:12px 16px; border-top:1px solid var(--line)}

/* resume where you were (qm.context.resume) */
.resumebar{display:flex; align-items:center; gap:12px; margin-bottom:var(--s4); padding:12px 16px;
  border-radius:12px; background:var(--brand); color:#fff; text-decoration:none; font-weight:600;
  min-height:56px}
.resumebar span{flex:1 1 auto}
.resumebar small{display:block; font-weight:500; opacity:.85}
.resumebar b{font-size:22px}

/* described parts: words now, matched to stock later */
.pill.p-todo{background:var(--warn-bg); color:var(--warn-tx)}

/* full-width native date and time pickers on the phone */
.fixtime{display:grid; grid-template-columns:1fr 1fr; gap:10px}
.fixtime input{width:100%; min-height:56px; font-size:18px}
.chips-pick{display:flex; flex-wrap:wrap; gap:8px}
.chips-pick label{display:inline-flex; align-items:center; min-height:44px; padding:0 14px;
  border:1px solid var(--line); border-radius:22px; background:var(--card); font-size:15px}
.chips-pick input{position:absolute; opacity:0; pointer-events:none}
.chips-pick label:has(input:checked){background:var(--brand); color:#fff; border-color:var(--brand)}

/* ── the photograph viewer (Matt, on site 2026-10-06: viewing a photo locked
   the app with no way back) ─────────────────────────────────────────────── */
.qm-viewer{position:fixed; inset:0; z-index:1000; background:rgba(0,0,0,.94);
  display:flex; align-items:center; justify-content:center;
  padding:calc(env(safe-area-inset-top) + 64px) 8px calc(env(safe-area-inset-bottom) + 16px)}
.qm-viewer img{max-width:100%; max-height:100%; object-fit:contain; touch-action:pinch-zoom}
.qm-viewer .qm-close{position:fixed; top:calc(env(safe-area-inset-top) + 10px); right:12px;
  min-width:96px; min-height:48px; border-radius:24px; border:0; background:#fff; color:#111;
  font-size:17px; font-weight:700}
.qm-viewer .qm-count{position:fixed; top:calc(env(safe-area-inset-top) + 22px); left:16px;
  color:#fff; font-size:14px; opacity:.8}
.qm-viewer .qm-nav{position:fixed; bottom:calc(env(safe-area-inset-bottom) + 16px); min-width:64px;
  min-height:52px; border-radius:26px; border:0; background:rgba(255,255,255,.18); color:#fff; font-size:26px}
.qm-viewer .qm-prev{left:16px}
.qm-viewer .qm-next{right:16px}
}

/* ── UX review 2026-10-21 ─────────────────────────────────────────────────
   .desk-only: office content hidden on a phone (Overview strip, recent
   movements, long hints). Rows chevrons readable in glare (F22). Two-line
   capture buttons (F20). */
@layer utilities {
  @media (max-width: 820px) { .desk-only { display: none !important; } }
}
@layer overrides {
  .entrylist .ego { color: var(--bink); }
  a.tap-second { line-height: 1.2; display: flex; align-items: center; justify-content: center;
                 padding: 8px 6px; text-align: center; }
}

@layer overrides { .gaplist ul { padding-left: 1.1em; margin: 4px 0; } .gaplist li { margin: 3px 0; } }

@layer overrides {
  .finish-card { border: 2px solid var(--brand); margin-bottom: 16px; }
  .finish-card h2 { font-size: 22px; }
  ul.checks { list-style: none; padding: 0; margin: 12px 0 0; }
  ul.checks li { display: flex; gap: 10px; align-items: flex-start; padding: 8px 0; border-bottom: 1px solid var(--line, #e5ded3); font-size: 16px; }
  ul.checks li:last-child { border-bottom: 0; }
  ul.checks .mark { width: 24px; flex: none; font-size: 18px; line-height: 1.3; text-align: center; }
  ul.checks li.ok .mark { color: #2f7a3a; }
  ul.checks li.ok .what { color: var(--mut, #6b6258); }
  ul.checks li.todo .what { font-weight: 600; }
  ul.checks .fixlink { font-weight: 600; display: inline-block; padding: 6px 0; }
  /* Ticked from what is kept on the phone, not yet on the server (static/outbox.js). */
  ul.checks .keptnote { display: block; font-size: 13px; font-weight: 400; color: var(--mut, #6b6258); }
  .finlabel { font-size: 17px; font-weight: 700; display: block; margin-top: 8px; }
  .finbtn { width: 100%; }
}
