/* ============================================================================================
   THE DASHBOARD'S LOOK, PORTED OUT OF Index.html.

   The palette, the card, the station rail, the tinted row marks, the bottom nav and the rail are
   the owner's own design (design/preview-v2.html, then design/card-gpt/ on 2026-09-04). This file
   carries them over. Where a rule reads oddly it is usually because it is fixing something that
   was found on a phone; those reasons are kept with the rule.

   THREE THINGS ARE DELIBERATELY NOT THE SAME AS THE ORIGINAL.

   1. WIDTH IS ASKED OF THE CARD, NOT MEASURED BY SCRIPT. `Index.html` runs syncCardLayout() on
      load and on resize: it measures a rendered card and publishes `data-cardsize` and
      `data-cardlayout` on <body>, because from 700px up the cards sit two to a row and a card on
      a laptop is often NARROWER than the same card on a phone. The viewport is not a usable
      proxy for the card. That is exactly what a container query is, so `.job` is a query
      container and the breakpoints below are the same numbers - 340 / 430 / 540 for the layout
      steps and 470 for the drawing - asked of the element instead of of the window. No script,
      no resize listener, and correct on the first paint rather than after it.

   2. THE FOLDS ARE <details>. Every open/close on the card was a button plus a `hidden` sibling
      plus a click handler. Native <details> is the same interaction with none of the JavaScript,
      and it keeps working with the strict Content-Security-Policy this app serves - see the note
      at the top of `src/views/layout.js`. The consequence for anyone editing: the old rules that
      guarded against `hidden` being outranked by a `display:` rule are gone, and their hazard
      with them, but `summary` needs `display:flex` set explicitly or it keeps its marker.

   3. NO INLINE STYLE ATTRIBUTES. The card stagger was `style="animation-delay:Nms"` in the
      original; here it is :nth-child, so `style-src` can be 'self' with no 'unsafe-inline'.

   The tokens below are unchanged from `Index.html`, including the aliases at the bottom of
   :root. EVERY COLOUR IS A TOKEN DEFINED IN ALL THREE THEME STATES - bare :root, the
   prefers-color-scheme block, and [data-theme="dark"]. A literal that only reads in the theme it
   was picked in is the standing trap in this stylesheet and has blanked things before.
   ============================================================================================ */

:root{
  /* `--card` WAS `rgba(255,255,255,.78)` UNTIL 2026-09-17 AND IS NOW OPAQUE, which is the single
     change that turns this palette into the reference's. A card at 78% lets the page's own grey
     through, so every white surface in the app was really #F7F8FA and the border was doing all the
     work of separating it from the page; the reference's workspace is white cards on light grey.
     Opacity was never bought anything here either - the note on `.findout` further down records
     the one place it actively had to be undone, because a translucent sheet floating over a list
     let both be read at once. Nothing in this file wants to see THROUGH a card. */
  --paper:#F8FAFC; --paper2:#EEF2F7; --card:#FFFFFF;
  /* `--ink3` WAS `#8A96A6` AND THAT WAS THE APP-WIDE CONTRAST PROBLEM, measured rather than
     guessed: 3.00:1 on a white card and 2.75:1 on the page, against the 4.5:1 small text needs.
     It is the colour of every metadata line in this app - `.sub`, `.row .k em`, the bottom bar's
     labels, every `.hint`. The new ramp is the reference's own two greys plus one step: #0F172A
     (Text), #475569, #5E6D83, and all three pass on all three light surfaces. */
  --ink:#0F172A; --ink2:#475569; --ink3:#5E6D83;
  --rule:#E5E7EB; --rule2:#EFF2F6;
  /* THE OLD NAMES FOR THE BRAND PURPLE, NOW ALIASES OF THE ONE BELOW. They were a second,
     very slightly different purple (#8B6BFF / #6543E8 / #EEEAFF) reached by about forty rules
     carried over from the original, while the new shell used --pri. Two purples nobody could
     tell apart is not a palette, it is a thing to get wrong later, so these now RESOLVE to the
     primary and there is one brand colour in this file. In dark the two were already the same
     three values, so nothing moves there at all. */
  /* ==========================================================================================
     `--brass` IS THE FILL, `--brassInk` IS THE INK, AND THAT DISTINCTION IS NEW ON 2026-09-18.

     Both are aliases of the brand family and always have been, so for as long as the brand was
     purple they were two shades of one readable colour and it did not matter which a rule reached
     for. Twenty-four rules reached for the fill to paint TEXT, a FOCUS RING or a HOVER BORDER.
     Navy is not so forgiving: `--pri2` in dark is #375C93, a MID navy, and a walk of the dark
     theme measured it at 2.52:1 painting the station line on an order card - under the 4.5:1 text
     needs and under the 3:1 an indicator needs. It was a latent bug the whole time; a palette
     where both values happened to be light is what hid it.

     So every one of those twenty-four now reads `--brassInk` (#14417E light, #A8C4EC dark - 10.06
     and 9.53 against their own surfaces), and `--brass` is left on the only two rules that
     genuinely FILL with it: the progress bar's own bar, and the `.jedit` chip that carries
     `--priOn` on top. If you are adding a rule here: text and rings take the INK.
     ========================================================================================== */
  --brass:var(--pri2); --brassInk:var(--priInk);
  /* THE FOUR STATUS COLOURS ARE DARKER THAN THEY WERE, AND EVERY ONE OF THEM WAS FAILING.
     Measured on a white card: late 3.78:1, ok 3.79:1, risk 2.84:1, ship 4.55:1 - three of the
     four under the 4.5:1 a 12px chip label needs, and `risk` was the worst thing in the palette
     after `--ink3`. They are TEXT as often as they are a dot (`.tico.t-risk` sets both), so the
     brief's own Success/Warning/Danger (#22C55E / #F59E0B / #EF4444) cannot be used literally -
     all three are LIGHTER than what is here and would read worse, not better. The hue is his;
     the lightness is what the measurement allowed. The pale `…Bg` tints are re-cut to match, and
     each colour is checked against its OWN tint as well as against the card. */
  --late:#C62B44; --lateBg:#FDECEF;
  --risk:#9A6206; --riskBg:#FDF3DF;
  --ok:#0F7A57; --okBg:#E6F6F0;
  --wait:#525E70; --waitBg:#EEF1F5;
  --ship:#1E5FB4; --shipBg:#E8F1FD;
  /* ==========================================================================================
     A FIFTH TINT PAIR, 2026-09-22, AND IT IS THE ONLY COLOUR THE SALES DASHBOARD NEEDED.

     THE OWNER NAMED THE FIVE ICON SQUARES ON THAT PAGE HIMSELF: "client blue, creative purple, RFQ
     green, quotation light blue" and the won-project mark yellow. Four of those five already exist
     as measured pairs - `--shipBg`/`--ship`, `--okBg`/`--ok`, `--priBg`/`--priInk` and
     `--riskBg`/`--risk` - and `.tico.t-ship`, `.t-ok`, `.t-brass` and `.t-risk` already draw them.
     PURPLE DID NOT. The brand went navy on 2026-09-18 and the two old purples became aliases of
     `--pri` in the same round, deliberately, so there is nothing in this palette to reach for.

     SO IT IS A NEW PAIR AND IT IS DECLARED IN ALL THREE THEME STATES, which is this file's
     standing rule and the one the gold round was refused under. Both roles are measured, not
     chosen: `--vio` is INK and `--vioBg` is the FILL behind it, exactly as the four above split.

       LIGHT  #6D28D9 on a white card 7.10:1   on #EFE9FD 6.00:1   on the page 6.79:1
       DARK   #C4A9F6 on #141C2E      8.38:1   on its own tint 6.34:1   on the page 9.43:1

     Every one of those is ABOVE all four existing tints (the weakest of which is `--risk` at
     4.62:1 on its own fill), so the newest colour in this file is not the one that will fail.

     IT IS NOT GOLD AND IT IS NOT A SECOND BRAND COLOUR. It reaches exactly one thing: the
     creative-request square on `/sales`, through `.tico.t-vio`. Nothing is painted with it as a
     border, a focus ring or a link, and there is no `--vioRule` or `--vioOn` - a token that exists
     is a token somebody paints something with, which is the finding the gold block above records.
     ========================================================================================== */
  --vio:#6D28D9; --vioBg:#EFE9FD;
  /* `--s1`/`--s2` WERE DEAD - defined in all three themes, referenced nowhere. They are alive
     again as the station bar's two ends, which kills the one hard-coded teal in this file. */
  --s1:#14417E; --s2:#4A8FD8; --grid:rgba(15,23,42,.08);
  --navBg:rgba(248,250,252,.82);
  /* ==========================================================================================
     THE BOTTOM BAR'S TWO NUMBERS, 2026-09-19, AND THEY ARE HERE BECAUSE THEY ARE A PAIR.

     `--nav-col` is the floor under one column and `--nav-fade` is how far the trailing gradient
     reaches. Neither is free to move without the other: the fade is allowed to be 16 only
     because an 84px column leaves the narrowest label on the bar (`Finance`, 40.05 of ink) 21.98
     of slack a side, and a wider fade or a narrower column would put the gradient on a word. The
     full arithmetic is under `.nav` further down this file; these two live in the token block so
     that changing them is one edit in the place a reader looks for numbers.

     THEY WERE 78 AND 18 FOR ONE MEASUREMENT AND THE CLEARANCE CAME OUT AT 0.98px - correct, and
     far too thin to survive a different font. This app ships no webfont, so `Finance` is 40.05
     wide in SF on the machine this was measured on and would be wider in Roboto or Segoe on a
     device that has neither: at 78/18 a four-pixel difference in one word would have put ink under
     the gradient, silently, on somebody else's phone. 84/16 leaves 5.98 here and still clears at
     Finance ink of 51, which is 27% wider than anything measured.

     THEY ARE NOT THEMED. Unlike every other token in this block they have one value in light,
     dark and auto - they are a measurement of type, and type does not change size with the
     colour scheme. That is why they are not restated in either of the two blocks below.
     ========================================================================================== */
  --nav-col:90px; --nav-fade:16px;
  --panel:#F1F5FA; --panelRule:#E5E9F0;
  --brassBg:var(--priBg);
  --shadow:var(--e-2);
  --radius:18px;
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  --serif:var(--sans); --ui:var(--sans);
  /* a drawing is a drawing: the sheet behind an extracted image never flips */
  --sheet:#FFFFFF;
  /* legacy names, kept so rules carried over verbatim still resolve */
  --zinc:var(--paper); --steel:var(--ink2); --mute:var(--ink3);
  --signal:var(--risk); --signal-ink:var(--risk);
  --go:var(--ok); --cool:var(--ship); --active:var(--brass);

  /* ---- THE SHELL, LIGHT. See THE NAMED SYSTEM below for what each one is for. ---- */
  --shell:#F8FAFC; --surface:#FFFFFF; --surface2:#FAFCFE;
  --line:#E5E7EB; --line2:#EFF2F6;
  /* ==========================================================================================
     THE BRAND COLOUR IS NAVY - 2026-09-18, the owner's second design reference.

     `--pri` IS THE ONLY BRAND HUE AND `--brass*` ARE STILL ITS ALIASES, which is the decision
     this whole round turns on. The reference is navy + gold, and the obvious reading of that is
     "brass becomes gold". IT MUST NOT. `--brass`/`--brassInk` reach the page as TEXT in
     twenty-nine rules - `a{}` itself among them, so every link in this app - and gold at
     #D4A15A is 2.32:1 on white. It fails text (4.5:1) and it fails the non-text bound (3:1). So
     the aliases follow the brand to navy, every one of those rules stays readable without being
     touched, and gold gets its own family below, applied only where it is legible.

     `--priInk` IS NOT `--pri`. A link painted in the body ink is not a link: #0F172A is 17.85:1
     on white and #0B1F44 is 16.23 - a reader cannot tell them apart. #14417E is 10.06:1 and is
     visibly the brand rather than the text.
     ========================================================================================== */
  --pri:#0B1F44; --pri2:#16325F; --priInk:#14417E; --priBg:#EAF0F9; --priRule:rgba(11,31,68,.24);
  --priGrad:linear-gradient(135deg,#0B1F44,#16325F); --priOn:#FFFFFF;
  /* ==========================================================================================
     GOLD, AND THE TWO TOKENS IT IS ALLOWED TO BE.

     `--gold` is a FILL and `--goldOn` is the ink that sits on it - deep navy, 6.99:1. There is no
     `--goldInk`, no `--goldBg` and no `--goldRule`, and their absence is the finding rather than
     an omission: gold at #D4A15A is 2.32:1 on white. It fails text (4.5:1) and it fails the 3:1 a
     border or a focus ring needs, so on every light surface in this app there is nothing gold is
     allowed to be. The three tokens were written, measured, found to have no legal use on a page,
     and deleted - a token that exists is a token somebody paints something with.

     SO GOLD IS BRAND CHROME: the rail's active door and the diagonal at its foot, both of them on
     navy, where it is 6.99:1 and looks like the reference. In DARK the fill lifts to #E7BE7E and
     may be ink as well (9.77:1 on a card), which is why that theme states it separately.

     WHAT THIS COSTS, SO IT IS NOT DISCOVERED LATER: below 768px there is no rail, so there is no
     gold on a phone at all. The reference's phone carries gold in one place - the logo mark - and
     this round draws no logo, by the owner's own instruction. See the report.
     ========================================================================================== */
  --gold:#D4A15A; --goldOn:#0B1F44;
  --topBg:rgba(248,250,252,.88); --topRule:#E5E7EB;
}

/* ============================================================================================
   THE NAMED SYSTEM — the part of this slice that outlives it.
   ============================================================================================
   Everything below is DESIGN SCALE and carries no colour, so it is declared once in bare :root
   and never restated per theme. The three theme states above and below own colour and nothing
   else. That split is the whole reason this block can be read as a system rather than as a list.

   SPACE — one 4px base, eight steps. Every gap, pad and margin in the new shell is one of these
   and never a number typed in place. --sp-4 (16px) is the shell's default gutter; --sp-6 (24px)
   is the gap BETWEEN blocks that are different things; --sp-3 (12px) is the gap between things
   that belong together.

   TEXT — six steps against a 15px body. --t-display is the page header and appears once per
   page; --t-title is a section heading; --t-body is prose and values; --t-sm is a secondary
   line; --t-xs is metadata; --t-micro is the uppercase label, which is the one step that is
   ALWAYS paired with --ls-caps and a weight of --w-bold or it reads as shouting.

   WEIGHT — the system carries its hierarchy in weight far more than in size, because the whole
   app is one face. A heading two steps up in weight and one in size reads ranked; two steps up
   in size alone reads shouty on a phone, which is the failure the shipped look already had.

   RADIUS — five steps plus a pill. The rule is CONTAINMENT: a thing inside another thing takes
   the next step down, so a chip (--r-sm) inside a card (--r-lg) never looks loose.

   ELEVATION — four steps and they are a sequence, not a set. --e-1 is a thing resting ON the
   page, --e-2 a thing raised off it, --e-3 a thing floating OVER it. Nothing in this app is
   allowed --e-3 unless it is genuinely over other content: the find panel and the flash bar.
   ============================================================================================ */
:root{
  /* THE REFERENCE'S OWN SCALE: 4 8 12 16 20 24 32 40 48. `--sp-8` was 44 and is now 40, which is
     his eighth step. THERE IS NO NINTH. 48 is in his list and nothing in this shell is 48px apart
     - the largest gap any of it actually wants is 40 - and a step declared for completeness is a
     step somebody reaches for because it exists rather than because the layout asked for it. It
     goes in the day something needs it. */
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px;
  --sp-5:20px; --sp-6:24px; --sp-7:32px; --sp-8:40px;

  /* THE PAGE TITLE IS THE ONE STEP THAT CHANGES WITH THE DEVICE, and it is the only type token
     restated in a media query (at the 768 block below): the reference asks for 24-26 on a phone
     and 28-32 on a desktop, which is a bigger jump than one value can serve. Everything else is
     one value at every width - a scale that changes underneath you is not a scale.

     `--t-micro` STAYS AT 10px AND THE BRIEF ASKS FOR 11-12. It is the uppercase label, and the
     thing it labels most often is the stage chip row - which the owner ruled on separately and
     more recently (2026-09-17, looking at five Sales chips wrapped onto two lines): "بدي ياهن
     على صف واحد و بخط اصغر". One line and SMALLER. A general instruction about badge sizes does
     not overturn a specific one about this row, so the row keeps the size he asked for. */
  --t-display:25px; --t-title:19px; --t-body:15px;
  --t-sm:13px; --t-xs:12px; --t-micro:10px;
  --ls-display:-.024em; --ls-title:-.018em; --ls-caps:.085em;

  --w-reg:450; --w-med:560; --w-semi:650; --w-bold:750; --w-heavy:800;

  /* CONTROLS 8-10, BUTTONS 10-12, CARDS 14-16, LARGE CONTAINERS 18-20 - the reference's own four
     bands. Only `--r-md` moved (13 to 12) to bring the button band inside its range. */
  --r-xs:8px; --r-sm:10px; --r-md:12px; --r-lg:16px; --r-xl:20px; --r-pill:999px;

  /* THE REFERENCE GIVES TWO SHADOWS AND THIS IS THEM: `0 2px 8px rgba(15,23,42,.05)` for a thing
     raised off the page, `0 8px 24px rgba(15,23,42,.08)` for floating navigation. The old `--e-2`
     carried a SECOND 26px-blur layer and `--e-3` a 60px one, which is the "oversized shadow" the
     brief asks to stop. `--e-1` keeps a single hairline layer for a thing merely resting. The
     sequence still means what it meant: on the page, off the page, over the page. */
  --e-1:0 1px 2px rgba(15,23,42,.04);
  --e-2:0 2px 8px rgba(15,23,42,.05);
  --e-3:0 8px 24px rgba(15,23,42,.08);

  /* THE SIDEBAR IS BRAND CHROME AND DOES NOT INVERT. It is the one surface in this app that is
     the same navy in light, dark and auto - that is what makes it read as the company's own edge
     of the window rather than as a panel of the page. It is still declared in all three theme
     states (below), because dark gets a DEEPER navy: the light palette's #12203B glows against a
     #080B11 page, and a brand colour that glows is a brand colour nobody keeps on. */
  /* THREE WIDTHS AND NOT TWO, because the reference asks for two different labelled rails: a
     tablet is 180-210 and a desktop 200-230. `--side-w-md` is the tablet's, `--side-w` the
     desktop's. There is no third: the 76px icon-only rail this file drew from 700px went with the
     2026-09-18 round, because no band in the reference's scheme wants an unlabelled one. */
  --side-w:216px; --side-w-md:192px;
  --side-bg:#0B1F44; --side-bg2:#081936;
  --side-ink:#FFFFFF; --side-ink2:#A7B6D0; --side-ink3:#8A9CBC;
  --side-hover:rgba(255,255,255,.08); --side-rule:rgba(255,255,255,.10);
  /* THE ACTIVE DOOR IS GOLD AND ITS INK IS THE RAIL'S OWN NAVY - the brief's "gold active
     treatment", and the one place in this app where gold carries a state rather than decorating
     one. It works because both halves are on the rail: #0B1F44 on #D4A15A is 6.99:1. The same
     gold as ink on a white page would be 2.32:1, which is why there is no such rule anywhere. */
  --side-on-bg:var(--gold); --side-on-ink:var(--goldOn);
  --side-mark:rgba(212,161,90,.30);
}
/* dark is a selected palette, not an inversion */
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    /* DARK IS NAVY-BLACK AND NOT NEUTRAL BLACK - #0A0F1C rather than #080B11. The identity is
       navy in both themes; a neutral grey page under a navy rail reads as two designs. */
    --paper:#0A0F1C; --paper2:#0F1626; --card:#141C2E;
    --ink:#F1F5F9; --ink2:#A9B6CA; --ink3:#8494AB;
    --rule:rgba(255,255,255,.10); --rule2:rgba(255,255,255,.06);
    --brass:var(--pri2); --brassInk:var(--priInk);
    --late:#FF7585; --lateBg:rgba(255,117,133,.14);
    --risk:#F2B95B; --riskBg:rgba(242,185,91,.13);
    --ok:#51D0A3; --okBg:rgba(81,208,163,.13);
    --wait:#A9B1BE; --waitBg:rgba(169,177,190,.11);
    --ship:#68AFFF; --shipBg:rgba(104,175,255,.14);
    /* THE FIFTH PAIR, DARK - see the long note in the light block above for both measurements. */
    --vio:#C4A9F6; --vioBg:rgba(196,169,246,.14);
    --s1:#A8C4EC; --s2:#4A8FD8; --grid:rgba(255,255,255,.07);
    --navBg:rgba(10,15,28,.80);
    --panel:rgba(255,255,255,.035); --panelRule:rgba(255,255,255,.07);
    --brassBg:var(--priBg);
    --shadow:var(--e-2);

    --shell:#0A0F1C; --surface:#141C2E; --surface2:#0F1626;
    --line:rgba(255,255,255,.10); --line2:rgba(255,255,255,.06);
    /* NAVY CANNOT BE THE SAME NAVY HERE. #0B1F44 on a #0A0F1C page is a brand colour nobody can
       see; `--pri` lifts to a mid navy that still takes white at 10.06:1, and `--priInk` becomes
       a pale blue because ink on a dark card has to go the other way. */
    --pri:#25426F; --pri2:#375C93; --priInk:#A8C4EC; --priBg:#1B2A45;
    --priRule:rgba(168,196,236,.34);
    --priGrad:linear-gradient(135deg,#16325F,#25426F); --priOn:#FFFFFF;
    /* GOLD LIFTS, AND IN THIS THEME IT WOULD ALSO BE LEGIBLE AS INK - #E7BE7E is 9.77:1 on a
       card. Nothing takes it up: a colour that may be text in one theme and not the other is a
       rule that has to be written twice and will be got wrong once. */
    --gold:#E7BE7E; --goldOn:#0B1F44;
    --topBg:rgba(10,15,28,.86); --topRule:rgba(255,255,255,.08);

    --side-bg:#0A1B3C; --side-bg2:#07142C;
    --side-ink:#FFFFFF; --side-ink2:#A7B6D0; --side-ink3:#8A9CBC;
    --side-hover:rgba(255,255,255,.07); --side-rule:rgba(255,255,255,.08);
    --side-on-bg:var(--gold); --side-on-ink:var(--goldOn);
    --side-mark:rgba(212,161,90,.26);

    --e-1:0 1px 2px rgba(0,0,0,.4);
    --e-2:0 2px 8px rgba(0,0,0,.4);
    --e-3:0 8px 24px rgba(0,0,0,.5);
  }
}
:root[data-theme="dark"]{
  /* THE SAME PALETTE AS THE BLOCK ABOVE, VERBATIM. Two copies because one answers
     `prefers-color-scheme` and one answers the reader's stored choice; the reasons for each
     value are written once, above. */
  --paper:#0A0F1C; --paper2:#0F1626; --card:#141C2E;
  --ink:#F1F5F9; --ink2:#A9B6CA; --ink3:#8494AB;
  --rule:rgba(255,255,255,.10); --rule2:rgba(255,255,255,.06);
  --brass:var(--pri2); --brassInk:var(--priInk);
  --late:#FF7585; --lateBg:rgba(255,117,133,.14);
  --risk:#F2B95B; --riskBg:rgba(242,185,91,.13);
  --ok:#51D0A3; --okBg:rgba(81,208,163,.13);
  --wait:#A9B1BE; --waitBg:rgba(169,177,190,.11);
  --ship:#68AFFF; --shipBg:rgba(104,175,255,.14);
  --vio:#C4A9F6; --vioBg:rgba(196,169,246,.14);
  --s1:#A8C4EC; --s2:#4A8FD8; --grid:rgba(255,255,255,.07);
  --navBg:rgba(10,15,28,.80);
  --panel:rgba(255,255,255,.035); --panelRule:rgba(255,255,255,.07);
  --brassBg:var(--priBg);
  --shadow:var(--e-2);

  --shell:#0A0F1C; --surface:#141C2E; --surface2:#0F1626;
  --line:rgba(255,255,255,.10); --line2:rgba(255,255,255,.06);
  --pri:#25426F; --pri2:#375C93; --priInk:#A8C4EC; --priBg:#1B2A45;
  --priRule:rgba(168,196,236,.34);
  --priGrad:linear-gradient(135deg,#16325F,#25426F); --priOn:#FFFFFF;
  --gold:#E7BE7E; --goldOn:#0B1F44;
  --topBg:rgba(10,15,28,.86); --topRule:rgba(255,255,255,.08);

  --side-bg:#0A1B3C; --side-bg2:#07142C;
  --side-ink:#FFFFFF; --side-ink2:#A7B6D0; --side-ink3:#8A9CBC;
  --side-hover:rgba(255,255,255,.07); --side-rule:rgba(255,255,255,.08);
  --side-on-bg:var(--gold); --side-on-ink:var(--goldOn);
  --side-mark:rgba(212,161,90,.26);

  --e-1:0 1px 2px rgba(0,0,0,.4);
  --e-2:0 2px 8px rgba(0,0,0,.4);
  --e-3:0 8px 24px rgba(0,0,0,.5);
}
/* `color-scheme` is what makes the browser's own furniture - form controls, scrollbars, the
   rubber-band overscroll on iOS - match the theme. Without it a dark page has a white gutter
   above it the moment the owner scrolls past the top. */
:root{color-scheme:light dark}
:root[data-theme="dark"]{color-scheme:dark}
:root[data-theme="light"]{color-scheme:light}

*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;padding:0}
body{
  background:var(--zinc); color:var(--ink); font-family:var(--sans);
  font-size:15px; line-height:1.45; -webkit-font-smoothing:antialiased;
  /* Room for the floating bottom nav, plus the home-bar inset on a modern iPhone. */
  padding:0 0 calc(88px + env(safe-area-inset-bottom));
}
.po,.jcode,.drow b,.mhb .mny,.days,.flag,.listtog em,.kv b,.stnum{font-variant-numeric:tabular-nums}
:focus-visible{outline:2px solid var(--cool);outline-offset:2px;border-radius:2px}
.wrap{max-width:760px;margin:0 auto;padding:0 14px}
a{color:var(--brassInk)}

/* Screen-reader-only. Used for the skip link and for labels a sighted reader gets from layout. */
.vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}
.skip{position:absolute;left:-9999px;top:0;z-index:100;background:var(--card);color:var(--ink);
  padding:10px 14px;border:1px solid var(--rule);border-radius:12px}
/* IT LANDS BESIDE THE SIDEBAR AND NOT ON TOP OF IT. `z-index:100` puts this above the rail's 40,
   so before the offset a keyboard reader's very first Tab painted a white card over the navy
   wordmark. The two widths are the same tokens `body`'s own padding-left uses, so the skip link
   cannot drift out of step with how much room the rail takes. */
.skip:focus{left:12px;top:12px}
@media(min-width:768px){ .skip:focus{left:calc(var(--side-w-md) + var(--sp-5))} }
@media(min-width:1200px){ .skip:focus{left:calc(var(--side-w) + var(--sp-6))} }

/* ============================================================================================
   THE TOP BAR.

   IT STICKS, WHICH IS THE ONE BEHAVIOUR THIS BAR GAINED IN THE 2026-09-17 SHELL. The search, the
   theme and the way out are chrome, and chrome that scrolls away is chrome you scroll back up for.
   `--topBg` is translucent so the page shows through it as it passes underneath, with an opaque
   fallback for engines with no backdrop-filter (see the @supports below).

   THE BORDER AND THE STICKY ARE SCOPED WITH `:has(.topbar)` BECAUSE `.masthead` HAS TWO JOBS.
   The chromed pages put the control row in it; the four pages with no chrome at all - sign in,
   first-admin setup, the two confirmations - put an <h1> in it and nothing else, and a hairline
   under a sign-in title with no bar above it is a rule with nothing to separate. Where `:has()`
   is not supported the bar is simply not sticky, which is a degradation and not a break.
   ============================================================================================ */
/* HOW MUCH OF THE TOP OF THE WINDOW THE STICKY MASTHEAD OWNS - 63px, measured in a real browser
   at 390, 834, 1024 and 1280, where it is the same at all four. Anything that sticks BELOW it
   offsets by this; today that is the production board's details panel and nothing else. It is a
   custom property rather than a literal so that the bar and the things that have to clear it
   cannot drift apart silently. */
:root{--stick:63px}
.masthead{background:var(--shell);color:var(--ink);padding:var(--sp-4) 0 var(--sp-3)}
/* `margin-bottom` IS THE FIX FOR A GAP THAT ONLY EXISTED WHEN AN OPTIONAL ELEMENT WAS THERE.
   The clearance under this rule used to be paid for by `.backrow`'s top margin - so a page WITH a
   breadcrumb had 36px between the hairline and the page header's tile, and a page without one had
   ZERO: on `/sales`, `/admin`, `/production` and `/find` the purple tile's top edge and its glow
   sat on the rule (measured at 390 and at 1280 on 2026-09-17). The masthead is the element whose
   rule is being crowded, so the masthead owns the clearance, and it owns it whatever follows -
   a crumb, a chip row, or the page header itself. `.backrow` gives back the 8px it used to add. */
.masthead:has(.topbar){position:sticky;top:0;z-index:30;border-bottom:1px solid var(--topRule);
  background:var(--topBg);padding:var(--sp-3) 0;margin-bottom:var(--sp-3)}
@supports (backdrop-filter:blur(8px)) or (-webkit-backdrop-filter:blur(8px)){
  .masthead:has(.topbar){-webkit-backdrop-filter:saturate(180%) blur(14px);
    backdrop-filter:saturate(180%) blur(14px)}
}
/* ONE LINE, AT EVERY WIDTH - the owner's instruction of 2026-09-18. It wrapped, and on a phone
   the wordmark and the search took the first line while the account pill took a second, which
   cost the masthead a whole extra row on every page of the app. The account's role chip moved
   into the fold to make the room (see `layout.js`); the name itself may shrink and ellipse, which
   is the last thing that gives because a name is the one part a reader recognises at a glance. */
.topbar{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:nowrap}
.topbar .sp{flex:1 1 auto}

/* THE WORDMARK, PHONE ONLY. From 700px up the sidebar carries the company's name and this is
   `display:none` - see the rail block near the foot of this file. One element, never two copies. */
.brandm{flex:0 0 auto;text-decoration:none;font-family:var(--ui);font-size:var(--t-sm);
  font-weight:var(--w-heavy);letter-spacing:.02em;color:var(--ink);white-space:nowrap}

.seg{display:inline-flex;gap:2px;padding:3px;border:1px solid var(--line);
  border-radius:var(--r-md);background:var(--surface)}
.seg a{appearance:none;text-decoration:none;display:inline-flex;align-items:center;
  border:0;background:none;color:var(--ink3);cursor:pointer;
  font-family:var(--ui);font-size:var(--t-micro);font-weight:var(--w-semi);padding:0 var(--sp-2);
  min-height:30px;border-radius:var(--r-xs);white-space:nowrap}
.seg a:hover{color:var(--ink)}
.seg a[aria-pressed="true"]{background:var(--pri);color:#fff}
.eye{flex:0 0 auto;text-decoration:none;display:inline-flex;align-items:center;gap:6px;
  border:1px solid var(--line);border-radius:var(--r-md);background:var(--surface);
  color:var(--ink2);cursor:pointer;font-family:var(--ui);font-size:var(--t-xs);
  font-weight:var(--w-semi);padding:0 var(--sp-3);min-height:38px;white-space:nowrap}
.eye:hover{color:var(--ink);border-color:var(--priRule)}
.eye[aria-pressed="true"]{background:var(--priBg);border-color:var(--priRule);color:var(--priInk)}

/* ---------- who is signed in, and the admin doors, behind one fold ----------
   The whole block was a flat row of text and links until 2026-09-17 and took two lines of a
   390px masthead on its own; it is a <details> now for the reason `findBox` is one - a real
   button, no script, under a policy that forbids inline script. See `prefsBar` in views/layout.js
   for what is inside it and why every node in it is the node that was there before. */
/* ==============================================================================================
   `0 1 auto` AND NOT `0 0 auto`, 2026-09-23 - THE NAME IS WHAT GIVES, WHICH THIS FILE ALREADY
   SAID AND THIS ROW COULD NOT DO.

   The block above promises that in a `nowrap` bar "the name itself may shrink and ellipse, which
   is the last thing that gives". It could not: every child of `.topbar` was `flex:0 0 auto`, so
   nothing gave at all and a bar that ran out of room simply overflowed the document.

   IT RAN OUT OF ROOM FOR THE FIRST TIME ON 2026-09-23, when `Show money` began standing in the
   corner while the cover is ON (see `prefsBar` in views/layout.js). At 390 that is 102px more
   than the bar had, and the page gained 16px of sideways scroll - measured, not guessed. With
   this the account fold gives those 16 back out of an 82px name, `.whonm`'s own
   `overflow:hidden;text-overflow:ellipsis` does the rest, and `Anas Rahmon` reads `Anas Rahmo…`
   in the one state the reader is trying to leave.

   IT IS INERT EVERYWHERE ELSE. A flex item only shrinks when the line overflows, and with the
   cover off the bar is exactly what it was - same four children, same widths, nothing to give.
   ============================================================================================== */
.who{flex:0 1 auto;position:relative}
/* `max-width:100%` IS THE SECOND HALF OF THE RULE ABOVE AND WITHOUT IT THE FIRST DOES NOTHING.
   `.who` shrank to 94px at 390 with the cover on and this summary went on measuring 125: an
   inline-flex box sizes itself shrink-to-fit against its own MIN-CONTENT floor, and the name's
   `white-space:nowrap` puts that floor at the whole name. `min-width:0` lets the NAME shrink
   inside this box; this is what lets the BOX shrink inside its parent. Measured either way. */
.whoopen{list-style:none;display:inline-flex;align-items:center;gap:var(--sp-2);
  min-width:0;max-width:100%;
  padding:0 var(--sp-2) 0 var(--sp-3);min-height:38px;border:1px solid var(--line);
  border-radius:var(--r-md);background:var(--surface);cursor:pointer;
  font-family:var(--ui);font-size:var(--t-xs);color:var(--ink2);white-space:nowrap}
.whoopen::-webkit-details-marker{display:none}
.whoopen::marker{content:""}
.whoopen:hover{border-color:var(--priRule);color:var(--ink)}
.who[open] > .whoopen{border-color:var(--priRule);color:var(--ink)}
.whonm{font-weight:var(--w-semi);color:var(--ink);max-width:11ch;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap;min-width:0}
.who .role{font-size:9px;font-weight:var(--w-bold);letter-spacing:var(--ls-caps);
  text-transform:uppercase;padding:3px 7px;border-radius:var(--r-pill);background:var(--priBg);
  color:var(--priInk)}
.who{min-width:0}
/* The role, on its own line at the top of the fold - one copy, every width. */
.whorole{margin:0 0 var(--sp-2)}
.whochev{display:inline-flex;flex:0 0 auto;color:var(--ink3);transition:transform .16s ease}
.whochev svg{width:13px;height:13px;display:block}
.who[open] .whochev{transform:rotate(90deg)}
/* THE PANEL. Out of flow and right-aligned, so opening it never reflows the bar it hangs off and
   never pushes the page down - the same rule `.findout` follows, for the same reason, and it
   takes `--paper` for the same reason too: a translucent card floating over content is two
   surfaces where the reader can only see one. */
.whoout{position:absolute;z-index:40;top:calc(100% + 8px);right:0;min-width:190px;
  display:flex;flex-direction:column;align-items:stretch;gap:2px;padding:var(--sp-2);
  background:var(--paper);border:1px solid var(--line);border-radius:var(--r-lg);
  box-shadow:var(--e-3)}
.whoout a,.whoout .linkbtn{display:flex;align-items:center;min-height:40px;padding:0 var(--sp-3);
  border-radius:var(--r-sm);font-family:var(--ui);font-size:var(--t-sm);
  font-weight:var(--w-med);color:var(--ink2);text-decoration:none;text-align:left}
.whoout a:hover,.whoout .linkbtn:hover{background:var(--paper2);color:var(--ink)}
.whoout form{display:block}
/* THE THEME SWITCH AND HIDE IQD, INSIDE THE PANEL - moved off the masthead 2026-09-18 (see
   `prefsBar` in views/layout.js). They are controls rather than destinations, so they sit together
   on one line above the admin links instead of taking a 40px row each: the panel is opened by a
   thumb on a 390px phone and three full-width rows of preference would push Sign out off it.
   `.seg` and `.eye` keep every rule they had in the bar; this only lays them out. */
.prefrow{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap;
  padding:var(--sp-1) var(--sp-1) var(--sp-2);border-bottom:1px solid var(--line2);
  margin-bottom:var(--sp-1)}
.prefrow .eye{flex:1 1 auto;justify-content:center}
.whoout .linkbtn{width:100%;color:var(--late);border-top:1px solid var(--line2);
  border-radius:0 0 var(--r-sm) var(--r-sm);margin-top:var(--sp-1);padding-top:var(--sp-1)}

/* ---------- the search control, and the panel it opens ----------
   THE OWNER'S ASK OF 2026-09-15: a search icon in the top corner of every page, a small field
   behind it, and results as you type. The control is a <details> so that the icon is a real button
   with no script at all (see `findBox` in views/layout.js); these rules are the whole of what the
   stylesheet owes it, and they are the FIRST bytes added to this file since the quotation slice.

   IT SITS LAST IN .topbar, AFTER .sp, SO IT IS IN THE RIGHT-HAND CORNER. Closed it is a 38px
   square, the same height as .eye beside it so the row does not grow. Open it takes a full basis
   and .topbar's own flex-wrap drops it to a line of its own, which is what turns a 38px square
   into a comfortable field rather than a sliver between two controls. */
.findbox{flex:0 0 auto;position:relative}
/* OPEN, IT TAKES A ROW OF ITS OWN AND LAYS THE ICON, THE FIELD AND THE BUTTON ALONG IT. Without
   the `display:flex` the summary is a block and the form sits UNDER it, which wastes a line and
   puts a lone 38px icon above a field - measured in a real browser, not reasoned about. */
.findbox[open]{flex:1 1 100%;display:flex;align-items:center;gap:9px}
.findbox[open] > .findform{flex:1 1 auto;margin-top:0}
/* NO MARKER. `list-style` is what Chrome and Firefox read; the WebKit pseudo-element is what
   Safari on his iPad reads, and this project has paid for that difference before. Both, always. */
.findopen{list-style:none;display:inline-flex;align-items:center;justify-content:center;
  gap:var(--sp-2);width:38px;height:38px;border:1px solid var(--line);
  border-radius:var(--r-md);background:var(--surface);color:var(--ink2);cursor:pointer}
.findopen::-webkit-details-marker{display:none}
.findopen::marker{content:""}
.findopen:hover{color:var(--ink);border-color:var(--priRule)}
.findbox[open] > .findopen{color:var(--ink);border-color:var(--priRule)}
/* THE WORDS INSIDE THE CLOSED BUTTON, which is what turns a 38px icon into the field the
   reference draws in this corner. Hidden on a phone, where the corner is 38px wide and the icon
   is the whole control; hidden again the moment the fold is open, because the real input beside
   it then carries the same words as its placeholder and saying them twice is noise. */
.findlab{display:none;font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-med);
  color:var(--ink3);white-space:nowrap}
/* ============================================================================================
   1100 AND NOT 820, AND THE NUMBER WAS MEASURED RATHER THAN CHOSEN.

   The labelled sidebar starts at 820 so that an iPad in portrait gets it. That leaves 834 - 224
   of sidebar - 20 of gutter - 28 of `.wrap` padding = 562px of top bar. The four controls in it
   want 300 (search pill) + 150 (theme) + 85 (Hide IQD) + 165 (name) + 24 of gaps = 724, so the
   row wrapped and the bar was two lines tall on the one device this app is most used on.

   At 1100 the same sum has 810px to sit in and fits on one line with room to spare, so the search
   wears its words from there up and stays the 38px icon below it - which is exactly what it is on
   a phone, and is one control behaving one way rather than a third state to learn.
   ============================================================================================ */
@media(min-width:1100px){
  .findbox:not([open]) > .findopen{width:auto;min-width:300px;justify-content:flex-start;
    padding:0 var(--sp-3)}
  .findbox:not([open]) .findlab{display:inline}
}
@media(min-width:768px){
  /* OPEN, IT STOPS TAKING THE WHOLE ROW. A 100% basis is right on a phone, where the field needs
     every pixel and the bar has room to wrap; on a laptop it pushed the name onto a second line
     for as long as the fold was open. Bounded, it opens in place. (It used to push the theme
     control and Hide IQD too; those are in the account panel now.) 768 rather than 820 so that it
     changes at the same width everything else in the shell does. */
  .findbox[open]{flex:0 1 460px}
}
.findform{display:flex;gap:8px;align-items:center;margin-top:9px}
.findq{flex:1 1 auto;min-width:0;font:inherit;font-size:16px;padding:0 13px;height:42px;
  border:1px solid var(--rule);border-radius:13px;background:var(--card);color:var(--ink)}
.findq::placeholder{color:var(--ink3)}
.findq:focus{outline:2px solid var(--brassInk);outline-offset:1px}
/* THE PANEL THE SCRIPT PAINTS INTO. Out of flow, so a result list never pushes the page down and
   never reflows the masthead; capped at 60vh and scrolled, because six sections of eight rows is
   taller than a phone. Its children are `.rows` boxes, which already carry the card surface, so
   this element is a positioner and nothing else - it has no background of its own to go stale. */
/* AN OPAQUE SHEET, AND THAT IS NOT A STYLE CHOICE. `--card` is `rgba(255,255,255,.78)` - it is
   designed to sit ON the page with the page showing faintly through it, which is right for a card
   in the flow and wrong for a panel floating over one: the first render of this put eight order
   rows over the Today tiles and the two were legible through each other. `--paper` is the opaque
   page colour in both themes, so the panel is a surface rather than a tint. */
.findout{position:absolute;z-index:40;top:calc(100% + 8px);left:0;right:0;
  max-height:60vh;overflow-y:auto;display:flex;flex-direction:column;gap:10px;
  padding:12px;background:var(--paper);border:1px solid var(--rule);border-radius:16px;
  box-shadow:var(--shadow)}
/* AND THE LISTS INSIDE IT DROP THEIR OWN SHADOW, because a card inside a floating sheet is two
   surfaces where the reader can only see one. They keep their rule and their radius. */
.findout .rows{box-shadow:none}
.findout[hidden]{display:none}
/* A SECTION, ON THE PAGE AND IN THE PANEL - one class, both renderers. `section{margin:0 0 26px}`
   is right under a page heading and far too much inside a scrolling panel, so this overrides the
   gap and lets the flex `gap` above own the spacing in the panel. */
.fsec{margin:0 0 18px}
.findout .fsec{margin:0}
.findout .shead{margin-bottom:6px}
.findout .shead h3{font-family:var(--ui);font-size:13px;font-weight:760;letter-spacing:-.02em;
  margin:0;color:var(--ink)}
/* THE LINE THAT SAYS RESULTS WERE CUT. Inside the `.rows` surface, under the last row, wearing the
   same top rule every row wears so it reads as part of the list rather than as a note beside it. */
.fcut{padding:9px 14px;border-top:1px solid var(--rule2);font-size:11.5px;color:var(--ink3)}
.linkbtn{appearance:none;background:none;border:0;padding:0;font:inherit;cursor:pointer;
  color:var(--brassInk);text-decoration:underline;min-height:32px}

/* ---------- section headings ---------- */
section{margin:0 0 var(--sp-6)}
.shead{display:flex;align-items:center;gap:9px;flex-wrap:wrap;margin-bottom:9px}
/* `overflow-wrap:anywhere` AND IT IS LOAD-BEARING, NOT TIDINESS. Several headings in this app end
   in an identifier a machine made - `Costing — AZD_2026_017_Sticker_For_Mall` is one, the client
   page's `CODE — Name` is another - and an underscored code is ONE unbreakable word to a line
   breaker. At the old 17px it happened to fit a 390px phone; at the page header's 26px it did not,
   and the whole document went 474px wide with the bottom nav dragged off the right edge with it
   (measured on `/pricing/<key>` at 390 before this rule). `anywhere` rather than `break-word`
   because it also shrinks the element's MIN-CONTENT width, which is what stops the flex row it
   sits in from overflowing in the first place. */
.shead h2{font-family:var(--ui);font-size:var(--t-title);font-weight:var(--w-bold);
  letter-spacing:var(--ls-title);margin:0;color:var(--ink);min-width:0;overflow-wrap:anywhere}
.shead .n{font-family:var(--ui);font-size:11px;font-weight:700;color:var(--ink2);
  display:inline-flex;min-width:25px;height:25px;padding:0 7px;align-items:center;
  justify-content:center;border-radius:999px;background:var(--paper2)}
.sub{font-size:var(--t-xs);line-height:1.52;color:var(--ink3);margin:-2px 0 var(--sp-3);
  max-width:62ch}

/* ============================================================================================
   THE PAGE HEADER - the tile, the page's name, and the one line under it.

   IT WRAPS `shead` AND DOES NOT REPLACE IT. See `pagehead` in views/layout.js: the heading, the
   count and the sentence are the same three elements every section on every page already uses,
   so the rules above still own how they are SET and these rules only own how the page-level one
   is SIZED and what stands beside it. A page that passes no sentence gets no `.sub`, here as
   everywhere else - the header is not a place to put explanatory text back.

   THE TILE TAKES THE PRIMARY TINT AND NOT A STATUS TONE. Every other tinted square in this app
   (`.tico`) means something - late, ready, shipped - and a page header means none of those. One
   tone, the brand's, on every page, so the tile reads as identity and never as a state.
   ============================================================================================ */
/* `flex-wrap` AND `row-gap` ARE FOR THE ACTION SLOT AND NOTHING ELSE. On a phone `.pact` takes a
   basis of 100% and drops to a line of its own under the title, which is the reference's own phone
   layout - a full-width primary button below the heading. From 768 up it stops wrapping and sits
   at the right-hand end of the header's own line. A page with no action never wraps at all. */
.phead{display:flex;align-items:flex-start;flex-wrap:wrap;gap:var(--sp-3);margin:0 0 var(--sp-5)}
.ptile{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;border-radius:var(--r-md);background:var(--priGrad);color:var(--priOn);
  box-shadow:var(--e-1)}
.ptile svg{width:22px;height:22px;display:block}
.ptx{min-width:0;flex:1 1 0;padding-top:1px}
.ptx .shead{margin-bottom:2px;gap:var(--sp-3)}
.ptx .shead h2{font-size:var(--t-display);font-weight:var(--w-heavy);
  letter-spacing:var(--ls-display);line-height:1.15}
.ptx .sub{margin:0;font-size:var(--t-sm);color:var(--ink2)}
/* THE ONE THING YOU ARE MEANT TO PRESS, BESIDE THE NAME OF THE PAGE. `pagehead`'s optional sixth
   argument renders into this; a page that passes none emits no `.pact` at all, so these rules are
   inert on thirty-eight of the thirty-nine destinations. */
.pact{flex:1 0 100%;display:flex}
.pact > .make{flex:1 1 auto;min-height:48px;font-size:var(--t-sm);border-radius:var(--r-md)}
.headline{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  flex-wrap:wrap;margin-bottom:14px}
.day{font-family:var(--ui);font-size:20px;font-weight:800;letter-spacing:-.03em}
.live{display:inline-flex;align-items:center;gap:7px;font-family:var(--ui);font-size:11px;
  color:var(--ink3)}
.live .dot{width:7px;height:7px;border-radius:50%;background:var(--ok);flex:0 0 auto}

/* ============================================================
   THE ROW MARK, THE CHEVRON, THE TEXT COLUMN
   Three classes carry the whole rhythm and are shared by every group of the card:
     .tico  the rounded tinted square with one glyph in it
     .ttx   the text column beside it - a small label over a large value
     .tchev the right-hand chevron on anything that opens
   ============================================================ */
.tico{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;border-radius:11px;background:var(--waitBg);color:var(--ink2)}
.tico svg{width:18px;height:18px;display:block}
.tico.t-ok{background:var(--okBg);color:var(--ok)}
.tico.t-late{background:var(--lateBg);color:var(--late)}
.tico.t-risk{background:var(--riskBg);color:var(--risk)}
.tico.t-ship{background:var(--shipBg);color:var(--ship)}
.tico.t-wait{background:var(--waitBg);color:var(--ink2)}
.tico.t-brass{background:var(--brassBg);color:var(--brassInk)}
/* THE SEVENTH TONE, 2026-09-22 - the Sales dashboard's creative-request square. `--vio`/`--vioBg`
   is the pair declared at the head of this file with both of its measurements; this is the only
   rule in the app that reaches either of them. */
.tico.t-vio{background:var(--vioBg);color:var(--vio)}
/* flex-basis 0, not auto. Every row carrying a mark is a WRAPPING flex line, and a wrapping line
   decides where to break from each item's content width BEFORE it shrinks anything - so a text
   column sized from its content drops onto its own line under the mark instead of sitting beside
   it. A zero basis makes the column's hypothetical width zero, so it always shares the mark's
   line and simply takes what is left. */
.ttx{display:flex;flex-direction:column;align-items:flex-start;min-width:0;flex:1 1 0;
  gap:1px;text-align:left}
.tnm{min-width:0;overflow-wrap:anywhere}
.tchev{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:20px;height:20px;color:var(--ink3);transition:transform .16s ease,color .12s ease}
.tchev svg{width:15px;height:15px;display:block}
details[open] > summary .tchev{transform:rotate(90deg)}
summary:hover .tchev{color:var(--brassInk)}

/* Every <summary> in this file is laid out by hand, so the browser's own disclosure triangle has
   to be turned off in both spellings - `display` for everyone, `::-webkit-details-marker` for
   older Safari, which is the one on the phone this is built for. */
summary{cursor:pointer;list-style:none}
summary::-webkit-details-marker{display:none}
summary::marker{content:""}

/* ============================================================
   THE ORDER CARD
   ============================================================ */
/* THE CARD IS THE QUERY CONTAINER. Every `@container job (...)` below is asking this element how
   wide it turned out, which is the thing syncCardLayout() went to the trouble of measuring. */
/* ============================================================================================
   A COLUMN, SO THE FOOTER CAN BE PINNED TO THE BOTTOM OF IT - §1, 2026-09-26.

   «Keep cards visually consistent in height, with aligned sections and spacing.» Two rules do
   that and this is the first: the card is a flex COLUMN and `.jfoot` takes `margin-top:auto`, so
   on a card that happens to be shorter than the one beside it the slack goes into the gap ABOVE
   the footer rather than leaving the footer floating in the middle of a stretched box. The second
   is `align-items:stretch` on `.jobs` below, which is what makes the boxes the same height in the
   first place.

   A `<details>` LAYS OUT AS A FLEX CONTAINER PERFECTLY WELL and folds exactly as it did: the
   summary is the first flex item, and a closed `<details>` does not generate boxes for the rest
   whatever its `display` is. Nothing about the fold, `folds.js`, `?fold=open` or the `open`
   attribute moved for this.
   ========================================================================================== */
/* ============================================================================================
   AND THE FLEX COLUMN HAS TO REACH INSIDE `::details-content`, OR IT REACHES NOTHING.

   MEASURED, NOT ASSUMED. With `.job{display:flex;flex-direction:column}` and `.jfoot{margin-top:
   auto}` alone, ONE card in a stretched row had its footer at the bottom and the one beside it
   had 54px of empty space under it - both cards 724px tall, both starting at the same y, two
   different footer positions. The reason is that Chrome renders a `<details>`' non-summary
   children inside a single generated `::details-content` box: the card's flex items are the
   SUMMARY and THAT BOX, not `.jfoot`, so `margin-top:auto` on the footer had nothing to push
   against. The card that looked right was the one whose content happened to fill its row exactly.

   SO THE GENERATED BOX IS MADE A FLEX COLUMN THAT GROWS, and the footer is a flex item of it.
   `min-height:0` because a flex item's default `min-height:auto` refuses to shrink below its
   content, which would have made a tall card push its own row taller than the grid meant it to
   be. Browsers that do not generate the box are unaffected: there is nothing for this rule to
   match, and `.jfoot` is then a direct flex item of `.job`, which is the arrangement above.
   ========================================================================================== */
.job::details-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}
.job{position:relative;container-type:inline-size;container-name:job;
  display:flex;flex-direction:column;
  background:var(--card);border:1px solid var(--rule);border-radius:var(--radius);
  padding:0;overflow:hidden;box-shadow:var(--shadow);margin:0 0 16px;
  transition:border-color .22s ease,box-shadow .22s ease}
.job:hover{border-color:var(--priRule)}
/* ============================================================================================
   THE SELECTED CARD - §2's «Keep the selected card highlighted», 2026-09-26.

   THE SERVER PUTS THE CLASS ON when the page is rendered with `?panel=<po>`, and `views/items.js`
   moves it when the panel is opened with no navigation. One class, two paths, so the highlight
   cannot mean one thing with the script running and another with it blocked.

   A RING AND A TINTED EDGE, NOT A BACKGROUND. The card is full of tinted panels already - the
   deadline pair, the delivery box, the preparation chips - and a fill behind all of them would
   fight every one. The ring is drawn with `box-shadow` (an inset one, so it cannot change the
   card's size and cannot shift the grid) and the 3px edge the card already has takes the accent.
   ========================================================================================== */
.job.picked{border-color:var(--pri);box-shadow:inset 0 0 0 2px var(--pri),var(--shadow)}
.job.picked::before{background:var(--pri)}
/* THE EDGE IS A PSEUDO-ELEMENT AND NOT A SPAN, which it was in the original. `<details>` demands
   that `<summary>` be its FIRST child - anything before it makes the browser generate a summary of
   its own and the card stops folding - so the coloured edge cannot be an element here. */
.job::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;z-index:2;
  background:var(--wait)}
.job.t-ok::before{background:var(--ok)}
.job.t-risk::before{background:var(--risk)}
.job.t-late::before{background:var(--late)}

@media(prefers-reduced-motion:no-preference){
  .job{animation:rise .34s cubic-bezier(.2,.7,.3,1) backwards}
  /* The stagger was an inline style="animation-delay:Nms" in the original, which a strict
     style-src forbids. Ten steps, then flat - the same cap the original used. */
  .job:nth-child(1){animation-delay:0ms}   .job:nth-child(2){animation-delay:35ms}
  .job:nth-child(3){animation-delay:70ms}  .job:nth-child(4){animation-delay:105ms}
  .job:nth-child(5){animation-delay:140ms} .job:nth-child(6){animation-delay:175ms}
  .job:nth-child(7){animation-delay:210ms} .job:nth-child(8){animation-delay:245ms}
  .job:nth-child(9){animation-delay:280ms} .job:nth-child(n+10){animation-delay:315ms}
}
@keyframes rise{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:none}}

/* ============================================================================================
   The card lists are a grid so a card can be wide enough for a side panel.

   `auto-fill` OVER A MINIMUM, AND NOT A COLUMN COUNT AT A VIEWPORT WIDTH - changed 2026-09-17 and
   it is a BUG FIX, not a tidy-up. It was `@media(min-width:700px){repeat(2,minmax(0,1fr))}`, which
   asks the WINDOW how many columns there is room for. The window is the wrong thing to ask the
   moment anything else on the page takes width: with the branded sidebar at 224px, an iPad in
   portrait is a 834px window with about 560px of column, and two columns of that is a 270px card.
   Measured at that width before the fix, every project code was ellipsed to `ETE…` and every
   salesperson's name broke mid-word - `Rawa` / `a`. The card's own layout was fine, because the
   card asks a CONTAINER QUERY how wide IT is (see `.job` above) and was correctly drawing its
   narrow form; what was wrong was being handed 270px in the first place.

   360px IS THE CARD'S OWN FIRST BREAKPOINT PLUS ITS PADDING, so a column never lands under the
   width the container queries below are written for. One column under ~750px of available space,
   two above it, decided by the space that is actually there - which is the same answer as before
   on a phone and on a laptop, and the right answer on the iPad where the old rule was wrong.

   `min(360px,100%)` AND NOT A BARE `360px`, WHICH IS THE TRAP IN THIS WHOLE PATTERN. The first
   argument of `minmax()` is a HARD floor: a track told its minimum is 360px stays 360px in a
   292px column and overflows the document rather than shrinking. Written plainly it broke a
   320px phone - an iPhone SE reported a 374px scrollWidth with the card hanging off the right
   edge, found by sweeping eighteen widths rather than by looking at three. Clamped to the
   container it degrades to one full-width column, which is what a narrow phone wanted anyway.
   ============================================================================================ */
/* `align-items:stretch` SINCE 2026-09-26 AND IT WAS `start` - §1's «Keep cards visually
   consistent in height», and it is one word. A grid row is as tall as its tallest item either
   way; `start` left the shorter cards floating at their natural height inside it, which is
   exactly the ragged bottom edge the mock-up does not have. Stretched, every card in a row is the
   same box and `.jfoot`'s `margin-top:auto` puts the slack above the footer. */
.jobs{display:grid;gap:16px;grid-template-columns:repeat(auto-fill,minmax(min(360px,100%),1fr));
  align-items:stretch}
@media(min-width:1200px){ .jobs{gap:20px} }
/* A single card standing on its own - an attention row, a search hit - is full width, so it gets
   the wide treatment even though the list cards beside it would not. */
.jobs.solo{grid-template-columns:minmax(0,1fr)}
/* WHERE THE FULL LIST STARTS, when something else was listed above it. On the production tab the
   `Needs attention` block is a bare heading and a row list with no <section> around it, so before
   this rule its last row and the first card of the main grid sat 16px apart and the reader had
   nothing telling them the inset had ended. It only bites where a list actually precedes the
   grid; where the grid follows the page header the two margins collapse and nothing moves. */
.jrows + .jobs,.rows + .jobs{margin-top:var(--sp-6)}

/* ---------- 1. HEADER: order number, title, metadata, chips ----------
   ONE SHAPE AT EVERY WIDTH: order tile on the left spanning both rows, title / metadata / status
   chip beside it, the fold chevron at the top right, and the deadline pair on its own row aligned
   under the title rather than under the tile.

   FOLDED IS THE CLOSED <details>: the identity row and nothing else. The drawing, the dates, the
   stations, the preparation, the money and the lists all live after the summary, so closing the
   card hides them and nothing else changes. */
.jident{display:grid;align-items:start;gap:8px 12px;padding:15px 15px 13px;
  grid-template-columns:auto minmax(0,1fr) auto;
  grid-template-areas:"no tx gear"}
.job:not([open]) > .jident{padding-bottom:15px}
@container job (min-width:540px){ .jident{gap:9px 13px} }

/* ============================================================================================
   THE THUMBNAIL, WHERE THE ORDER-NUMBER TILE STOOD - §1, 2026-09-26.

   «Small project thumbnail beside the project name, order number, project code, client/location,
   and current status. Keep thumbnails consistently sized.»

   IT REPLACES `.jidno` RATHER THAN SITTING BESIDE IT, and the order number did not disappear: it
   moved up into `.jidhead` as a small `#421` chip level with the status, which is where the
   mock-up puts it. A 62px tile reading `421` over `# ORDER` and a 64px picture beside it would
   have been two square blocks competing for the same corner of a 274px card.

   CONSISTENTLY SIZED IS A FIXED BOX AND AN `object-fit`, WHICH IS THE ONLY WAY IT CAN BE KEPT.
   The images behind these are a photograph somebody took on a phone, a Drive thumbnail of a PDF
   and a 16:10 render of a drawing; nothing about the DATA is consistent. The frame is, and the
   picture is cropped to it - so a card with a tall photograph, a card with a wide one and a card
   with no image at all are the same height. That is half of §1's «Keep cards visually consistent
   in height», and it is the half that data cannot break.

   `aspect-ratio:4/3` AND A WIDTH THAT GROWS WITH THE CARD, in the same three bands the title
   already uses. At 390 the card is one column and the thumbnail is 64px; at a 540 card it is
   96px, which is about the proportion the mock-up draws at 1536.
   ========================================================================================== */
.jthumb{grid-area:no;display:flex;align-items:center;justify-content:center;
  width:64px;aspect-ratio:4/3;border-radius:12px;overflow:hidden;
  background:var(--paper2);border:1px solid var(--panelRule);
  color:var(--ink3);text-decoration:none;flex:0 0 auto}
@container job (min-width:430px){ .jthumb{width:80px;border-radius:13px} }
@container job (min-width:540px){ .jthumb{width:96px;border-radius:14px} }
.jthumb img{display:block;width:100%;height:100%;object-fit:cover;background:var(--sheet)}
/* NOTHING TO SHOW IS A DASHED EMPTY FRAME AND NOT A MISSING ELEMENT - §6's rule at its smallest:
   an order nobody has photographed is not an order with a hole where a photograph would be. */
.jthumb.none{border-style:dashed;background:none}
.jthumb.none svg{opacity:.5}
/* `zoom-in` SAYS THE TAP OPENS THE FULL IMAGE, which is `.shot.draw`'s own cursor for the same
   act. The placeholder is a `<div>` and gets neither this nor the focus ring, because it opens
   nothing. */
a.jthumb{cursor:zoom-in}
a.jthumb:hover,a.jthumb:focus-visible{box-shadow:inset 0 0 0 2px var(--priRule)}

/* THE ORDER NUMBER AND THE STATUS ON ONE LINE, ABOVE THE NAME - the mock-up's own header row.
   A flex row that WRAPS: on a 274px card a long status word takes the second line rather than
   squeezing the number, and the number never breaks. */
.jidhead{display:flex;align-items:center;flex-wrap:wrap;gap:7px;min-width:0;margin:0 0 4px}
.jidhead .po{display:inline-flex;align-items:center;font-family:var(--ui);font-size:12.5px;
  font-weight:800;letter-spacing:-.01em;line-height:1;color:var(--ink);white-space:nowrap;
  padding:5px 9px;border-radius:999px;background:var(--okBg);border:1px solid var(--panelRule)}
.jidhead .po::before{content:"#";margin-right:2px;color:var(--ink3);font-weight:750}
@container job (min-width:540px){ .jidhead .po{font-size:13.5px;padding:5px 11px} }

.jidtx{grid-area:tx;min-width:0}
/* ============================================================================================
   THE PROJECT NAME, AND IT RESERVES ITS SECOND LINE WHETHER IT USES IT OR NOT - §1, 2026-09-26.

   «Keep cards visually consistent in height, with aligned sections and spacing. Long names must
   not overlap other content.» Those two sentences pull against each other and `min-height` is
   what settles them: a one-line name and a two-line name occupy the same box, so the deadline
   row, the station strip and the figure row start at the same y on every card in a row - and a
   name that needs the second line simply wraps into space that was already paid for.

   IT IS A FLOOR AND NOT A CLAMP. A name long enough to need a THIRD line gets one; the card grows
   and the footer stays pinned to the bottom (see `.jfoot`), so the row it is in grows with it and
   nothing overlaps. Truncating the owner's project names to keep a grid tidy is not a trade this
   board gets to make - `overflow-wrap:anywhere` is what stops a 40-character token pushing
   through the thumbnail beside it, and it has been on this rule since the card was written.
   ========================================================================================== */
.jidtx .jtitle{font-family:var(--serif);font-size:18.5px;font-weight:800;letter-spacing:-.032em;
  line-height:1.16;margin:0 0 3px;overflow-wrap:anywhere;min-height:calc(2 * 1.16em)}
@container job (min-width:430px){ .jidtx .jtitle{font-size:20px} }
/* THE CHIP IS PINNED TO THE FIRST LINE of the code/sales pair - level with the project code,
   never dropping to the sales name below it. A grid rather than a flex row: flex let it wrap onto
   a line of its own the moment the two together came within a few pixels of the width, which is
   what it did on a 390px phone. Column one is sized to the text so a longer code gets what it
   needs and the chip follows it, rather than the chip taking a fixed bite from the right. */
/* `.jidrow` STOOD HERE UNTIL 2026-09-26 and is gone with the row it named. It was the grid that
   pinned the status chip level with the project code; the chip is now on `.jidhead` above the
   name, beside the order number, which is where the mock-up puts it and where it no longer has to
   share a line with a code that can be twenty characters long. `.jmeta` is a direct child of
   `.jidtx` now and keeps every rule it had. */
.jmeta{display:flex;flex-direction:column;gap:1px;min-width:0;
  font-size:12px;color:var(--ink2);line-height:1.4;overflow-wrap:anywhere}
.jmeta > .jcode{order:0} .jmeta > .jsub{order:1}
/* ONE LINE, ALWAYS. .jmeta sets overflow-wrap:anywhere for the sales name and the code inherited
   it, so SMP_2026_001 broke after the eleventh character and pushed the chip down. A project code
   is one token: it shrinks, at worst it ellipses, it never breaks in the middle. */
.jcode{font-family:var(--ui);font-size:11.5px;font-weight:750;letter-spacing:.01em;
  color:var(--ink2);overflow-wrap:normal;word-break:keep-all;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;max-width:100%}
.jsub{font-size:12.5px;color:var(--ink3);overflow-wrap:anywhere}
.jtop{display:flex;align-items:center;gap:7px;row-gap:6px;flex-wrap:wrap;position:relative}

.jacts{grid-area:gear;display:flex;align-items:center;gap:1px;align-self:start;justify-self:end}
/* The fold affordance. It is drawn as the card's chevron and it is the <summary>'s own marker in
   spirit, so it must NOT be a button: a button inside a summary is a second activation target on
   top of the one the summary already is. */
.jfold{color:var(--ink3);opacity:.6;padding:6px;min-width:32px;min-height:32px;border-radius:9px;
  display:inline-flex;align-items:center;justify-content:center;
  transform:rotate(0deg);transition:transform .14s ease,opacity .14s ease}
.job[open] > .jident .jfold{transform:rotate(90deg)}
.jident:hover .jfold{opacity:1;background:var(--paper2)}

/* CONFIRMED and the column-I status chip. Two chips can both read "Delivery" on a delivered
   order - one is the order's status (column I), one the delivery status (column V) - so they are
   never given the same treatment: this one is a solid pill up here beside the title, the other is
   a labelled row down in the date rail.
   Sized so BOTH fit one row of the title column at every card width: the pair needs about 207px
   and the column is 198px on a 472px card, which is why the compact size is the base and the
   roomier one is the exception. */
.okmark{display:inline-flex;align-items:center;gap:5px;font-family:var(--ui);font-size:9.5px;
  font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--paper);
  background:var(--go);border-radius:999px;padding:5px 9px;white-space:nowrap}
.stat{display:inline-flex;align-items:center;font-family:var(--ui);font-size:9.5px;font-weight:750;
  letter-spacing:.07em;text-transform:uppercase;padding:5px 9px;border-radius:999px;
  white-space:nowrap;border:1px solid transparent;background:var(--paper2);color:var(--ink2)}
.stat.act{background:var(--shipBg);color:var(--ship)}
.stat.hold{background:var(--riskBg);color:var(--risk)}
.stat.rdy{background:var(--okBg);color:var(--ok)}
.stat.dlv{background:var(--paper2);color:var(--ink2)}
@container job (min-width:540px){
  .okmark,.stat{font-size:10px;padding:5px 11px}
}

/* ---------- THE TWO DEADLINES ----------
   TWO EQUAL BOXES, SIDE BY SIDE, across the whole width of the header. A grid of two halves, not
   a flex row: flex let the deadline take what it needed and pushed the delivery status onto a
   second line. Halves hold their ground and the content inside them stacks instead.

   Red is the date Anas set in Tasks column L, blue is the one sales promised the client in
   Account Management. The countdown is printed ONCE, against whichever of the two the server
   treats as authoritative, because two countdowns disagreeing on one line is how a reader stops
   trusting either. */
.jdd{display:grid;grid-template-columns:1fr 1fr;gap:8px;align-items:stretch;min-width:0;
  margin:0;padding:0 15px 13px}
.jdd > .dline{margin:0;padding:0;min-width:0}
.jdd > .dline.ddpair{display:flex;flex-direction:column;gap:6px}
.jdd .ddb{width:100%}
.ddb{display:inline-flex;align-items:center;gap:9px;padding:6px 11px 6px 8px;
  border-radius:14px;border:1px solid transparent;min-width:0}
.ddb.own{background:var(--lateBg)}
.ddb.sales{background:var(--shipBg)}
.ddb.own.none,.ddb.sales.none{background:none;border:1px dashed var(--rule)}
.ddb > .tico{width:27px;height:27px;flex:0 0 27px;border-radius:9px}
.ddx{display:flex;flex-direction:column;gap:1px;min-width:0}
.ddx i{font-style:normal;font-size:8.5px;font-weight:750;letter-spacing:.09em;
  text-transform:uppercase;white-space:nowrap;color:var(--ink3)}
.ddb.own .ddx i{color:var(--late)}
.ddb.sales .ddx i{color:var(--ship)}
.ddx b{font-size:13.5px;font-weight:800;letter-spacing:-.01em;white-space:nowrap;color:var(--ink)}
.ddb.own .ddx b{color:var(--late)}
.ddb.sales .ddx b{color:var(--ship)}
.ddx b.dlnone{font-style:italic;font-weight:600;color:var(--ink3)}
.ddx em{font-style:normal;font-size:10.5px;font-weight:700;color:var(--ink2);white-space:nowrap}

/* ============================================================================================
   §1's «Show deadline and delivery status on ONE COMPACT ROW» - 2026-09-26, and it is also half
   of «aligned sections».

   THE TWO DEADLINE TILES SIT SIDE BY SIDE FROM A 430px CARD and stack only below it. They were
   stacked at every width, which is correct on a phone and is what made this block the one
   section of the card whose height is decided by the DATA: an order carrying both his deadline
   and a sales promise drew two tiles and the order beside it with only one drew one, so the
   station strips on two cards in the same row started 47px apart. Measured at a 1280 window on
   POs 1042 and 1047.

   THE EMPTY HALF IS LEFT EMPTY AND IS NOT COLLAPSED, which is the whole point: the pair is a
   fixed two-track grid, so a card with one deadline is exactly as tall as a card with two and
   the row below it starts at the same y on both. `.ddb` already draws nothing at all when there
   is no tile to draw, so the space is blank rather than a box with nothing in it.

   AND `2fr 1fr` RATHER THAN `1fr 1fr` ACROSS THE WHOLE ROW, because the row is now three tiles
   and not two: two deadlines and a delivery status. Equal thirds would give the delivery chip as
   much room as a date with a countdown under it, which it does not need.
   ========================================================================================== */
@container job (min-width:430px){
  .jdd{grid-template-columns:2fr 1fr}
  .jdd > .dline.ddpair{flex-direction:row;display:grid;grid-template-columns:1fr 1fr;gap:8px;
    align-items:stretch}
}

/* The delivery half is built to match the deadline half exactly: same tile, same padding, same
   radius, label over value. They are a pair, so they have to read as one. */
.jdd > .dline.deliv{display:flex;flex-wrap:wrap;align-items:center;align-content:center;gap:9px;
  padding:6px 11px 6px 8px;border-radius:14px;background:var(--shipBg);
  border:1px solid transparent}
/* The truck reads as part of the label rather than as a tile of its own: small, inline, on the
   same line as the words. No tinted square behind it - at this size the square would be bigger
   than the glyph inside it. */
.jdd > .dline.deliv .dlh{display:flex;align-items:center;gap:6px;min-width:0}
.jdd > .dline.deliv .dlh > .tico{width:17px;height:17px;flex:0 0 17px;border-radius:0;
  background:none;color:var(--ship)}
.jdd > .dline.deliv .dlh > .tico svg{width:15px;height:15px}
/* The two halves must end up THE SAME HEIGHT. They already stretch to match; the only thing that
   made the delivery box taller was its own label wrapping, because it is set nowrap and at half a
   phone's width it demanded more than was left beside the mark. min-width:0 lets it shrink, and
   it ellipses long before it would ever wrap. */
.jdd > .dline.deliv .dlx{display:flex;flex-direction:column;align-items:flex-start;gap:2px;
  min-width:0;flex:1 1 auto}
.jdd > .dline.deliv .dlx > i{font-style:normal;font-size:8.5px;font-weight:750;
  letter-spacing:.09em;text-transform:uppercase;color:var(--ship);white-space:nowrap;
  max-width:100%;overflow:hidden;text-overflow:ellipsis}
.dstat{display:inline-flex;align-items:center;font-family:var(--ui);font-size:11px;
  font-weight:750;letter-spacing:.05em;text-transform:uppercase;padding:3px 9px;
  border-radius:999px;white-space:nowrap;border:1px solid transparent;
  background:var(--paper2);color:var(--ink2)}
.dstat.none{background:none;border:1px dashed var(--rule);color:var(--ink3);
  text-transform:none;font-weight:600;letter-spacing:0;font-style:italic}
.dstat.dlv{background:var(--okBg);color:var(--ok)}
.dstat.rdy{background:var(--shipBg);color:var(--ship)}
.dstat.hold{background:var(--riskBg);color:var(--risk)}
.dstat.iss{background:var(--lateBg);color:var(--late)}


/* ============================================================================================
   §1's THREE FIGURES, AND §1's FOOTER - the two rows the card now ends with, 2026-09-26.
   ============================================================================================
   «Display Revenue, Actual Cost, and Expected Cost in three clearly aligned columns with
   horizontal labels and readable currency values.»

   THREE EQUAL TRACKS AND NOT `auto-fit`, WHICH IS THE WHOLE OF "clearly aligned". Equal tracks
   mean the second figure starts at the same x on every card in a row whatever the first one says,
   so an eye running down the board reads a column of costs rather than three ragged ones.
   `minmax(0,1fr)` and not `1fr`: a bare `1fr` has a MIN-CONTENT floor, so one long figure would
   push the track wider than its share and quietly unalign the row - the same trap `.jobs` records
   above with `minmax(min(360px,100%),1fr)`.

   ONE COLUMN UNDER 340 OF CARD, WHICH IS THE PHONE. Three currency figures in a 274px card is
   about 80px a column with a 27px mark in each; measured, `USD 16,306` ellipsed on all three.
   Stacked, each one is a full-width labelled row and nothing is cut. Two-across is deliberately
   NOT offered: the three are a set and a 2+1 grid reads as though the third were a different kind
   of figure.

   THE LABEL IS ABOVE THE VALUE AND BOTH RUN HORIZONTALLY - which is what «horizontal labels»
   asks for, and is `.ttx`'s own shape. See the Finance-label note above for what happens when a
   label in this family is allowed to shrink; these do not, for the same reason.
   ========================================================================================== */
.jfig:empty{display:none}
.jfig{display:grid;grid-template-columns:minmax(0,1fr);gap:9px 11px;
  padding:12px 15px;border-top:1px solid var(--panelRule)}
/* 420 OF CONTAINER AND NOT 340, AND THE NUMBER WAS READ OFF THE PANEL RATHER THAN OFF A CARD.
   Three currency columns need about 140px each once the 27px mark and the gap are taken out, and
   the figures here are eight digits wide on the live sheet. At 340 they fitted on a card and did
   not fit in the 358px DETAILS PANEL, which draws the identical row as its financial summary:
   measured there, the labels came back `ACTUAL C…` and `EXPECTED …` and the amounts broke across
   three lines. Stacked is the honest answer under 420 - each figure gets the full width, and on a
   phone that is the only answer there is. */
@container job (min-width:420px){ .jfig{grid-template-columns:repeat(3,minmax(0,1fr))} }
.jfg{display:flex;align-items:center;gap:9px;min-width:0}
.jfg > .tico{width:27px;height:27px;flex:0 0 27px;border-radius:9px}
.jfg > .tico svg{width:15px;height:15px}
.jfg > .ttx{gap:2px;min-width:0}
.jfg i{font-style:normal;font-size:8.5px;font-weight:750;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink3);white-space:nowrap;
  max-width:100%;overflow:hidden;text-overflow:ellipsis}
/* THE FIGURE. `.mny` is the money span every figure in this app is wrapped in - Hide-IQD paints
   over it - and `NIL` is a bare dash with NO `.mny` on it, which is correct: there is no figure
   there to hide. Both are sized here so an absent amount and a present one sit on the same
   baseline and the row does not jump. */
/* ============================================================================================
   A LARGE FIGURE WRAPS AND IS NEVER ELLIPSED - and that is §6's «large financial values» read as
   a layout rule rather than as a caption.

   MEASURED ON THE REAL FIGURES. The first draft set these `white-space:nowrap` with an ellipsis,
   which is what every other one-line label on this card does. At a 1280 window the board is two
   columns, a card is 490px, a figure column is about 150px - and `USD 18,450,000` came back as
   `USD 18,450,...` on the card the owner looks at most. An ellipsed amount is not a smaller
   amount, it is a WRONG one: `USD 16,061,...` and `USD 16,0...` read as different numbers and
   neither is the number. A figure that has to break breaks at its own space instead, which
   costs a second line on a row whose cards are all the same height anyway.

   `tabular-nums` IS INHERITED from the `.po,.jcode,…` rule at the head of this file via `.mny`,
   so the three columns still line up digit for digit when they do fit on one line.
   ========================================================================================== */
.jfg .mny,.jfg .nil,.jfg .jfgn{font-family:var(--ui);font-size:14.5px;font-weight:800;
  letter-spacing:-.015em;line-height:1.2;color:var(--ink);
  max-width:100%;overflow-wrap:anywhere}
.jfg .nil{color:var(--ink3)}
/* A SHARED PROJECT CODE PRINTS A COUNT AND NOT A FIGURE - `estimateBlock`'s ruling, carried over.
   It is set smaller and in the secondary ink so it cannot be misread as an amount. */
.jfg .jfgn{font-size:13px;font-weight:700;color:var(--ink2)}
@container job (min-width:540px){ .jfg .mny,.jfg .nil{font-size:16px} }
/* WHY THERE IS NO FIGURE, IN ONE LINE, UNDER ALL THREE. §6: a timeout reads as ABSENT and never
   as zero, and a row of three dashes with nothing beside it is a card declining to say which
   kind of absence this is. It spans the whole grid so the three columns stay aligned. */
.jfignote{grid-column:1/-1;margin:0;font-size:11.5px;line-height:1.45;color:var(--ink3)}

/* --------------------------------------------------------------------------------------------
   THE FOOTER - «a compact footer with the dates completion count and "View details"».

   `margin-top:auto` IS THE HALF THAT MAKES THE CARDS LINE UP. See `.job`'s own note: the card is
   a flex column and `.jobs` stretches every card in a row to the tallest, so without this the
   footer of a short card would sit in the middle of a stretched box. With it the slack goes into
   the gap above the footer and every `View details` on a row is on the same line.

   BOTH HALVES ARE 44px TALL, which is not decoration: they are the two things on this card a
   finger is aimed at most often, and the suite measures every control on this page at 390, 834,
   1024 and 1280 against exactly that floor.
   ------------------------------------------------------------------------------------------ */
.jfoot{margin-top:auto;display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:5px 9px 5px 11px;border-top:1px solid var(--panelRule);background:var(--panel)}
.jfd,.jfv{display:inline-flex;align-items:center;gap:7px;min-height:44px;padding:0 8px;
  border-radius:10px;font-family:var(--ui);font-size:12.5px;font-weight:700;
  text-decoration:none;color:var(--ink2);min-width:0}
.jfd svg,.jfv svg{flex:0 0 auto;opacity:.75}
.jfd em{font-style:normal;font-weight:800;color:var(--ink);font-variant-numeric:tabular-nums}
.jfv{color:var(--priInk);font-weight:750}
.jfd:hover,.jfv:hover,.jfd:focus-visible,.jfv:focus-visible{background:var(--paper2)}
.jfv:hover svg{opacity:1}

/* The client/location line under the project code - §1's «client/location», with the mock-up's
   pin. `min-width:0` on the text so a long governorate ellipses rather than pushing the row. */
.jsub.jloc{display:inline-flex;align-items:center;gap:5px;min-width:0}
.jsub.jloc svg{flex:0 0 auto;opacity:.7}
.jsub.jloc > span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ============================================================================================
   §2 AND §5: THE BOARD AND THE PROJECT DETAILS PANEL
   ============================================================================================
   «On wide screens, show the details panel beside the board without squeezing cards into
   unreadable widths. On narrower screens and iPad, use an overlay drawer when necessary. On
   mobile, use a single card column and a full-screen details panel.»

   EVERY BREAKPOINT BELOW IS A `@container` AND NOT A `@media`, AND HERE THAT IS LOAD-BEARING
   RATHER THAN A HOUSE STYLE. The mock-up is 1536 wide; the board is not. This app has a branded
   rail, so the CONTENT width at the four widths the suite drives is 362 / 594 / 732 / 1012 at
   windows of 390 / 834 / 1024 / 1280. A rule that asked `@media (min-width:1024px)` for a
   side-by-side layout would have put one inside a 732px column - and `.jobs` records at length
   what happened the last time this file asked the window a question only the container could
   answer: two columns of 270px on an iPad, every project code ellipsed to `ETE…`.

   SO THE ONE THRESHOLD IS 900 OF CONTAINER, AND IT IS ARITHMETIC RATHER THAN TASTE. The panel
   wants 340px to draw a three-column financial summary and a cost table; a card is unreadable
   under about 360 (that is `.jobs`' own floor, measured). 340 + 20 + 360 = 720, which a 732
   container clears by twelve pixels - a board of exactly one column at its floor, with a panel
   jammed against it. At 1012 the same sum leaves 652 for the board, which is one generous column
   or, at `.jobs`' 360 floor, still one. So:

     AT 1012 (a 1280 window)  the panel is BESIDE the board. The board goes from two columns to
                              one, which is §5's «Adapt the number of card columns to the
                              available width» - and one 652px card is a wider card than the two
                              496px ones it replaced, so nothing was squeezed into anything.
     AT 732 AND 594 AND 362   the panel is an OVERLAY DRAWER over the board, 420px wide at 732 and
                              594, and the full width at 362. The board underneath keeps whatever
                              column count it had; nothing about it is reflowed by opening a
                              panel, which is also why closing one cannot move it.

   THE DRAWER IS `position:absolute` AND NEVER `position:fixed`, and that is a constraint this
   file has already recorded once: an element with `container-type` is a containing block for
   `position:fixed`, so a fixed drawer inside `.pboard` would not be fixed to the viewport at all.
   Absolute inside a full-height `.pboard`, with the SCROLLER sticky inside it, gives the same
   thing honestly - see `.dpin`.
   ========================================================================================== */
/* THE CONTAINER AND THE GRID ARE TWO ELEMENTS, AND THAT IS NOT AN EXTRA DIV FOR NOTHING.
   `@container` asks the nearest ANCESTOR container, so `.pboard` - which DECLARES the container -
   cannot be the element a `@container pboard` rule styles. Written as one element, every
   `grid-template-columns` below matched nothing at all: the base single-column rule stood, the
   panel's own `grid-column:2` created an IMPLICIT column, implicit tracks are `auto`, and the
   measured result at a 1280 window was a 502px panel beside a 490px board - a layout that looked
   plausible and was an accident. `.pboard` carries the container and the positioning context the
   overlay drawer is absolute inside; `.pbgrid` carries the columns. */
.pboard{container:pboard / inline-size;position:relative}
.pbgrid{display:grid;gap:20px;grid-template-columns:minmax(0,1fr);align-items:start}
.pbmain{min-width:0}

/* THE ASIDE IS ALWAYS IN THE MARKUP AND CARRIES `hidden` WHEN NOTHING IS OPEN, because
   `views/items.js` paints into an element that has to already be there. `[hidden]` is honoured by
   the browser's own UA sheet; it is restated here because several `display` rules below are more
   specific than it and would otherwise win. */
.dpanel[hidden]{display:none}
.dpanel{min-width:0}
/* ITS OWN VERTICAL SCROLLING - §2 asks for it by name. The aside is as tall as the board; the
   PIN inside it sticks to the top of the viewport and scrolls its own content, so wherever the
   reader is scrolled the panel is on screen and the board behind it has not moved. That is one
   mechanism for the side-by-side layout and the drawer both, which is why there is no second one.
   `100dvh` and not `100vh`: on a phone the difference is the browser's own chrome, and a panel
   that ran under the address bar would hide its own close button. */
/* ============================================================================================
   IT STICKS BELOW THE MASTHEAD AND NOT UNDER IT - measured, 2026-09-26.

   `.masthead:has(.topbar)` is `position:sticky; top:0; z-index:30` and 63px tall on every page
   behind the login. A panel sticking to `top:12px` therefore parks its own head - and its CLOSE
   BUTTON - underneath it: `elementFromPoint` at the centre of the X came back `SUMMARY.whoopen`,
   the account menu in the masthead, which is the browser saying in as many words that a tap aimed
   at Close would have opened the account menu instead.

   `--stick` IS THE MASTHEAD'S HEIGHT AND IT IS DECLARED HERE RATHER THAN MEASURED IN SCRIPT,
   because there is no script on this page and a sticky offset is not worth one. 63 is the bar as
   it is drawn at every width this suite checks; the 13 beside it is the same gap the panel had.
   If the masthead ever grows a row, this is the one number that moves with it - and
   `board-panel.test.js` fails at the X before anybody sees it.

   AND THE PANEL SITS BELOW THE MASTHEAD IN STACKING ORDER TOO - `z-index:20` against its 30. The
   masthead is CHROME: a drawer that covered the search box and the account menu would be a drawer
   the reader cannot get out from behind on a phone.
   ========================================================================================== */
.dpin{position:sticky;top:calc(var(--stick) + 13px);
  max-height:calc(100dvh - var(--stick) - 26px);overflow-y:auto;
  overscroll-behavior:contain;-webkit-overflow-scrolling:touch;
  background:var(--card);border:1px solid var(--rule);border-radius:var(--radius);
  box-shadow:var(--shadow)}

@container pboard (min-width:900px){
  /* ============================================================================================
     BESIDE THE BOARD, AND THE PANEL TRACK IS A PLAIN LENGTH - which it is for a measured reason
     rather than a stylistic one.

     IT WAS `clamp(340px,32%,420px)` FOR AN HOUR AND THE WHOLE DECLARATION WAS BEING DROPPED.
     Measured in headless Chrome at a 1280 window: `getComputedStyle(.pboard).gridTemplateColumns`
     came back `490.047px 501.953px` - two tracks that are neither 340 nor 420 and do not add up
     to anything either number explains. What was actually happening is that the declaration was
     invalid and thrown away, leaving the BASE rule's single `minmax(0,1fr)` column; `.dpanel`'s
     own `grid-column:2` then created an IMPLICIT column, implicit tracks are `auto`, and an auto
     track sized to a details panel's content is 502px. The layout looked plausible and was an
     accident, which is the worst kind of layout to ship.

     SO THE TRACK IS A LENGTH, AND IT STEPS ONCE. 360 is the panel's own floor - a three-column
     financial summary and an account table do not read under it - and 400 is what a genuinely
     wide window can spare. At a 1012 container (a 1280 window, which is the owner's desk) that
     leaves the board 632px: §5's «without squeezing cards into unreadable widths» measured rather
     than assumed, since `.jobs` records 360 as the width below which a card stops being readable
     and 632 is most of twice that.
     ========================================================================================== */
  /* ONLY WHEN THERE IS A PANEL IN IT - see `pbopen` in `views/pages.js`. A board that gave up
     a 360px track to an empty aside would draw one card column where it draws two. */
  .pbgrid.pbopen{grid-template-columns:minmax(0,1fr) 360px}
  .pbmain{grid-column:1}
  /* `align-self:stretch` AGAINST THE GRID'S `align-items:start`, and it is what makes the sticky
     scroller inside worth having. A panel sized to its own content is a 480px box at the top of a
     2,300px board: scroll to the thirtieth card and the panel has gone, which is the opposite of
     a details panel. Stretched, the aside is as tall as the board and `.dpin` sticks inside it,
     so the panel is on screen wherever the reader is - the same mechanism the overlay drawer
     below uses, which is why there is only one. */
  .dpanel{grid-column:2;align-self:stretch}
}
@container pboard (min-width:1100px){ .pbgrid.pbopen{grid-template-columns:minmax(0,1fr) 400px} }

@container pboard (max-width:899px){
  /* THE OVERLAY DRAWER. `inset-block` full height so the sticky pin inside it can follow the
     reader down the board; `z-index` above the cards and below nothing else this page draws. */
  .dpanel{position:absolute;top:0;bottom:0;right:0;width:min(420px,100%);z-index:20}
  .dpin{top:calc(var(--stick) + 9px);max-height:calc(100dvh - var(--stick) - 18px)}
  /* THE BOARD BEHIND IT IS DIMMED AND NOT DISABLED. A pointer-events:none board would make the X
     the only way out, which is wrong on a touch screen where a reader reaches past a drawer all
     the time; this is a hint about which surface is in front, drawn by the panel's own shadow. */
  .dpin{box-shadow:0 18px 44px rgba(11,31,68,.22),var(--shadow)}
}
@container pboard (max-width:559px){
  /* FULL SCREEN ON A PHONE - §5's own words. The drawer takes the whole board column. */
  .dpanel{width:100%}
}

/* ---------- THE PANEL'S OWN HEAD, TABS AND BODY ---------- */
.dphead{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:11px;align-items:start;
  padding:14px 14px 12px;border-bottom:1px solid var(--panelRule)}
/* `grid-area:auto` FIRST, AND IT IS NOT REDUNDANT. `.jthumb` carries `grid-area:no` for the
   CARD's `.jident`, which declares `grid-template-areas:"no tx gear"`. `.dphead` declares no
   areas at all, so that named area is one this grid has never heard of - and an item placed in
   an unknown named area is placed in an implicit row of its own. Measured: the panel's thumbnail
   rendered UNDER the project name instead of beside it. */
.dphead > .jthumb{grid-area:auto;grid-column:1;grid-row:1}
.dpid{grid-column:2;min-width:0;display:flex;flex-direction:column;gap:4px}
.dpno{font-family:var(--ui);font-size:11px;font-weight:800;letter-spacing:.06em;
  color:var(--ink3);font-variant-numeric:tabular-nums}
.dpname{margin:0;font-family:var(--serif);font-size:19px;font-weight:800;letter-spacing:-.03em;
  line-height:1.16;color:var(--ink);overflow-wrap:anywhere}
.dptop{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
/* THE CLOSE BUTTON - «The panel should have a clear close button». A 44px target, in the corner
   the mock-up puts it in, and a real link: with the script blocked it goes back to the board. */
.dpx{grid-column:3;display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;margin:-6px -6px 0 0;border-radius:12px;
  color:var(--ink2);text-decoration:none}
.dpx:hover,.dpx:focus-visible{background:var(--paper2);color:var(--ink)}

.dptabs{display:flex;gap:2px;padding:0 8px;border-bottom:1px solid var(--panelRule);
  overflow-x:auto;scrollbar-width:thin}
.dpt{flex:0 0 auto;display:inline-flex;align-items:center;min-height:44px;padding:0 12px;
  font-family:var(--ui);font-size:13px;font-weight:700;color:var(--ink2);text-decoration:none;
  border-bottom:2.5px solid transparent;white-space:nowrap}
.dpt:hover{color:var(--ink)}
.dpt.on{color:var(--ink);border-bottom-color:var(--pri)}

.dpbody{padding:0 0 14px}
/* THE FINANCIAL SUMMARY AT THE HEAD OF `Costs & team` - §2's own first word for that tab. The
   figure row is the CARD's `.jfig`, drawn again, so the two cannot disagree; what this adds is
   the heading, because in a panel it is one of four sections and needs naming. */
.dpsec{padding:12px 14px 0}
.dph{margin:0 0 2px;font-family:var(--ui);font-size:11px;font-weight:750;letter-spacing:.07em;
  text-transform:uppercase;color:var(--ink3)}
.dpsec > .jfig{border-top:0;padding:6px 0 0}
/* ============================================================================================
   THE TWO FOLDS UNDER THE SUMMARY KEEP THEIR CHEVRON AND LOSE THEIR CHIPS - in the panel only.

   `moneyBlock`'s summary chips read `REVENUE USD 18,450,000  ACTUAL COST USD 16,061,500`, which
   is the right thing on a CARD, where that fold is the only place those figures appear. In the
   panel they sit four centimetres under a Financial summary that has just said both, in larger
   type, beside their own marks. Saying a figure twice on one screen is how a reader stops
   believing either copy - `.jmoney details[open] > .moneytog em` already takes these off for
   exactly that reason when the fold is opened, and this is the same rule one surface along.

   THE FOLDS THEMSELVES STAY, because what is BEHIND them is not in the summary: the profit or
   loss verdict, what has been paid and by whom, the monthly chart, how the cost was composed,
   and each costing's own provenance line. The rows now read `Finance ›` and `Expected cost ›`,
   which is what they are.
   ========================================================================================== */
.dpbody .jmoney .moneytog > em{display:none}

/* WORKED BY, OVERTIME AND COST BREAKDOWN AS THREE ROWS OF THE PANEL - §2 names all three at this
   level, and `listsBlock(…, flat)` is what takes the `Project lists` fold off them. `.jwho` is
   their own class on the card and keeps every rule it has; this only gives the group the panel's
   gutter, which a `.job` used to give it. */
.dpflat{padding:8px 14px 0;display:grid;gap:8px}

/* THE BLOCKS INSIDE ARE THE CARD'S OWN RENDERERS and therefore carry the card's own classes -
   `.jdd`, `.jprod`, `.jprep`, `.jmoney`, `.jdetails`, `.dates`, `.jedit`. Not one of those rules
   is duplicated here; what this does is give them the 15px gutter they had inside a card, because
   the panel has no `.job` around it. */
.dpbody > .jedit{margin:11px 14px}
.dpfiles{padding:12px 14px 0}
.dpfiles > .shot{border-radius:12px;border:1px solid var(--panelRule)}

/* THE PANEL IS ITS OWN QUERY CONTAINER TOO, so the blocks it borrows from the card - which are
   every one of them written against `@container job (...)` - have something to ask. Without this
   they would resolve against `pboard`, which is the board's width and not theirs: at a 1280
   window that is 1012px, and the station strip and the figure row would both draw their widest
   form inside a 380px panel. Named `job` deliberately: these ARE the card's blocks, and giving
   them a second name would mean maintaining a second set of breakpoints for one design. */
.dpbody{container:job / inline-size}

/* ---------- 2. THE PREVIEW ROW: the drawing, and the date rail beside it ----------
   They read side by side on a card wide enough to carry both, and stack on a phone. Stacked, the
   DATES COME FIRST and the drawing sits under them - Anas's order, so the deadline is read before
   the picture. Side by side there is no "above", and the drawing reads better on the left, so the
   two are put back in that order with `order`. */
.jshow{display:flex;flex-direction:column;align-items:stretch;
  border-top:1px solid var(--panelRule);border-bottom:1px solid var(--panelRule);
  background:var(--panel)}
.jshow > .jdates{border-bottom:1px solid var(--panelRule)}
@container job (min-width:470px){
  .jshow{flex-direction:row;align-items:stretch}
  .jshow > .shot{order:0;aspect-ratio:auto;align-self:stretch;height:auto;flex:1 1 auto;
    width:auto;min-width:0;border-bottom:0;border-right:1px solid var(--panelRule)}
  .jshow > .jdates{order:1;border-bottom:0;
    flex:0 0 clamp(212px,44%,272px);width:clamp(212px,44%,272px);padding:4px 15px 10px}
  .jshow > .shot.none{min-height:0}
  .jshow > .shot.none .dwcap{position:absolute;inset:0;flex-direction:column;text-align:center}
  .jshow > .shot.none.nofile{border-right-style:dashed;border-bottom:0}
}

/* --- THE DRAWING PANEL --- */
.shot{position:relative;display:block;width:100%;margin:0;padding:0;flex:0 0 auto;
  aspect-ratio:16/10;border:0;border-bottom:1px solid var(--rule);border-radius:0;
  background:linear-gradient(150deg,var(--paper2),transparent);overflow:hidden;
  color:var(--ink3);text-align:left;text-decoration:none}
.shot:before{content:"";position:absolute;inset:0;pointer-events:none;opacity:.6;
  background-image:
    repeating-linear-gradient(0deg,var(--rule2) 0 1px,transparent 1px 24px),
    repeating-linear-gradient(90deg,var(--rule2) 0 1px,transparent 1px 24px)}
.shot img{position:relative;z-index:1;display:block;width:100%;height:100%;object-fit:contain;
  background:var(--sheet)}
.shot figcaption{position:absolute;z-index:2;left:0;right:0;bottom:0;
  font-family:var(--ui);font-size:10.5px;color:var(--ink2);
  padding:7px 10px;background:var(--navBg);border-top:1px solid var(--rule2);
  line-height:1.4;overflow-wrap:anywhere}
@supports (backdrop-filter:blur(8px)) or (-webkit-backdrop-filter:blur(8px)){
  .shot figcaption{-webkit-backdrop-filter:blur(9px) saturate(130%);
    backdrop-filter:blur(9px) saturate(130%)}
}
.dwcap{position:absolute;z-index:2;inset:0;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:9px;padding:16px;text-align:center;font-size:11.5px;
  line-height:1.35;color:var(--ink3)}
.dwcap svg{opacity:.65;flex:0 0 auto}
.dwcap b{display:block;font-weight:650;color:var(--ink2);font-size:12px;margin-bottom:3px}
/* Two different facts, two treatments. Nothing uploaded at all reads as an EMPTY frame; a file
   that is there but yields no drawing keeps a solid edge, because the file really is there. */
.shot.none{background:transparent;aspect-ratio:auto;min-height:62px}
.shot.none .dwcap{position:static;flex-direction:row;gap:9px;padding:13px 14px;
  justify-content:center;height:100%}
.shot.none:before{opacity:.28}
.shot.none:after{content:"";position:absolute;inset:11px;border:1.5px dashed var(--rule);
  border-radius:11px;pointer-events:none}
.shot.none.nofile{border-bottom-style:dashed}
.shot.none.nodraw{background:var(--paper2)}
.shot.none.nodraw:before{opacity:.16}
a.shot:hover{box-shadow:inset 0 0 0 2px var(--priRule)}
/* A LINKED, LOADED DRAWING. The blueprint grid (`.shot:before`) is the placeholder while the image
   lazy-loads; `.shot img` above paints over it opaque once it arrives. `zoom-in` says the link opens
   the drawing larger - it opens the SAME same-origin proxy image full-size, never a Drive URL. */
.shot.draw{cursor:zoom-in}
/* ============================================================================================
   THE SHEET IS THE PAPER UNDER A DRAWING, NOT A SLAB WHERE THERE IS NO DRAWING.
   ============================================================================================
   `--sheet` STAYS WHITE IN BOTH THEMES AND THAT IS DELIBERATE - its own note in `:root` says so,
   and this repo has already been bitten by the thing it guards against: the note on `loadLogo` in
   `views/asset.js` records a soft mask that "would otherwise have made the dark crown invisible on
   a dark background". A drawing proxied from Drive is an `image/png` thumbnail and can be black
   line art on transparency. A dark mat behind it would blank the drawing, which is a worse failure
   than a bright one. So this does not flip the token.

   WHAT WAS ACTUALLY WRONG WAS THE SIZE OF THE WHITE. `aspect-ratio:16/10` with `object-fit:contain`
   means every drawing whose shape is not 16:10 is letterboxed, and the bars are painted in
   `--sheet` because the background belongs to the `<img>` box rather than to the artwork inside it.
   On `03-orders-open-390-dark` that was a 390x43 band of #FFFFFF across a dark card - the only
   light-mode leak in the walk, and it was white where there was no drawing at all.

   SO THE BOX IS THE ARTWORK. The image takes its own shape, capped by the panel, and the panel's
   own theme-aware gradient fills whatever is left. The sheet is then exactly and only behind the
   drawing, which is what the token was always for. `min-height` keeps a lazy image from collapsing
   the panel to nothing before it arrives; the blueprint grid is what shows in the meantime, which
   is what the note above says it is for. */
.shot.draw{aspect-ratio:auto;min-height:62px;
  display:flex;align-items:center;justify-content:center}
.shot.draw img{width:auto;height:auto;max-width:100%;max-height:100%}

/* --- the date rail --- */
.jdates{flex:0 0 auto;min-width:0;padding:2px 15px 9px}
.jdates .dates{margin:0}
/* The summary row: the count is the value and reads first, with "Dates" naming it underneath.
   Both are the renderer's own text; only which of the two is drawn larger has changed. */
.jdates .datetog{display:flex;align-items:center;gap:11px;padding:10px 0 9px;min-height:52px;
  font-size:12px;font-weight:650;color:var(--ink2)}
.jdates .datetog .ttx{gap:0}
.jdates .datetog .tnm{font-size:11.5px;font-weight:650;color:var(--ink2);order:2}
.jdates .datetog em{font-style:normal;font-size:15px;font-weight:800;letter-spacing:-.02em;
  color:var(--ink);order:1}
.jdates .datebody{padding:0 0 8px}
/* Mark on the left, then the label over the value - the same two-line text column the money cards
   and the detail tiles use, so the whole card has one rhythm. */
.jdates .dline{display:flex;margin:0;padding:9px 0 8px;gap:11px;font-size:13px;align-items:center;
  flex-wrap:wrap;border:0;border-top:1px solid var(--panelRule)}
.jdates .dline > .dlx{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 7px;
  flex:1 1 0;min-width:0}
.jdates .dline .dlx > i{flex:0 0 100%;font-style:normal;font-size:9px;letter-spacing:.1em;
  font-weight:700;text-transform:uppercase;color:var(--ink3);margin-bottom:1px}
.jdates .dline .dlnone{font-size:13px;color:var(--ink3);font-style:italic}
.drow{display:flex;align-items:baseline;gap:10px;padding:4px 0;font-size:12.5px;
  border-top:1px solid var(--panelRule)}
.drow .dk{color:var(--ink3);min-width:112px}
.drow b{font-weight:700}
.drow .dnone{color:var(--ink3);font-style:italic}

/* ---------- 3. FINANCE: revenue, cost, profit ----------
   .jmoney and .jdetails are interpolated with no whitespace of their own precisely so that an
   empty one is really :empty and can take itself out of the card rather than leaving a padded
   blank panel behind. THAT IS ALSO THE PERMISSION PATH: a role that receives no money figures
   gets an empty .jmoney and the panel is simply not there - not hidden, not greyed, absent. */
.jmoney:empty,.jdetails:empty,.jprep:empty{display:none}
.jmoney{padding:13px 15px 3px;display:grid;gap:9px}
.jmoney .moneytog{display:flex;align-items:center;gap:11px;padding:2px 0 6px;min-height:40px;
  font-size:13px;font-weight:750;color:var(--ink)}
/* ============================================================================================
   §4's «Fix the current Finance label wrapping into individual vertical letters» - 2026-09-26,
   AND IT IS A WIDTH BUG IN THIS ROW RATHER THAN A TYPO ANYWHERE.

   WHAT WAS HAPPENING, EXACTLY. `.moneytog` is a flex row of three items: the tinted mark, the
   `.ttx` column holding the word `Finance`, and an `<em>` of summary chips. `.ttx` is declared
   `flex:1 1 0` (see THE ROW MARK above - a zero basis is what keeps a text column beside its
   mark instead of dropping under it) and `.tnm` is declared `overflow-wrap:anywhere` (for the
   forty-character project names it carries elsewhere). The chips inside the `<em>` are
   `white-space:nowrap` and therefore cannot shrink below their own width. So on a narrow card
   carrying two long figures - `REVENUE USD 16,306` and `ACTUAL COST USD 3,501` - the `<em>` took
   what it needed, `.ttx` shrank towards zero because it is allowed to, and `overflow-wrap:
   anywhere` then broke F/i/n/a/n/c/e one letter per line. That is the photograph he sent.

   `estimateBlock` IN `views/card.js` DESCRIBED THIS EXACT MECHANISM ON 2026-09-20 and declined to
   fix it - «a defect of that row and not this one's to fix» - because `app.css` is
   content-addressed and eleven test files pin its hash. This slice re-pins all eleven, so it is
   fixed here, at the row, which is where the two lines that cause it live.

   THE FIX IS TWO DECLARATIONS AND NOT A NEW CLASS. The heading is a fixed, known, seven-letter
   word: it does not shrink (`flex:0 0 auto` on its column) and it does not break (`nowrap` and
   `overflow-wrap:normal`, which override the two inherited rules for this row only). The chips
   were already `flex-wrap:wrap`, so what gives now is the SUMMARY - one figure drops to a second
   line, which is what a summary is for - instead of the label.
   ========================================================================================== */
.jmoney .moneytog > .ttx{flex:0 0 auto}
.jmoney .moneytog .tnm{font-size:11.5px;font-weight:650;color:var(--ink2);
  letter-spacing:.06em;text-transform:uppercase;
  white-space:nowrap;overflow-wrap:normal;word-break:keep-all}
/* The sale and the cost live on the SUMMARY, not in the list - saying them twice made the group
   worth opening for nothing. What is inside is what the summary cannot carry: the profit, what
   has been paid, and who owes. And once it is open the summary figures come off, because the
   numbers they were standing in for are now on screen underneath. */
.jmoney .moneytog em{display:flex;flex-wrap:wrap;gap:3px 14px;margin-left:auto;
  justify-content:flex-end;font-style:normal;font-size:13px}
.jmoney details[open] > .moneytog em{display:none}
.jmoney .jbody{padding:0;display:grid;gap:9px}
.jmoney .jbody .pl.rev,.jmoney .jbody .pl.cost{display:none}
.mhb{display:inline-flex;align-items:baseline;gap:5px;white-space:nowrap}
.mhb i{font-style:normal;font-size:8.5px;font-weight:750;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink3)}
.mhb .mny{font-weight:800;color:var(--ink)}
.mhb.held i{color:var(--ink3)}

/* TWO ACROSS IS THE PHONE'S ANSWER, not three. A mark takes 34px and the padding another 26, so
   at 390px a third column would leave about 33px for a figure set at 19px. Revenue and cost pair
   off and the profit takes the full width beneath them, which is the right ranking anyway: it is
   the number that says whether the job made money. Three across returns on a genuinely wide card.
   ONE COLUMN IS THE FLOOR and it is not hypothetical: a card two-up on a 716px laptop is 274px
   wide, and two money columns there gave "REVEN / UE" over "IQD 3,5 / 26,000". */
.jmoney .plrow{display:grid;gap:9px;margin:0;grid-template-columns:minmax(0,1fr)}
@container job (min-width:340px){
  .jmoney .plrow{grid-template-columns:repeat(2,minmax(0,1fr))}
  .jmoney .plrow > .pl:nth-child(3):last-child{grid-column:1 / -1}
}
@container job (min-width:540px){
  .jmoney .plrow{grid-template-columns:repeat(3,minmax(0,1fr))}
  .jmoney .plrow > .pl:nth-child(3):last-child{grid-column:auto}
}
.pl{display:flex;flex-direction:row;align-items:center;gap:11px;padding:12px 13px;
  border-radius:16px;border:1px solid var(--panelRule);font-family:var(--ui);
  font-size:19px;font-weight:800;letter-spacing:-.025em;line-height:1.16;
  white-space:normal;overflow-wrap:normal}
@container job (min-width:340px){ .pl{font-size:16.5px} }
@container job (min-width:540px){ .pl{font-size:19px} }
/* `overflow-wrap:anywhere` was breaking the FIGURE itself - "IQD 18," on one line and "450,000"
   on the next. A number that wraps midway is worse than a number a point smaller, so the value
   never breaks and the type steps down instead. Only the note underneath is free to wrap; it is
   a sentence. */
.pl .mny{white-space:nowrap}
.pl em{overflow-wrap:anywhere}
.pl > .ttx{gap:2px}
.pl i{font-style:normal;font-size:9px;font-weight:750;letter-spacing:.11em;opacity:1;
  text-transform:uppercase}
.pl.rev{background:var(--okBg);color:var(--ink)}
.pl.rev i{color:var(--ok)}
.pl.cost{background:var(--lateBg);color:var(--ink)}
.pl.cost i{color:var(--late)}
.pl.cost em{display:block;font-style:normal;font-size:9.5px;font-weight:600;letter-spacing:0;
  line-height:1.35;margin-top:3px;color:var(--ink2)}
.pl.pos{background:var(--okBg);color:var(--ok)}
.pl.pos i{color:var(--ok);opacity:.85}
.pl.neg{background:var(--lateBg);color:var(--late)}
.pl.neg i{color:var(--late);opacity:.85}
/* A figure with nothing behind it yet. It keeps the tile so the reader can see that the question
   EXISTS on this card, and drains the colour so a dash can never be mistaken for a good or a bad
   result. `.pl.rev.none` outranks `.pl.rev` on class count. */
.pl.none,.pl.rev.none{background:var(--panel);color:var(--ink2)}
.pl.none i,.pl.rev.none i{color:var(--ink3)}
.pl.none em{display:block;font-style:normal;font-size:9.5px;font-weight:600;letter-spacing:0;
  line-height:1.35;margin-top:3px;color:var(--ink3)}
/* The mark sits ON the tinted card, so it takes the card's own surface rather than repeating the
   tint - a green square on a green card is a smudge, not a mark. */
.pl > .tico{background:var(--card);box-shadow:0 1px 2px rgba(17,24,39,.06)}

/* --- what was paid, and what the client owes --- */
.paid{display:flex;gap:11px;align-items:center;flex-wrap:wrap;font-size:13.5px;
  padding:12px 13px;border-radius:16px;background:var(--panel);border:1px solid var(--panelRule)}
.paid > .ttx{gap:2px}
.paid i{font-style:normal;padding:0;background:none;font-size:9px;font-weight:750;
  letter-spacing:.11em;text-transform:uppercase;color:var(--ink3)}
.paid.ok i{color:var(--ok)}
.paid.part i{color:var(--risk)}
.paid.bad i{color:var(--late)}
.paid.client i{color:var(--ship)}
.paid b{font-size:15px;font-weight:750;line-height:1.3;overflow-wrap:anywhere;
  unicode-bidi:plaintext}
.paid em{font-style:normal;font-size:10.5px;color:var(--ink2)}

/* ---------- 4. PREPARATION ----------
   Three states, and the colour is never the only signal: the word is on the chip as well. A row
   of coloured dots with no words is unreadable in a screenshot, in print, and to anyone who does
   not separate red from green.
   A BLANK CELL IS `Not set`, IN GREY, and never Pending. Colouring an untouched cell orange would
   put three "work outstanding" chips on 805 delivered orders. */
.jprep{margin:0 15px 13px;padding:13px 14px 12px;border-radius:18px;
  background:var(--panel);border:1px solid var(--panelRule)}
.jprep .jph{margin-bottom:10px}
.pchips{display:flex;flex-wrap:wrap;gap:7px}
.pchip{font:inherit;font-size:12px;font-weight:650;display:inline-flex;align-items:center;gap:7px;
  padding:6px 11px;min-height:32px;border-radius:11px;border:1px solid transparent;
  background:var(--paper2);color:var(--ink)}
.pchip em{font-style:normal;font-size:10.5px;font-weight:700;opacity:.85}
.pchip .pdot{width:9px;height:9px;border-radius:50%;flex:0 0 9px;background:currentColor}
.pchip.wait{background:var(--riskBg);color:var(--risk)}
.pchip.act{background:var(--shipBg);color:var(--ship)}
.pchip.ok{background:var(--okBg);color:var(--ok)}
.pchip.none{background:var(--paper2);color:var(--mute);border-color:var(--panelRule)}
.pchip.none .pdot{background:transparent;border:1.5px dashed currentColor}

/* ---------- 5. PRODUCTION STATIONS ----------
   STATION COLOURS, set by the owner: green = done, violet = being worked now, AMBER = still to
   do, BLACK = not needed for this order. Not-needed was briefly red and he changed it the same
   day: red is spoken for by "late" and "at a loss", and a station that is simply not part of the
   job is not a problem. Black is a neutral - and through --ink it becomes near-white in dark
   mode, which is the same role, not a colour flip. */
.jprod{margin:7px 15px 13px;padding:13px 14px 12px;border-radius:18px;
  background:var(--panel);border:1px solid var(--panelRule)}
.jph{display:flex;align-items:center;gap:11px;font-size:13px;font-weight:750;color:var(--ink)}
.jph > .tico{background:var(--card)}
.jpwrap{display:grid;gap:11px;grid-template-columns:minmax(0,1fr);margin-top:10px}
.strip{display:flex;gap:2px;align-items:flex-end;flex-wrap:wrap;row-gap:10px;margin:0;padding:0}
.st{text-align:center;flex:1 1 0;min-width:0}
/* THE BAR IS THE CONTROL, for a reader who may write a station: `a.st` is the same segment with a
   fragment on it, pointing at that station's picker (`.pick:target` below). Three things make it a
   target a thumb can hit rather than a 14px ribbon:
     - the anchor wraps the bar AND the label under it, so the dead gap between them is inside the
       target instead of between two of them;
     - the padding takes the box past 40px tall without moving the bar, because the strip aligns its
       segments on their BOTTOM edge and every segment gets the same padding;
     - the hit area is the whole segment, ~42px wide on a phone at seven across.
   No colour, weight or size changes: the strip has to still read as a strip at a glance, and a
   station's state is still the bar's own colour and the word under it. */
a.st{display:block;text-decoration:none;color:inherit;padding:8px 2px 6px;border-radius:13px;
  -webkit-tap-highlight-color:transparent}
a.st:hover{background:var(--paper2)}
a.st:focus-visible{outline:2px solid var(--brassInk);outline-offset:-2px}
.st i{display:block;height:14px;border-radius:999px;border:1px solid var(--rule);
  background:transparent}
.st.done i{background:var(--ok);border-color:transparent}
/* `--s2` TO `--s1`, WHICH ARE TOKENS AGAIN. This was `linear-gradient(90deg,#41BBD2,var(--brass))`
   - a hard-coded teal that followed no palette and would have stayed teal through the navy swap,
   beside a token that moved. It is the only gradient in this file that was half literal. */
.st.active i{background:linear-gradient(90deg,var(--s2),var(--s1));border-color:transparent;
  box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--priRule)}
.st.wait i{background:var(--risk);border-color:transparent}
/* A station that is not needed keeps its SHORTER bar. The reduced height is the second signal
   that says "not part of this job" rather than "a black stage". */
.st.na i{height:8px;background:var(--ink);border-color:transparent;border-radius:2px}
.st span{display:block;font-family:var(--ui);font-size:8.5px;margin-top:7px;color:var(--ink3);
  overflow:hidden;white-space:nowrap}
.st.done span{color:var(--ok);font-weight:600}
.st.active span{color:var(--brassInk);font-weight:700}
.st.wait span{color:var(--risk);font-weight:600}
.st.na span{color:var(--ink2);font-weight:600}
.railnote{margin:0;font-size:11.5px;line-height:1.45;color:var(--ink2);align-self:center}
.railnote b{font-weight:700;color:var(--ink)}

/* ---------- 6. PROJECT DETAILS ----------
   The lists become a tile grid. This wrapper is CLOSED on load and each list inside is closed
   too: opening their contents for every card was not what was asked for and buries the card. The
   count on the summary is of lists actually rendered, which varies per order - and, in this app,
   varies per ROLE, because a list a role may not see is not built. */
.jdetails{margin:0 12px 12px;border-radius:18px;overflow:hidden;
  background:var(--panel);border:1px solid var(--panelRule)}
.jdetails .listtog{display:flex;align-items:center;gap:11px;padding:11px 13px;min-height:56px;
  font-size:13px;font-weight:750;color:var(--ink)}
.jdetails .listtog .tnm{flex:1 1 auto}
.jdetails .listtog em{font-style:normal;font-size:12px;font-weight:750;color:var(--ink2);
  min-width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;
  padding:0 6px;border-radius:999px;background:var(--card)}
.jdetails .jbody{padding:0 9px 9px;display:grid;gap:8px;align-items:stretch;
  grid-template-columns:minmax(0,1fr)}
.jwho{margin:0;min-width:0;border:1px solid var(--panelRule);border-radius:14px;
  background:var(--card);overflow:hidden}
/* Mark, two lines of text, chevron. */
.whotog{display:flex;padding:11px 12px;min-height:58px;align-items:center;flex-wrap:nowrap;
  gap:11px;white-space:normal;font-size:13px;font-weight:700;color:var(--ink)}
.whotog .ttx{gap:2px}
.whotog .tnm{line-height:1.25}
.whotog em{font-style:normal;margin-left:0;text-align:left;font-size:12px;font-weight:500;
  color:var(--ink2);white-space:normal;line-height:1.35}
.whobody{padding:0 13px 13px 15px}
/* TWO COLUMNS ON ANYTHING WIDER THAN A PHONE. 430 is the first width at which a tile still has
   about 120px of text column after the 34px mark, the 20px chevron and the gutters - below it the
   summaries wrap to three lines and the grid costs more height than it saves. */
@container job (min-width:430px){
  .jdetails .jbody{grid-template-columns:repeat(2,minmax(0,1fr))}
  /* At this size the two columns are about 190px each and the furniture takes 76 of them, so
     everything steps down so "Materials & cost" still sets on one line. */
  .whotog{padding:10px;gap:8px;min-height:54px;font-size:12.5px}
  .whotog .tico{width:30px;height:30px;border-radius:10px}
  .whotog .tico svg{width:16px;height:16px}
  .whotog .tchev{width:16px}
  .whotog em{font-size:11.5px}
  .whobody{padding:0 11px 12px 13px}
  /* An open list is read, not scanned: it takes the full width of the grid so its tables are not
     squeezed into half a card. */
  .jdetails .jwho:has(> details[open]){grid-column:1 / -1}
}
@container job (min-width:540px){
  .whotog{padding:11px 12px;min-height:58px;font-size:13px}
}

/* the rows inside an open list */
.whodep{display:flex;justify-content:space-between;align-items:baseline;font-family:var(--ui);
  font-size:9.5px;text-transform:uppercase;letter-spacing:.14em;color:var(--brassInk);
  margin:14px 0 6px}
.whobody .whodep:first-child{margin-top:2px}
.whodep b{color:var(--brassInk);font-weight:500;white-space:nowrap}
.whorow{display:flex;justify-content:space-between;align-items:baseline;gap:8px;padding:4px 0}
.whorow span{font-size:13px;color:var(--ink)}
.whorow b{font-family:var(--sans);font-size:12px;color:var(--steel);font-weight:400;
  white-space:nowrap;flex:0 0 auto;display:flex;gap:10px;align-items:baseline;
  justify-content:flex-end}
.whorow b u{text-decoration:none;font-family:var(--ui);font-size:11.5px;color:var(--ink);
  min-width:66px;text-align:right;display:inline-block;font-variant-numeric:tabular-nums}
.whorow b u i{font-style:normal;color:var(--mute)}
.acct{display:flex;justify-content:space-between;align-items:baseline;gap:10px;padding:2.5px 0}
.acct span{font-size:12.5px;color:var(--ink)}
.acct span i{font-style:normal;font-family:var(--ui);font-size:10px;color:var(--mute);
  margin-right:6px}
.acct b{font-family:var(--ui);font-size:11.5px;font-weight:400;color:var(--steel);
  white-space:nowrap;font-variant-numeric:tabular-nums}
.acct.big b{color:var(--ink);font-weight:600}
/* A PRESSABLE ACCOUNT ROW - 2026-09-20, and the only rule this slice added.
   The 501 and 502 lines of a cost breakdown are LINKS: pressing one loads that project's bill of
   materials into the box at the foot of the panel (`acctRow` in views/card.js), and with every
   script blocked that link is the whole feature. It has to look exactly like the `<div class=
   "acct">` beside it, because it IS the same row - what says it can be pressed is the chevron,
   which is this card's own mark for "there is more behind this".
   NOTHING IN THIS FILE RESETS THE BROWSER'S OWN `a` RULE, so without this the row arrives
   underlined from end to end - text-decoration propagates to children and they cannot refuse it.
   Every other clickable element here already carries this same pair explicitly (.brandm, a.st,
   a.qopen, .seg a, .eye); this is the sixth, not a new idea. The hover mirrors
   `summary:hover .tchev` one line for one line, so a fold and a press feel the same. */
a.acct{text-decoration:none;color:inherit}
a.acct:hover .tchev{color:var(--brassInk)}
.pnlrule{border-top:1px solid var(--rule2);margin:8px 0 4px}
.costnote{font-size:11px;color:var(--mute);margin-top:9px;line-height:1.4}
/* THE WAY BACK, ABOVE THE PAGE HEADER. It rides on `.costnote` for its weight and colour - see
   `backRow` in views/layout.js, which reuses that class deliberately - and this rule owns only the
   rhythm around it, which `.costnote`'s 9px/1em could not carry once the title under it grew to
   the display step: the arrow and the icon tile ended up 20px apart and read as one block.
   On the sidebar widths it is strictly redundant - the rail says where you are - and it is kept
   because it is the only way back on a phone that is one tap rather than two. */
/* ============================================================================================
   THE EYEBROW - and it is the breadcrumb, drawn the way the reference draws it.

   The reference's phone screen puts a small uppercase `SALES` directly above the page title. This
   app already had an element in that exact position saying that exact thing: the breadcrumb, which
   on `/creative-request` reads `← Sales`. Drawing a NEW eyebrow would have been a second element
   naming the room the first one already names, on a page whose owner has asked three times for
   less text under a heading. So the breadcrumb is set as the eyebrow and there is no new node.

   THE ARROW STAYS. Without it this is a label that happens to be tappable; with it, it is
   obviously the way back - which is what it has always been and still is.
   ============================================================================================ */
/* THE WAY BACK IS A CONTROL, SO IT IS SHAPED LIKE ONE - the owner, 2026-09-18: "سهم الرجوع
   للسيلز يكون إلو شكل متناسق اكتر مع الصفحة". It was bare uppercase text with an arrow in front
   of it, sitting twelve pixels above a row of pills, and it read as a caption that happened to be
   a link rather than as the thing you press to leave.

   IT IS THE CHIP ROW'S OWN PILL, one step quieter: the same 34px height, the same 11px face, the
   same radius and border, on the page background rather than on a card so it never competes with
   the lit chip under it. Stacked, the two rows now read as one ladder - out of this section,
   across to a sibling page - which is what the owner's reordering made them.

   THE ARROW STAYS IN THE MARKUP, not in a pseudo-element: it is part of the sentence a screen
   reader says, and `backRow` has drawn it inline since it shipped. */
.backrow{margin:0 0 var(--sp-2)}
.backrow a{display:inline-flex;align-items:center;gap:6px;min-height:34px;padding:0 11px;
  border:1px solid var(--rule);border-radius:11px;background:transparent;
  font-family:var(--ui);font-size:11px;font-weight:var(--w-bold);letter-spacing:var(--ls-caps);
  text-transform:uppercase;text-decoration:none;color:var(--ink3)}
.backrow a:hover{border-color:var(--priRule);color:var(--priInk);background:var(--priBg)}
.backrow a:focus-visible{outline:2px solid var(--priInk);outline-offset:2px}

/* ---------- the one-line attention row ---------- */
.jrows{border-radius:18px;box-shadow:var(--shadow);background:var(--card);overflow:hidden;
  border:1px solid var(--rule)}
.jrow{border-top:1px solid var(--rule2)}
.jrow:first-child{border-top:0}
.jrowtog{display:flex;align-items:center;gap:10px;padding:13px 14px;min-height:52px;
  font-size:13.5px;color:var(--ink)}
.jrowtog .rpo{font-family:var(--ui);font-size:14px;font-weight:800;flex:0 0 auto;
  font-variant-numeric:tabular-nums}
.jrowtog .rtitle{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;color:var(--ink2);font-size:13px}
.jrowtog .flag{flex:0 0 auto;font-size:11px;color:var(--steel);white-space:nowrap}
.jrowtog .flag.late{color:var(--late)}
.jrowtog .flag.due{color:var(--signal-ink)}
.jrowtog .flag.hold{color:var(--mute)}
.jrowbody{padding:0 12px 12px}
.jrowbody .job{margin:0;box-shadow:none}

/* ---------- plain list surfaces (Numbers, Find, About) ---------- */
.rows,.res,.note{border-radius:18px;box-shadow:var(--shadow);background:var(--card);
  border:1px solid var(--rule);overflow:hidden}
/* AND `noclip` TAKES THAT ONE DECLARATION BACK — 2026-09-24, and it is the whole of the bug the
   owner reported as «بدي عميل تاني يشيك زر ال … لا يفتح لعمل الحذف».

   WHAT HAPPENED. `cardActs` (views/record.js) draws an action row as ONE `.row` inside one of
   these surfaces, and the `⋯` menu the owner asked Delete into is a `<details>` whose `.cwmenu`
   is `position:absolute; top:calc(100% + 6px)` - it opens BELOW the row. The row is the whole of
   the surface, so the menu opens entirely outside it, and `overflow:hidden` painted none of it.
   Measured in a real browser on `/creative-request/<key>` at 390, 834, 1024 and 1280: the
   `<details>` really was `open`, the two entries really were 190x44 and really were inside the
   viewport, and `document.elementFromPoint` at the centre of each returned `BODY`. The markup was
   right, the POST behind it was right, and a finger landed on nothing. That is why every test
   that read the HTML passed.

   IT IS A MODIFIER AND NOT A CHANGE TO THE RULE ABOVE, because that rule is what clips a list's
   first and last row to the 18px radius on every list in this app. An action-row surface has
   nothing to clip - one child, transparent, `border-top:0` - so it pays nothing to give the clip
   up, and no other surface is touched.

   AND IT IS NOT `:has()`. `.rows:has(.cwadd[open])` would have needed no markup at all and would
   have failed the WRONG WAY ROUND on an engine that does not know it - the menu invisible again,
   silently. That is this file's own stated rule about `:has()` (see `.wdays` further down) read
   back to it. A class the server writes works in every browser. */
.rows.noclip,.res.noclip,.note.noclip{overflow:visible}
.note{border-top:3px solid var(--risk);padding:14px 16px;font-size:13px;line-height:1.6;
  color:var(--ink2)}
.note b{color:var(--ink)}
.row{display:flex;align-items:center;gap:11px;padding:11px 14px;
  border-top:1px solid var(--rule2)}
.row:first-child{border-top:0}
.row .k{flex:1 1 auto;min-width:0;font-size:13.5px;color:var(--ink);overflow-wrap:anywhere}
.row .k em{display:block;font-style:normal;font-size:11.5px;color:var(--ink3);margin-top:2px}

/* ============================================================================================
   AND THE RECORD CARD'S STATUS BAR KEEPS ITS WORD AT 390 - 2026-09-26.

   ============================================================================================
   THE FAULT, MEASURED IN A REAL BROWSER AND NOT REASONED ABOUT. `creativeStatusBar` draws four
   flex items into one `.row`: the workflow word, the pending chip, the pause control and
   `Request action`. `.row .k` above is `flex:1 1 auto; min-width:0; overflow-wrap:anywhere` and
   every other item is `flex:0 0 auto`, so the word is the only thing that gives - and at 390 there
   is nothing left for it to give from.

     BEFORE TODAY, pending revision on a record at `Awaiting Client Review`, Chrome at 390:
       the chip took 143, `Request action` took 130, and the word was a cell 37px WIDE and
       111px TALL - six lines of one or two characters.
     AND WITH THE WITHDRAWAL CONTROL BESIDE THEM, which is the 26th's own addition:
       48 more pixels gone, the word a cell 1px WIDE and 371px TALL, and the whole bar 695px
       high on a 390px phone. The tab strip and everything under it went below the fold.

   IT IS THE `.cst.pair` FAULT ON A DIFFERENT SURFACE, WORD FOR WORD. That rule's own note six
   hundred lines down records «`Booth_wrap_expo` shredded into a vertical ribbon, and a row that
   had been 98px became 264px … exactly the class of fault this app has shipped twice and caught
   only by rendering». That fix was scoped to `.crows` - the LIST - and the record card's status
   bar was never given it.

   THE FIX IS THAT THE ROW WRAPS INSTEAD OF THE WORD. Two declarations: the row may take a second
   line, and the word has a floor under it. At 390 the word and its chip keep the first line and
   the two controls drop to the second, hard against the right edge where `margin-left:auto` on
   `.cwsplit` already puts them. Measured after: the word is one line of 14.5px type and the bar
   is 122px instead of 695.

   NOTHING WIDER THAN A PHONE CHANGES AT ALL, and that is measured rather than intended: at 834
   the four items and their gaps come to 523 of 592, at 1024 to 523 of 730 and at 1280 to 523 of
   1010, so the row never reaches its wrap point and the bytes are inert. There is no query here
   for that reason - `min-width` on a flex item IS the responsive rule, and it answers the real
   content box rather than the viewport, which is what this file asks `@container` for elsewhere.

   `:not(.acts)` IS WHAT KEEPS THIS OFF THE OTHER `.rows.noclip`. Exactly two things in this app
   emit that class - `creativeStatusBar`, whose row is bare, and `cardActs` in `views/record.js`,
   whose row is `.row.acts` and is a row of buttons with no `.k` in it at all. The action row is
   untouched, which is why the selector names it rather than trusting that it has no `.k`. */
.rows.noclip > .row:not(.acts){flex-wrap:wrap}
.rows.noclip > .row:not(.acts) > .k{min-width:min(100%,180px)}
/* AND THE CONTROLS TAKE THE SECOND LINE TOGETHER. A wrapping flex line breaks between ANY two
   items, so the first cut of the rule above put the pause control at the end of line one and
   `Request action` alone on line two - eleven pixels below the control he asked it to sit NEXT
   TO. `.cend` is `creativeStatusBar`'s own wrapper around whatever the caller put in that slot,
   so the slot is ONE item and wraps as one. `margin-left:auto` is where `.cwsplit` used to carry
   it and is what holds the block against the right edge on either line; `.cwsplit`'s own copy is
   inert inside a shrink-to-fit parent and is left alone rather than edited, because that rule is
   shared with `Send to pricing` at the foot of the same card. */
.cend{display:inline-flex;align-items:center;gap:11px;margin-left:auto}

/* ============================================================================================
   AND THE STATUS BAR WEARS ITS STATE'S COLOUR - ITEM 4, 2026-09-26.

   ============================================================================================
   THE OWNER: «the header bar I need to make it highlight according to the status so if the status
   is Design approved make it verified sign in the bar and make the highlight of the bar light
   green color and If the request was to revision or changing brief make it light blue , and if
   the request is canceled make it in Red ... and also do not forget the Waiting Assignment».

   NOT ONE NEW COLOUR. The five pairs are this file's own and every one of them is already a
   measured pair used as a chip tint: `--okBg`/`--ok`, `--shipBg`/`--ship`, `--riskBg`/`--risk`,
   `--lateBg`/`--late` and `--paper2`/`--ink3`. `.stat` has worn four of them since the app
   shipped and `.dstat` the fifth, so both themes and both contrast ratios are already paid for -
   which is the whole reason the bar was made to wear the TABLE's tone rather than a new palette.

   THE TINT IS THE BACKGROUND AND THE RULE IS THE EDGE, which is `.note`'s own construction ten
   lines up (`border-top:3px solid var(--risk)`) turned on its side. A tint alone is too quiet at
   the `--paper2` end to be seen as a signal; a rule alone is 4px of colour on a 390px phone. The
   two together read at arm's length in both themes, which is what was actually measured.

   `border-left` AND NOT `border-inline-start`, because this app's three RTL-ish strings are
   content and the shell is `dir="ltr"` throughout; every other directional rule in this file is
   physical and a single logical property here would be the odd one out.

   WHY `.rows` KEEPS ITS OWN BACKGROUND EVERYWHERE ELSE: every selector below needs `.wfbar`, and
   `creativeStatusBar` in `views/creative.js` is the only thing in this app that emits it. The
   eleven other `.rows` surfaces on the same card - the fact tiles, the version list, the request
   bar, the file table - are byte-for-byte what they were.

   MEASURED AT 390, 834, 1024 AND 1280 IN ALL TWELVE STATES. The 4px edge comes off `.row .k`,
   whose floor is `min(100%,180px)` from the rule above, so the wrap point moves by four pixels
   and the word still takes one line; the bar is 122px at 390 with a pending request on it, which
   is what the three rules of this morning left it at.

   THE TICK IS SIZED HERE AND NOWHERE ELSE. `I_STEP_DONE` declares 16px on the tag, which is a
   floor; 15px on the bar's 14.5px heading is the ring reading as a mark beside the word rather
   than as a second glyph competing with it. `vertical-align:-2px` sits it on the type's own
   baseline - measured, not guessed - and `currentColor` means it is already the tone's ink. */
.wfbar{border-left:4px solid var(--rule)}
.wfbar.wfb-rdy{background:var(--okBg);border-left-color:var(--ok)}
.wfbar.wfb-act{background:var(--shipBg);border-left-color:var(--ship)}
.wfbar.wfb-hold{background:var(--riskBg);border-left-color:var(--risk)}
.wfbar.wfb-iss{background:var(--lateBg);border-left-color:var(--late)}
.wfbar.wfb-dlv{background:var(--paper2);border-left-color:var(--ink3)}
.wfvfy{display:inline-flex;margin-right:6px;vertical-align:-2px}
.wfvfy svg{width:15px;height:15px}

/* ============================================================================================
   THE DESIGNER'S ACCEPT, DIRECTLY UNDER THE BAR - ITEMS 3 AND 5, 2026-09-26.

   «just make it as a visible button below the header bar as we say before , and make it in nice
   way». That sentence is the whole of the brief on its appearance, so what it got is this app's
   own primary-action treatment and nothing invented: `--go` on the fill, which is `--ok` and is
   what `.okmark` already wears, `--r-md`-sized corners in the family every other control here
   uses, and 44px of height - the floor for anything a finger presses, which every button this
   round has been measured against.

   IT IS A ROW AND IT WRAPS, because a record can legitimately offer two of these at once (a
   pending cancellation is both an ask to confirm and a state to acknowledge) and because a
   button whose label is `Confirm — starting the revision` is 240px wide at 13.5px type. At 390 a
   second one takes its own line; at 834 and above they sit side by side. MEASURED, in both
   themes, in every state that can draw one.

   THE ANCHOR AND THE BUTTON ARE ONE CLASS AND THAT IS DELIBERATE. Two of the six acknowledgements
   open a panel (the server wants words) and four post directly; a reader must not have to learn
   which is which from the way the control looks, because the difference is not theirs - it is the
   store's. `text-decoration:none` and the explicit font are what make an `<a>` and a `<button>`
   render identically here, which is the `.stat.go` / `a.stat` pair's own arrangement.

   EVERY SELECTOR NEEDS `.wfack`, which `confirmBar` in `views/creative.js` is the only thing in
   this app that emits. `.card button`, `.acts > a`, `.chip` and `.prow` are untouched. */
.wfack{display:flex;flex-wrap:wrap;gap:10px;margin-top:10px}
.wfack .wfackf{margin:0}
.wfack .wfackb{display:inline-flex;align-items:center;justify-content:center;min-height:44px;
  padding:0 18px;border:1px solid transparent;border-radius:var(--r-md);background:var(--go);
  color:var(--paper);font-family:var(--ui);font-size:13.5px;font-weight:700;letter-spacing:-.005em;
  text-decoration:none;cursor:pointer}
.wfack .wfackb:hover{box-shadow:0 0 0 1.5px var(--go)}
.wfack .wfackb:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
/* ============================================================================================
   A DOOR ROW - the hub's own row, and the reference's phone list item.

   THE REFERENCE ASKS FOR 60-68px: mark, label, count, chevron. IT IS ALL FOUR SINCE 2026-09-18.
   The slot that held the sentence now holds the figure, which is what the reference put there and
   what the owner asked for - see `rows` in views/hub.js for why a door may have no figure at all
   and why that draws nothing rather than a zero.

   THE GRID IS WHAT PUTS THE CHEVRON AT THE END without a wrapper element around the row's parts.
   THREE COLUMNS NOW AND NOT TWO SPANNED ROWS: the label takes the free width, the figure sizes to
   its own digits, the chevron sits last. `grid-row:1/3` came off the chevron with the second line;
   a one-row grid whose only child claimed two rows was harmless and is now a lie about the shape.

   THE FIGURE IS `--t-body` AGAINST THE LABEL'S `--t-sm` and both are `--w-bold`. It is the one
   thing on the row that changes, so it is the one thing drawn a step up; heavier AND larger would
   have made a hub of twelve numbers with a word beside each. Tabular figures so a column of counts
   lines up down the list rather than shuffling by a pixel per digit.
   ============================================================================================ */
.row.door{padding:var(--sp-3) var(--sp-4);min-height:64px}
.row.door a.qopen{display:grid;grid-template-columns:minmax(0,1fr) auto auto;
  column-gap:var(--sp-3);align-items:center;width:100%}
.row.door .qno{grid-column:1;font-size:var(--t-sm);font-weight:var(--w-bold);color:var(--ink)}
.row.door .dn{grid-column:2;font-family:var(--ui);font-size:var(--t-body);
  font-weight:var(--w-bold);color:var(--ink);font-variant-numeric:tabular-nums;line-height:1}
/* THE CHEVRON, DRAWN OUT OF TWO BORDERS. It is `::after` on the anchor rather than a glyph in the
   markup because five inline SVGs cost this hub 1,257 raw bytes and 16 elements on every render -
   see the note above `rows` in views/hub.js. A box with its right and bottom edges, turned 45
   degrees, is the same mark for nothing. */
.row.door a.qopen::after{content:"";grid-column:3;align-self:center;
  width:7px;height:7px;margin-right:2px;border:solid var(--ink3);border-width:0 1.6px 1.6px 0;
  transform:rotate(-45deg)}
.row.door a.qopen:hover::after{border-color:var(--priInk)}
.row.door a.qopen:hover .dn{color:var(--priInk)}
/* `0 1 auto` AND NOT `0 0 auto`, WHICH IS WHY A LABEL USED TO COLLAPSE TO ONE CHARACTER WIDE.
   The value refused to shrink, so on a 390px client page the row's other children - a 183px name,
   a `Correct` chip and a chevron - asked for more than the row had, and the ONLY child that could
   give was `.k`, which has `min-width:0` and `overflow-wrap:anywhere` beside it. `Client name` was
   squeezed to 23px and broke as `Cli / ent / na / me`, turning a 42px row into 100px. Letting the
   value shrink moves the give to the side that can take it: the name wraps at a space and the
   label keeps its own width. NO `min-width:0` HERE ON PURPOSE - the automatic minimum size is what
   stops a figure being squeezed narrower than its longest unbreakable run, so `IQD 3,990,000`
   wraps before `3,990,000` and never breaks between two digits. */
.row .v{flex:0 1 auto;font-family:var(--ui);font-size:13px;font-weight:750;color:var(--ink);
  font-variant-numeric:tabular-nums}
.bar{flex:0 0 90px;height:7px;border-radius:999px;background:var(--paper2);overflow:hidden}
.bar > i{display:block;height:100%;background:var(--brass);border-radius:999px}
/* The twenty widths a bar can have. See the note above barList() in pages.js: an inline
   style="width:N%" would require style-src 'unsafe-inline', and that header is not worth two
   pixels of accuracy on a bar whose exact number is printed beside it. */
.w5{width:5%}   .w10{width:10%} .w15{width:15%} .w20{width:20%} .w25{width:25%}
.w30{width:30%} .w35{width:35%} .w40{width:40%} .w45{width:45%} .w50{width:50%}
.w55{width:55%} .w60{width:60%} .w65{width:65%} .w70{width:70%} .w75{width:75%}
.w80{width:80%} .w85{width:85%} .w90{width:90%} .w95{width:95%} .w100{width:100%}
/* ============================================================================================
   THE TWO CHARTS, 2026-09-20 - AND EVERY GEOMETRY NUMBER IS IN THE MARKUP, NOT HERE.

   `views/charts.js` writes the bars, the baseline and the labels as presentation attributes on an
   inline `<svg>`, because `style-src 'self'` refuses an inline style and nothing about a chart is
   worth re-opening that header for. What is HERE is only what a stylesheet is for: colour, type,
   and the one size decision below. Decoration in the file that downloads once; markup paid for on
   every visit.

   `max-width` IS THE WHOLE SIZE ARGUMENT AND IT IS NOT A TASTE. A `viewBox` with `width:100%`
   scales the drawing - labels, bar widths, stroke, all of it - by the box it is given. The box is
   sized at roughly one user unit per CSS pixel on a PHONE (nine months is 240 units inside ~300px
   of tile), so the phone is the 1:1 case and everything else scales UP. Without a ceiling the
   Numbers tab at 1280 hands it ~1000px, a 3.3x blow-up that turns an 8px month label into 26px
   and the bars into slabs. MEASURED, not guessed, at both ends: 500px caps the desktop at 2.03x,
   where a month label renders at 16.3px against the page's own 15px body, and the phone's 362px
   is 1.47x, where the same label is 11.8px. Both were read off a rendered page at 390 and 1280,
   which is the only way either number could have been arrived at. The card never reaches the cap
   - it measures 330px on a phone and 464px at 1280 - and never needed one.

   THE TWO SERIES ARE `--s1` AND `--s2`, which is what those tokens were cut for - the comment in
   the token block calls them "the station bar's two ends" and this is the second thing to want a
   pair. Both are defined in all three themes, so the chart inverts with the page rather than
   staying a light-mode drawing on a dark card.

   `.ax.gap` IS THE MONTH NOBODY WROTE ANYTHING DOWN FOR, and it is the only mark on either chart
   that means an ABSENCE. A solid baseline under a column with no bar means the figure is zero; a
   dotted one means there is no figure. See the header of `views/charts.js` - 61 of the 536 live
   projects have one of these in the middle of their own series, and drawn the ordinary way it is
   the same empty space as a zero.
   ============================================================================================ */
.chart{margin:0 0 14px}
.cht{display:block;width:100%;max-width:500px;height:auto}
.cht .ax{stroke:var(--rule);stroke-width:1}
.cht .ax.gap{stroke:var(--ink3);stroke-width:1;stroke-dasharray:1.5 2.5;opacity:.5}
.cht .c1{fill:var(--s1)}
.cht .c2{fill:var(--s2)}
.cht .ml,.cht .pk{font-family:var(--mono);font-size:8px;fill:var(--ink3)}
.cht .ml{text-anchor:middle}
.cht .yr{font-family:var(--mono);font-size:7px;fill:var(--ink3);text-anchor:middle;opacity:.75}
/* The legend, and it carries the figures - on the money chart it carries ALL of them, because a
   numeral inside the `<svg>` is one the owner's Hide-IQD switch cannot paint over. */
.chart .ck{display:flex;flex-wrap:wrap;gap:6px 18px;margin-top:7px;font-family:var(--ui);
  font-size:11px;font-weight:650;letter-spacing:.06em;text-transform:uppercase;color:var(--ink3)}
.chart .ck > span{display:inline-flex;align-items:center;gap:7px;min-width:0}
.chart .ck > span::before{content:"";flex:0 0 auto;width:9px;height:9px;border-radius:3px}
.chart .ck > .c1::before{background:var(--s1)}
.chart .ck > .c2::before{background:var(--s2)}
.chart .ck b{font-family:var(--ui);font-size:12px;font-weight:750;letter-spacing:0;
  text-transform:none;color:var(--ink);font-variant-numeric:tabular-nums}
.sig-ok{color:var(--ok)}
.sig-risk{color:var(--risk)}
.empty{padding:20px 16px;font-size:13px;color:var(--ink3);text-align:center}
/* ============================================================================================
   AN EMPTY CATEGORY, COMPACT - the owner's stage 6, 2026-09-18.

   HIS WORDS: a category with nothing in it draws a short line "at roughly 80-100px, not a 150px+
   card". MEASURED BEFORE THE CHANGE, on the Creative Request page at 390px: the heading block and
   the empty box together came to 40 + 8 + 61 = 109px per empty list, and all three were drawn at
   once, so a page with nothing on it was 327px of headings saying nothing three times.

   WHAT ACTUALLY GOT IT UNDER 100 WAS THE TABS AND NOT THIS RULE, and that is worth writing down.
   One list is drawn at a time, and the heading came off the section because the lit tab already
   carries the label and the count (see `section2` in views/creative.js). So the empty state is now
   the tab strip plus one box: the "short line plus one short line" of his own example, where the
   first line is the tab that says WHICH list is empty and the second says it is empty. There is no
   second sentence explaining where a row would come from - those were removed on his instruction on
   2026-09-16 and are not coming back as an empty state.

   THIS RULE IS THE REST OF THE WAY: 20px of padding to 14, which takes the box from 61px to 49 and
   the whole empty tab to ~99px at 390. It is a MODIFIER and not a change to `.empty`, because
   `.empty` is also the delivered row's version line, the not-configured banner and the brief
   paragraph - three things that are not a category with nothing in it and that would each lose a
   line's breathing room for no reason.
   ============================================================================================ */
.empty.tight{padding:14px 16px}

/* ---------- the quotation board ----------
   FIVE RULES, ALL OF THEM INSIDE THE LIST SURFACE ABOVE. A quotation is drawn as a `.row` with the
   card's own `.stat` chip on the end of it, so the board needed no chip, no tint and no tile of its
   own; what is here is the three things a `.row` has never had to hold before.

   THE PICKER PANEL IS A SIBLING OF ITS ROW, inside the same `.rows` box, because `.row` is a flex
   line and a panel inside one would be laid out beside the chip instead of under it. It therefore
   takes the row's own 14px gutter, exactly as `.job > .pick` takes the card's 15px - and, like
   that one, it is an empty box until `:target` names it, since both of its children are out of flow
   at rest. */
.rows > .pick{margin:0 14px}
/* The number, in the interface face and tabular so a column of them lines up - the same treatment
   `.jrowtog .rpo` gives a PO. */
.qno{font-family:var(--ui);font-weight:800;font-variant-numeric:tabular-nums;color:var(--ink2)}
/* HOW LONG IT HAS BEEN SITTING, which is the one thing on this page that is worth a colour of its
   own. Amber at a week, red at a fortnight, and only while the quotation is still open - see
   `ageTone` in `src/rfqs.js`. Plain otherwise: `.row .k em` is already the right grey. */
.qage{font-weight:750;font-variant-numeric:tabular-nums}
.qage.due{color:var(--risk)}
.qage.late{color:var(--late)}
/* The project name opens the record. `color:inherit` for the same reason `a.ddb` takes it: the row
   decides its own text colour and a link must not repaint it. */
a.qopen{text-decoration:none;color:inherit}
a.qopen:hover{color:var(--brassInk)}

/* ============================================================================================
   THE CREATIVE LISTS - the work rows on Creative Request and Creative Design.
   ============================================================================================
   THE FIRST BYTES ADDED TO THIS FILE SINCE THE SEARCH SLICE, and the owner was asked for them and
   agreed: "بدي اعمل واجهة احترافية و متناسقة و مرتبة" - a professional, consistent, tidy
   interface - and "الابعاد و النسب تكون مدروسة" - the proportions thought about. That could not be
   done out of the existing vocabulary alone, because the existing vocabulary had no ROW TITLE:
   `.row .k` is 13.5px body text and `.qno` - the only heavier thing a row has ever carried - is the
   interface face in `--ink2`, tabular, designed for a QUOTATION NUMBER. A project name drawn in it
   came out grey and quieter than the status chip beside it, which is the wrong way round on a list
   you read by name. Everything below fixes that and gives the two columns a rhythm.

   THE SCALE, AND IT IS FOUR SIZES AND NOT MORE:
     14.5 / 640   the project name        - the thing you are scanning for
     11.5 / 400   the line of facts       - `.row .k em`, unchanged, the app's existing secondary
     10   / 750   the message mark        - micro type, uppercase, the chip's own family
      9.5 / 750   the status chip         - `.stat`, unchanged
   NO NEW COLOUR AND NO NEW TOKEN: every value below resolves through the palette that already
   exists in all three theme states, which is this stylesheet's standing rule.

   TWO COLUMNS: IDENTITY LEFT, STATE RIGHT. `align-items:flex-start` and not `center`, so the chip
   sits level with the NAME rather than floating between the two lines - `.jidrow` on the order card
   made exactly this correction for exactly this reason and the note there explains it at length.
   The right column is a flex column, so the mark stacks under the chip and both hold one right edge
   however wide the widest of them is.

   IT IS ON THE LIST AND NOT ON THE ROW. `.crows` is the class the two creative pages put on their
   `.rows` box; the rows inside it are the same `<div class="row" id="…">` every other list in this
   app emits, so nothing here can reach the RFQ board, the quotation list, the client list or the
   Numbers page, and a row keeps one set of bytes across the whole app.
   ============================================================================================ */
.crows > .row{align-items:flex-start;gap:12px;padding:12px 14px}
.crows > .row .k{font-size:13px}
/* THE PROJECT NAME. `overflow-wrap:anywhere` because these are typed by people and arrive as
   `Car_branding_fleet_of_nine` - one unbroken token wider than a phone. It breaks rather than
   pushing the state column off the row. */
.cttl{display:block;font-size:14.5px;font-weight:640;letter-spacing:-.012em;line-height:1.28;
  color:var(--ink);overflow-wrap:anywhere}
.crows > .row .k em{margin-top:4px;line-height:1.5}
/* THE STATE COLUMN. `flex:0 0 auto` so it takes what it needs and the name takes the rest. */
.cst{flex:0 0 auto;display:flex;flex-direction:column;align-items:flex-end;gap:6px;min-width:0}

/* ============================================================================================
   TWO CHIPS IN THE STATE COLUMN - item 4, 2026-09-21: "لازم يكون جنب الستاتوس `Designer
   Assignment`".
   ============================================================================================
   `.cst.pair` AND NOT `.cst`, WHICH IS THE WHOLE CARE IN THIS RULE. Three lists share `.cst` - the
   designers' bench, Creative Request and the RFQ board - and the other two put a chip above a
   message mark in a COLUMN, deliberately. Widening the base class would have turned every one of
   those into a row and moved two pages nobody asked to change. The bench's row asks for the
   modifier; the others are untouched bytes.

   THE ROW IS THE WIDE FORM AND THE COLUMN IS THE PHONE FORM, AND THAT SPLIT IS A MEASUREMENT
   RATHER THAN A PREFERENCE. The first cut of this rule was `row wrap` at every width. Rendered in
   Chrome at 390px with a real designer on the card, the pair measured 186px wide - and with the
   4px rail, the two 30px controls and four 12px gaps around them, that left the project name a
   cell **44px wide and 240px tall**: `Booth_wrap_expo` shredded into a vertical ribbon, and a row
   that had been 98px became 264px. Exactly the class of fault this app has shipped twice and
   caught only by rendering.

   SO AT PHONE WIDTH THE TWO CHIPS STACK, hard against each other at the right edge, and the name
   keeps its column. "جنب الستاتوس" is answered at every width a person can see both chips on one
   line, and at the one width where that is impossible the assignment is the next thing after the
   status rather than somewhere else on the card - which is what the instruction is for.

   768px IS THE FILE'S OWN SECOND BREAKPOINT (390/768/1024/1280) and the measured point where the
   text column survives the pair: at 768 the name keeps 380px. `max-width:56%` is the floor under
   it from there up - a two-person designer cell ("Layth Hassan, Salim Aziz") would otherwise take
   whatever it liked, because `flex:0 0 auto` gives this column what it asks for. */
.cst.pair{gap:5px;max-width:36%}
/* AND THE ROW'S OWN GAPS COME IN ON A PHONE. A `.crows` row is five flex items - the rail, the
   name, the state column, the dots and the chevron - so a 12px gap is 48px of a 362px content
   box, and every pixel of it comes out of the only item that has anything long to say. 9px is
   still a clear separation at this type size and hands the name back 12px. Measured: with the
   cap above it, the project name keeps ~128px at 390px against the 51px the first cut left it. */
@media (max-width:767px){
  .crows > .row{gap:9px}
}
/* AND A CHIP IN THIS COLUMN MAY WRAP ITS OWN TEXT, WHICH IS THE ONE PLACE `.stat` GIVES UP
   `white-space:nowrap` - and it is a measured fault rather than a preference. Their sheet's
   Designer cells really hold TWO names ("Layth Hassan, Salim Aziz" is in the fixture because it
   is in the data), and a chip that cannot break is a chip as wide as both names: measured in
   Chrome at 390px, the column took 250px and left the project name a 51px cell. A chip with its
   text on two lines is a chip; a project name two characters wide is not a name. The cap above is
   what it wraps INSIDE, and `text-align:right` keeps the second line hard against the edge the
   column is aligned to. */
.cst.pair .stat{white-space:normal;text-align:right}
@media (min-width:768px){
  .cst.pair{flex-flow:row wrap;justify-content:flex-end;align-items:center;gap:5px 6px;
    max-width:56%}
}

/* THE DESIGNER CHIP. `--brassBg`/`--brassInk` are the tokens the designer PICKER already wears
   (`controls()` gives its roster `tone: () => 'brass'`), so the chip and the panel it opens are
   one thing and no colour was invented for this.

   `.none` IS THE UNASSIGNED STATE AND IT IS QUIETER, not louder. "Not assigned" is an absence;
   painting it in the creative side's own colour would make every untaken job the brightest thing
   on the list. It keeps the neutral chip's surface and the dimmest readable ink - and it is still
   a LINK where the reader may assign, which is what says it can be acted on. */
.stat.brass{background:var(--brassBg);color:var(--brassInk)}
.stat.brass.none{background:var(--paper2);color:var(--ink3)}

/* ============================================================================================
   THE DEADLINE AND WHO ASKED - the rest of item 4, on the row's third line.
   ============================================================================================
   "بحيث يظهر بشكل واضح ولكن انتبه ليس بشكل كبير و سيئ ولا بشكل مخفي" - clear, but not big and
   bad, and not hidden. The label is the 9.5px uppercase face the field list already uses and the
   value is ordinary body ink: what makes these findable is that they are always in the same place
   with the same word in front of them, not that they shout.

   IT WRAPS AS A WHOLE MARK. `.cmt` is `inline-flex` with `white-space:nowrap` on the pair, so
   `DEADLINE 2026-10-09` never breaks between its label and its date - a date that wrapped away
   from its word would read as a loose number. Two marks that do not fit put the second on its own
   line, which at 390px is what happens with a long salesperson's name. */
.cmeta{display:flex;flex-wrap:wrap;gap:3px 14px;margin-top:5px}
.cmt{display:inline-flex;align-items:baseline;gap:5px;white-space:nowrap}
.cmt > i{font-style:normal;font-family:var(--ui);font-size:9.5px;font-weight:750;
  letter-spacing:.09em;text-transform:uppercase;color:var(--ink3)}
.cmt > b{font-size:12px;font-weight:600;color:var(--ink2);font-variant-numeric:tabular-nums}

/* THE MESSAGE MARK - "there is a conversation on this one, and this side wrote last".
   IT IS NOT A SECOND CHIP, deliberately. No fill and no border: a filled pill under a filled pill
   reads as two states, and this is not a state - it is a note about the record. The dot carries the
   tint, the word carries the meaning, and `currentColor` keeps the two from ever disagreeing.
   AN UNKNOWN SIDE KEEPS THE GREY IT INHERITS, which is the honest colour for "the record does not
   say" - see `creativeSide` in `src/creative.js` for why an admin's message has no side. */
.mmk{display:inline-flex;align-items:center;gap:6px;font-family:var(--ui);font-size:10px;
  font-weight:750;letter-spacing:.06em;text-transform:uppercase;white-space:nowrap;
  color:var(--ink3);font-variant-numeric:tabular-nums}
.mmk > i{flex:0 0 7px;width:7px;height:7px;border-radius:50%;background:currentColor}
.mmk.sales{color:var(--ship)}
.mmk.creative{color:var(--brassInk)}

/* THE ACTION ROW inside an opened record: everything that can be done to it, on one line.
   `flex-wrap` and a row-gap because three links do not always fit 390px, and a wrapped line is
   better than a link squeezed to two characters.

   EVERY RULE IS SCOPED UNDER `.acts` AND NONE OF THEM IS A BARE CLASS, and that is this file's own
   hard-won rule rather than a preference. The first cut of this block styled `.act` - and `act` is
   already a TONE WORD in two of this app's vocabularies: `.stat.act` is the blue "Active" status
   chip and `.pchip.act` is the "Active" row inside a picker. A bare `.act` here matches both, sits
   LATER in the file than either, and wins every same-specificity tie - so an "Active" chip came out
   at 12px instead of 9.5 and an "Active" picker row lost 8px of its height, on the RFQ board, the
   quotation list and the order card. Exactly the `.ok` bug the banner block further down carries
   its own paragraph about. `.acts > a` cannot reach anything that is not in one of these rows.

   THE DESTRUCTIVE ONE IS PUSHED TO THE FAR END BY THE STYLESHEET and not by a spacer element: a
   delete hard against an attach is a delete a thumb finds by accident. It is grey at rest and red
   under the finger, which is `.bx button`'s own treatment for the same kind of control. */
.row.acts{flex-wrap:wrap;gap:8px 16px;padding:12px 14px}
.acts > a{display:inline-flex;align-items:center;min-height:24px;font-family:var(--ui);
  font-size:12px;font-weight:700;text-decoration:none;color:var(--brassInk);white-space:nowrap}
.acts > a:hover{text-decoration:underline}
.acts > a.gone{margin-left:auto;color:var(--ink3)}
/* THE `⋯` MENU SITS WHERE `Delete this request` USED TO - 2026-09-24. That control carried
   `margin-left:auto` and therefore the right edge of the action row; the owner moved it INTO a
   three-dot menu, and without this the move would have quietly relocated it to the middle of the
   row beside `+ Attach`. The position is part of what was being kept. */
.acts > details.cwadd{margin-left:auto}
.acts > a.gone:hover{color:var(--late)}

/* THE ATTACH FORM, FOLDED until the `+ Attach` link names it. The `.pick` mechanism - one `:target`
   per document, no script - applied to a form rather than to a chip list; `scroll-margin` for the
   same reason `[id^="land-"]` carries one, so the fragment jump does not leave it under the
   masthead. */
.cfold{display:none}
.cfold:target{display:block;scroll-margin:96px 0 26px}
/* AND OPENED BY THE SERVER WHEN THE SERVER IS REFUSING SOMETHING. A POST answered with a page has
   no fragment to `:target` with, so a refusal drawn inside this form would be a red line inside
   `display:none`. The server knows which row it refused; the open state is a class on that one
   form. See `creativeBriefRoute` in `app.js`. */
.cfold.open{display:block}

/* ============================================================================================
   THE STATUS RAIL - 2026-09-17, and the whole of the owner's "بدي لون للحالة" answered in 4px.
   ============================================================================================
   HE ASKED FOR GREEN ON `Approved` AND WAS OFFERED FOUR, because one colour on one state only ever
   says "not that one" about every other row. The four are the four TONES THIS FILE ALREADY HAS and
   no fifth colour was invented: `--ok`, `--ship`, `--risk`, `--late`, each already restated in all
   three theme states, each already carrying a chip somewhere in this app.

   IT IS NEVER THE ONLY SIGNAL. This file's standing rule - "the colour is never the only signal:
   the word is on the chip as well" - is kept by construction here: the rail has no word BECAUSE
   the status chip two columns to its right is the word, on the same row, always. A reader who
   cannot tell the four apart reads the chip; the rail is what makes a list of eleven scannable at
   arm's length.

   IT IS NOT THE CHIP'S OWN TONE, and that is the reason it is a second mapping rather than a reuse
   of `creativeStage().tone`. Two of our four answers - `On Hold` and `Rejected` - are BOTH `hold`
   amber on the chip, which is correct there because the word disambiguates them and wrong here
   because a rail has no word. See `creativeBarTone` in `src/creative.js`.

   `align-self:stretch` AND NOT A HEIGHT. The row is `align-items:flex-start` (see `.crows > .row`),
   so a rail with a fixed height would be a stub beside a two-line card and a bar beside a one-line
   one. Stretching makes it exactly as tall as the row it belongs to, which is what a rail is. */
.cbar{flex:0 0 4px;align-self:stretch;min-height:34px;border-radius:999px;background:var(--ink3);
  opacity:.9}
.cbar.rdy{background:var(--ok)}
.cbar.act{background:var(--ship)}
.cbar.hold{background:var(--risk)}
.cbar.iss{background:var(--late)}

/* THE CHEVRON - the mark that says which way pressing this row goes, and the way back out of it.
   THE ROW HAD NO WAY OUT UNTIL 2026-09-17: the head's `href` when the row was open was a bare
   fragment on a URL that still carried `?open=`, so it opened and never closed. The close is a
   real navigation (see `requestRow`); this is the affordance for it, and the quarter-turn is the
   whole of the state, which is why it needs no script - the server already knows which row is open.
   `.jfold` on the order card turns the same way for the same reason.
   44px OF TARGET AND 18px OF GLYPH. It is the last thing on a flex row on a 390px phone, which is
   exactly where a thumb lands, and a 18px hit area there is a control nobody can press. */
.cvx{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;margin:-4px -6px -4px 0;border-radius:11px;color:var(--ink3);
  transition:transform .14s ease,color .14s ease,background .14s ease}
.cvx:hover{color:var(--brassInk);background:var(--paper2)}
.cvx:focus-visible{outline:2px solid var(--brassInk);outline-offset:2px}
.cvx.on{transform:rotate(90deg);color:var(--brassInk)}

/* ============================================================================================
   THE FACTS UNDER AN OPEN CARD - a small field list, 2026-09-17.
   ============================================================================================
   "يظهر بشكل واضح ولكن انتبه ليس بشكل كبير و سيئ ولا بشكل مخفي" - clearly, but not big and ugly,
   and not hidden. What it replaces is five facts joined by middle dots in 11.5px grey on the row
   itself, which on a phone was a four-line paragraph in which no fact could be found by eye.

   A PAIR GRID AND NOT A `<table>`. Six table rows are six 44px lines and a value column that
   cannot wrap; `auto-fit` + `minmax(150px,1fr)` is two columns at 1280 and one at 390, and the
   label sits ABOVE its value rather than beside it, so a long client name has the whole track.
   The measurements are `.figs`' own - the same grid, at a quarter of the type size, because this
   is a field list and not a figure. */
.flds{display:grid;gap:10px 14px;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  padding:12px 14px;border-top:1px solid var(--rule)}
.rows > .flds:first-child{border-top:0}
.fld{min-width:0}
/* THE ONE FIELD THAT TAKES THE WHOLE ROW, and it is the minted number. `AZD_2026_030_Design_Booth`
   is 25 unbroken characters and the longest measured on the account team's tab is 38; a 150px
   track breaks it mid-word (`C0002_2026_005_Boo` / `th_graphics_Baghdad`), which is the identifier
   a person copies out rendered as two nonsense halves. `1/-1` is the whole row at every width -
   one column on a phone, five at 1280 - which is `.nogrid > .long`'s own answer to the same
   question on the quotation-document form. */
.fld.wide{grid-column:1/-1}
.fld > i{display:block;font-style:normal;font-family:var(--ui);font-size:9.5px;font-weight:750;
  letter-spacing:.09em;text-transform:uppercase;color:var(--ink3);margin-bottom:3px}
.fld > b{display:block;font-size:13px;font-weight:600;color:var(--ink);line-height:1.35;
  overflow-wrap:anywhere}

/* THE BRIEF, UNDER THE FACTS. `.empty` is a centred grey line and a brief is a paragraph somebody
   typed, so it is left, dark enough to read, and it keeps the newlines it was written with.

   STILL HERE BECAUSE CREATIVE REQUEST STILL DRAWS IT. The designers' bench moved its brief to the
   top of the card and into `.brief` below on 2026-09-21 (item 5); the sales page's own card is
   where the brief was WRITTEN and keeps the order it has had since September - facts, then the
   words, then what came back. One page's reading order changed because one page has a reader who
   must notice it. */
.empty.brf{text-align:left;color:var(--ink2);white-space:pre-wrap;line-height:1.55;
  padding:13px 14px}

/* ============================================================================================
   THE BRIEF, FIRST ON THE CARD - item 5, 2026-09-21: "البريف يلي بيكتبو السيلز لازم يكون ظاهر
   بشكل يقدر المصمم يلاحظو".
   ============================================================================================
   IT IS A BOX OF ITS OWN AND NO LONGER A VARIANT OF THE EMPTY STATE. `.empty.brf` above is the
   "there is nothing here" line overridden three ways to be readable; a paragraph a person wrote
   for this reader should not be borrowing the styling of an absence.

   WHAT MAKES IT NOTICEABLE IS POSITION AND A RULE, NOT SIZE. It is the first block on the card
   and it carries a 3px `--brassInk` edge - the creative side's own colour, already on this page
   in the designer chip and every link. The type is 14px/1.6, which is one step up from a field
   value and one step DOWN from a heading: "ليس بشكل كبير و سيئ".

   `white-space:pre-wrap` AND `overflow-wrap:anywhere` TOGETHER. A brief is typed in a textarea
   with real newlines, and it can also contain a pasted 60-character address with no space in it;
   the first keeps the shape the writer gave it and the second stops the second thing pushing the
   card sideways at 390px. */
.brief{border-radius:18px;box-shadow:var(--shadow);background:var(--card);
  border:1px solid var(--rule);border-left:3px solid var(--brassInk);
  padding:12px 15px 14px;margin:0 0 12px}
.brief > i{display:block;font-style:normal;font-family:var(--ui);font-size:9.5px;font-weight:750;
  letter-spacing:.09em;text-transform:uppercase;color:var(--ink3);margin-bottom:5px}
.brief > p{margin:0;font-size:14px;line-height:1.6;color:var(--ink);white-space:pre-wrap;
  overflow-wrap:anywhere}

/* ============================================================================================
   ONE ATTACHED FILE, AS A ROW - the owner's ask of 2026-09-17.
   ============================================================================================
   "بدي الملفات تظهر كاسطر و معلومات عنها ... ومابدي تكون الصورة كبيرة" - as rows, with information
   about them, and NOT a big picture. The 44px tile is the whole of that instruction; it is a number
   in one place, so both pages and every kind of file agree about it.

   `aspect-ratio:1` AND `object-fit:cover` ON THE TILE, which is the one place in this app that
   crops rather than letterboxing - and it is the right trade HERE for the reason `.shot.draw`
   letterboxes THERE: a 44px square is an identifier, not a drawing. A contained thumbnail at this
   size is a stamp-sized image with grey bars round it, which identifies nothing. The full file is
   one tap away at its own URL, uncropped.

   THE GLYPH BRANCH IS NOT A FAILURE STATE. It is the answer for a HEIC off an iPhone, a PDF on a
   local store, and every link - so it is a tidy tinted tile with the app's own icon in it, sized
   identically, and never a dashed placeholder or a broken frame. */
/* `.row.frow` AND NOT `.frow`, CORRECTED 2026-09-17. This rule was a bare `.frow{gap:11px}` and
   there is a SECOND bare `.frow` further down this file - the form footer that lays out Send and
   Cancel - which declares `gap:4px 18px`. Same specificity, later in the file, so the later one won
   every tie and every file row on both creative pages has been drawing an 18px gap between its
   thumbnail and its name since the day both landed. The two shapes share a name honestly (a FILE
   row and a FORM row) and the fix is the one that costs nothing: scope this one to the element it
   is actually on, which is a `.row`. The form footer is a bare `<div class="frow">` and cannot
   match it. */
.row.frow{gap:11px}
.fth{flex:0 0 44px;width:44px;height:44px;aspect-ratio:1;border-radius:11px;overflow:hidden;
  display:flex;align-items:center;justify-content:center;background:var(--sheet);
  border:1px solid var(--rule)}
.fth img{width:100%;height:100%;object-fit:cover;display:block}
.fth.none{background:var(--paper2);color:var(--ink3)}
.fth.none svg{width:19px;height:19px}
.frow .k em{font-variant-numeric:tabular-nums}

/* ASK ABOUT THIS ONE FILE - the control at the end of a file row, 2026-09-17.
   IT IS WHAT THE OWNER'S DRAG BECAME. HTML5 drag-and-drop is a script API and does not fire under
   a thumb on an iPhone at all; this is a LINK to the same page with `ask=` on it, which lands the
   reader in the message box with the file named on the message they are about to send. See
   `askAbout` in `views/creative.js`.
   44px OF TARGET AND 15px OF GLYPH, `.cvx`'s own measurement for the same reason: it is the last
   thing on a flex row on a 390px phone, which is exactly where a thumb lands. The word rides with
   the glyph because an icon alone on a row full of file names is a guess. */
.cask{flex:0 0 auto;display:inline-flex;align-items:center;gap:5px;min-height:30px;padding:0 9px;
  border-radius:9px;font-family:var(--ui);font-size:11px;font-weight:700;letter-spacing:.02em;
  text-decoration:none;color:var(--ink3);background:var(--paper2);white-space:nowrap}
.cask:hover{color:var(--brassInk);background:var(--brassBg)}
.cask:focus-visible{outline:2px solid var(--brassInk);outline-offset:2px}

/* ============================================================================================
   THE CARD'S MENU - three dots in the corner, and what is behind them. 2026-09-17.
   ============================================================================================
   THE OWNER: "طريقة عرض الكارد بعد التسليم جدا سيئة مسافات كتيرة ... قاد القسم شيلو". A standing
   three-field upload form under every card became one control the size of three dots, and his own
   design reference (`design-target/`) uses exactly this mark at the end of every row of the Sales
   list under a column headed `Actions`.

   IT IS `:target` AND REUSES `.cfold`, so there is no second folding mechanism in this file and no
   script: opening an entry targets something else, which closes the menu, which is what a menu
   should do. What `.cmenu` adds is the SHAPE - a small panel at the right-hand end, which is where
   the dots that opened it are.

   `margin-left:auto` AND A `max-width`, not a `position:absolute`. An absolutely positioned menu
   over a card is a menu that can be clipped by a scroll container, can hang off the bottom of a
   390px viewport, and has to be told where the button is. This one is a block in the flow,
   right-aligned, directly under the row it belongs to - it pushes the card down by its own height
   while it is open and by nothing at all when it is not. */
/* 30px OF BOX AND -4px OF SIDE MARGIN, WHICH IS 22px OF ROW. It sits between the state column and
   the chevron on the ONE row that is open, and every pixel it takes comes off the project name:
   measured at 390px, the full 34px box with no negative margin cost `Truck_and_cars_wrap` its last
   two characters to a second line. The TARGET is still 30px tall against the chevron's 34, which
   is inside the 24px floor this file keeps for a thumb on a row. */
.cdot{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:30px;height:30px;margin:-2px -4px;border-radius:10px;color:var(--ink3);
  transition:color .14s ease,background .14s ease}
.cdot:hover{color:var(--brassInk);background:var(--paper2)}
.cdot:focus-visible{outline:2px solid var(--brassInk);outline-offset:2px}
.cmenu{max-width:270px;margin:0 14px 14px auto;border:1px solid var(--rule);border-radius:13px;
  background:var(--card);box-shadow:var(--e-2);overflow:hidden}
.cmenu > a{display:block;padding:12px 15px;font-family:var(--ui);font-size:13px;font-weight:650;
  text-decoration:none;color:var(--ink);border-top:1px solid var(--rule2)}
.cmenu > a:first-child{border-top:0}
.cmenu > a:hover{background:var(--paper2);color:var(--brassInk)}
.cmenu > a.gone{color:var(--ink3)}
.cmenu > a.gone:hover{color:var(--late);background:var(--lateBg)}

/* A FIELD WHOSE VALUE IS THE CONTROL - `Designer` and `Delivery date` on the bench, 2026-09-17.
   THE OWNER'S NINTH ITEM: "يجب ان يكونو بحقول بحيث يستطيع المصمم ان يختار انه سينفذ هذا التاسك".
   So the value in the field list IS the way to set it, which is this codebase's standing rule about
   controls (the thing that shows the state is the thing you tap) applied where it was being broken.
   It reads as the field's own value - same size, same weight - and says it is pressable with the
   dotted rule under it rather than with a link colour, because a field list where two of eleven
   values are blue reads as two broken values. */
.fset{color:inherit;text-decoration:none;border-bottom:1px dashed var(--ink3);
  padding-bottom:1px}
.fset:hover{color:var(--brassInk);border-bottom-color:var(--brassInk)}
.fset:focus-visible{outline:2px solid var(--brassInk);outline-offset:3px;border-radius:3px}

/* THE PRODUCTION LINE - its own row under the delivery row, with a blue rule at its start.
   THE OWNER, VERBATIM: "عند الارسال يظهر كسطر اخر تحت سطر التسليم و يكون بدايته في خط طولي ازرق".
   `--ship` is the blue this file already has in all three theme states and is the same blue
   `.cbar.act` wears, so nothing new was invented for it.
   THE RULE IS AN ELEMENT AND NOT A `border-left`, for `.cbar`'s reason: `align-self:stretch` makes
   it exactly as tall as the line, where a border would be painted inside the row's padding and read
   as a stripe down the card rather than as a mark on one row. */
.row.prod{gap:11px;align-items:stretch}
.prule{flex:0 0 3px;align-self:stretch;min-height:30px;border-radius:999px;background:var(--ship)}
.row.prod .k{align-self:center}

/* WHAT A MESSAGE CARRIES - the chips under one line of the thread, 2026-09-17.
   A FILE POSTED INTO THE CONVERSATION, or the file the question is ABOUT. They are drawn the same
   because they are the same thing to a reader - a thing on this message you can open - and the
   word under the name is what tells them apart ("About" on a pointer, the type and the size on an
   upload). Inside `.msg`, so they take the message's own tint when it is yours. */
.mfs{display:flex;flex-wrap:wrap;gap:8px;margin:9px 0 0}
.mfc{display:inline-flex;align-items:center;gap:9px;max-width:100%;padding:6px 11px 6px 6px;
  border:1px solid var(--rule);border-radius:12px;background:var(--card);text-decoration:none;
  color:inherit;min-width:0}
.mfc:hover{border-color:var(--brassInk)}
.mfc .fth{flex:0 0 32px;width:32px;height:32px;border-radius:8px}
.mfc .fth.none svg{width:15px;height:15px}
.mfc .k{display:flex;flex-direction:column;min-width:0}
.mfc .k b{font-size:12.5px;font-weight:650;color:var(--ink);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.mfc .k em{font-style:normal;font-family:var(--ui);font-size:10px;font-weight:700;
  letter-spacing:.06em;text-transform:uppercase;color:var(--ink3)}

/* THE FILE THE NEXT MESSAGE IS ABOUT - on the form, above the box, before it is sent.
   IT IS NOT A FIELD AND MUST NOT LOOK LIKE ONE: nothing here is typed into. The chip says what is
   attached to the message and the link beside it is the way to drop it, which is a navigation back
   to the same page without `ask=`. */
.askrow{display:flex;align-items:center;flex-wrap:wrap;gap:8px 12px;margin:0 0 4px}
.achip{display:inline-flex;align-items:center;gap:8px;min-width:0;padding:7px 12px;
  border-radius:999px;background:var(--shipBg);color:var(--ship)}
.achip > i{font-style:normal;font-family:var(--ui);font-size:9.5px;font-weight:750;
  letter-spacing:.09em;text-transform:uppercase}
.achip > b{font-size:12.5px;font-weight:650;color:var(--ink);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.adrop{font-family:var(--ui);font-size:12px;font-weight:650;color:var(--ink3);
  text-decoration:none}
.adrop:hover{color:var(--late);text-decoration:underline}

/* ARCHIVE - every file this task holds, in one folded block under the thread's heading.
   "وكلشي بيتحمل بالمحادثة بكون في زر اسمو Archive وقت اكسبو بياخدني على جميع الملفات و الصور
   بالمرفقات للتاسك المعني". `.cfold` is the mechanism; what this adds is the box and the four
   group labels, which are the `.fld` label's own micro type because they are doing the same job. */
.arch{margin:0 14px 14px;padding:4px 0 8px;border:1px solid var(--rule);border-radius:13px;
  background:var(--paper2)}
.ahd{display:block;padding:11px 15px 3px;font-family:var(--ui);font-size:9.5px;
  font-weight:750;letter-spacing:.09em;text-transform:uppercase;color:var(--ink3)}
.arch .rows{margin:0;border:0;background:transparent;box-shadow:none}
.arch .row.frow{background:transparent}

/* THE PRODUCTION FORM, IN THE OTHER COLOUR - "بدي تكون الفورم مختلف شوي ... يكون لون الفورم مختلف".
   It is the same `.card` with `--ship` on its edge, its heading rules and its button, so the form
   and the blue-ruled row it produces are visibly one thing. No new colour: this is the blue the
   rule, the chip and the rail already use. */
.card.prodform{border-color:var(--ship);box-shadow:0 0 0 1px var(--shipBg),var(--shadow)}
.card.prodform label{color:var(--ship)}
.card.prodform button{background:var(--ship);border-color:var(--ship);color:#FFFFFF}
.card.prodform button:hover{filter:brightness(1.06)}

/* THE PRIMARY CONTROL AT THE TOP OF A LIST PAGE - `+ New creative request`.
   IT IS THE APP'S OWN BUTTON: `.card button`'s gradient and 46px height, `.tab[aria-current]`'s
   shadow, drawn on an `<a>` because it opens a page rather than submitting anything. Content width
   and not full bleed - a 390px gradient bar reads as a banner, and the one thing a page wants you
   to press should look like a control. */
/* THE PRIMARY ACTION, SIZED LIKE AN ACTION AND NOT LIKE A BANNER - the owner, 2026-09-17:
   "صغر ال new creative request لتصير حجمها مناسب و تكون بطريقة حلوة". It was 46px tall at 13.5px
   and ran most of the width of a phone, which read as a slab across the page rather than as the
   one thing to press.

   IT IS THE CHIP ROW'S OWN SCALE, DELIBERATELY: 36px against the chips' 34, 12px type against
   their 11. Sitting two pixels taller and half a step heavier than the row above it is what makes
   it read as the action while belonging to the same family - a different size would have been a
   third scale on one screen.

   RIGHT-ALIGNED, which is where the owner's own design reference puts it (`design-target/`, the
   `+ New Request` pill at the top right of the page header). It also settles what the old
   full-width block could not: a control the width of the page has no relationship to the lists
   under it, while one at the end of a line is plainly a thing you do to them.

   The shadow drops with the height - a 28px throw under a 36px pill is a slab's shadow on an
   action's body. */
.make{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;
  padding:0 14px;
  border-radius:11px;text-decoration:none;font-family:var(--ui);font-size:12px;font-weight:720;
  color:var(--priOn);border:1px solid transparent;background:var(--priGrad);
  box-shadow:var(--e-2)}
.make:hover{filter:brightness(1.06)}
.make:focus-visible{outline:2px solid var(--brassInk);outline-offset:2px}

/* SEND, AND THE WAY OUT, ON ONE LINE at the foot of a form. `.card button`'s own `margin-top` is
   what separates the pair from the last field, so this row only lays the two out and gives the link
   the same 46px target the button has - a Cancel a thumb misses is a Cancel that gets pressed
   twice. See `sendRow` in `views/creative.js` for why Cancel is a link and why it is here rather
   than at the top of the page. */
.frow{display:flex;align-items:center;flex-wrap:wrap;gap:4px 18px}
.fcancel{display:inline-flex;align-items:center;min-height:46px;margin-top:18px;
  font-family:var(--ui);font-size:13px;font-weight:650;color:var(--ink3);text-decoration:none}
.fcancel:hover{color:var(--ink);text-decoration:underline}

/* A FORM FIELD THAT IS A FACT AND NOT A BOX - the salesperson a non-admin cannot change. It takes
   the input's own vertical rhythm so the form's baseline does not jump around it, and none of the
   input's chrome, because there is nothing here to type into. */
.card p.fixed{margin:0;min-height:46px;display:flex;align-items:center;font-size:15px;
  color:var(--ink)}

/* ============================================================================================
   ONE RFQ: THE BRIEF AND THE CHAT.
   ============================================================================================
   TWO SHAPES THIS APP HAS NEVER DRAWN - a wall of photographs and a thread of messages - and
   nothing else. Every other part of that page is borrowed whole: `shead`, `.rows`/`.row`/`.k`/`.v`,
   `.stat`, `.card`, `.hint`, `.empty`, `.costnote`, and `.shot` with its blueprint placeholder and
   its `object-fit:contain`, which is the card's picture box used unchanged inside a grid cell.
   NOT ONE NEW COLOUR: every value below is a token that already exists in all three theme blocks,
   so light, dark and the toggle are correct without a fourth definition to keep in step. */

/* THE PHOTO WALL. `auto-fill` rather than `auto-fit`, and the difference is visible: with one
   photograph, `auto-fit` collapses the empty tracks and stretches it to the full width of the page,
   which on a phone held sideways is a single enormous picture. `auto-fill` keeps the track width,
   so one photo is the size of one photo. 150px is two columns on the narrowest phone this shop
   uses and four on an iPad. */
.bgrid{display:grid;gap:10px;margin:0 0 14px;
  grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}
/* The cell is what `.shot` normally gets from `.jshow` on a card - a border, a radius and something
   to clip to. `.shot` is `border-radius:0` by design (it sits flush at the top of a card), so the
   rounding has to happen here, on the box that owns it. */
/* `margin:0` IS A BUG FIX AND NOT A TIDY-UP, found on 2026-09-16 while measuring the creative
   page. The cell is a `<figure>`, and a `<figure>` carries a USER-AGENT margin of `1em 40px` that
   nothing in this stylesheet had ever reset - there is no global reset here beyond `box-sizing` and
   `html,body`. So every cell of both photo walls (this one and `grid()` on the RFQ brief) sat 40px
   in from each side of its own grid track and lost 80px of it: measured at 834px, a 169px track
   held an 89px picture. The comment above claims "two columns on the narrowest phone" and it was
   two columns of half-width pictures. One declaration, both pages. */
.bcell{position:relative;margin:0;overflow:hidden;background:var(--card);
  border:1px solid var(--rule);border-radius:12px}
.bcell .shot{border-bottom:1px solid var(--rule)}
.bcap{display:block;padding:8px 10px;font-size:11.5px;color:var(--ink2);overflow-wrap:anywhere}
.bcap em{display:block;margin-top:2px;font-style:normal;font-size:10.5px;color:var(--ink3)}
/* The mark on a document or a link row. Sized like the chevron on a card row so a list of mixed
   entries has one optical left edge. */
.bmark{flex:0 0 auto;display:flex;align-items:center;color:var(--ink3)}
.blink{display:block;margin-top:2px;font-style:normal;font-size:10.5px;color:var(--ink3);
  overflow-wrap:anywhere}

/* REMOVE. A real form with one button, and it has to be small without becoming a tap-target you
   hit by accident: 11px text inside a 30px-high pill, which is above the 28px the platform
   guidelines floor a control at and well under the 46px a primary button takes. In a grid cell it
   floats over the caption's top-right corner; in a `.row` it is just the last flex item. */
.bx{margin:0}
.bcell .bx{position:absolute;top:8px;right:8px}
.bx button{margin:0;min-height:30px;padding:0 11px;border-radius:9px;
  font-family:var(--ui);font-size:11px;font-weight:700;letter-spacing:.02em;
  color:var(--ink2);background:var(--paper2);border:1px solid var(--rule);
  background-image:none;box-shadow:none;cursor:pointer}
.bx button:hover{color:var(--late);border-color:var(--late)}
.bcell .bx button{background:var(--card)}

/* THE THREAD. A column of cards, oldest at the top, each one as wide as it needs to be - not
   bubbles alternating sides. A thread that reads differently depending on who is looking at it is
   a thread you cannot quote back in a sentence about what was agreed. */
/* ============================================================================================
   AND IT WAS TAKEN IN ON 2026-09-21 - item 5: "قسم الـ`thread` اخد مساحات كبيرة — إذا فينا نقلص
   المساحات و يكون مرتب اكتر" - the thread took big spaces; shrink them and make it tidier.
   ============================================================================================
   THE NUMBER THAT SAYS SO. Measured in Chrome at 390px on a card with five messages: the thread
   block was 817px of a 2,389px page - a third of the card - and five messages accounted for 441px
   of it at 88px each, for two lines of text apiece.

   WHAT CHANGED IS SPACING AND NOTHING ELSE. Every message still carries its author, its role, its
   time and its own words at a readable size; nothing is hidden behind a fold, nothing is
   truncated, and no message is collapsed. The gap between cards, the padding inside them and the
   distance between the byline and the text were simply larger than a conversation needs when it
   is the third block on a card rather than a page of its own.

   13.5px/1.5 AND NOT 14/1.55. One step, and it is the step between "a document" and "a chat" -
   the same size `.bcap` and the card's own `em` lines already use for text that sits inside
   something else. A message is still a paragraph and is not a caption.

   IT REACHES THE RFQ THREAD TOO, AND THAT IS CORRECT RATHER THAN COLLATERAL. `threadBlock` is one
   renderer for both conversations in this app - see `views/rfq.js` - and two threads that were
   one design until today should not become two because one of them was measured. */
.thread{display:flex;flex-direction:column;gap:7px;margin:0 0 12px}
.msg{border:1px solid var(--rule);border-radius:13px;background:var(--card);padding:8px 12px 9px;
  box-shadow:var(--shadow)}
/* YOUR OWN, MARKED AND NOT MOVED: a brass rule down the left edge, which is the same "this one is
   yours" signal the app uses elsewhere and costs no layout. */
.msg.me{border-left:3px solid var(--brassInk)}
.msg h4{display:flex;align-items:baseline;gap:6px;margin:0 0 3px;
  font-family:var(--ui);font-size:10.5px;font-weight:600;color:var(--ink3)}
.msg h4 b{font-weight:750;color:var(--ink2)}
.msg h4 em{font-style:normal}
.msg h4 time{margin-left:auto;flex:0 0 auto;font-variant-numeric:tabular-nums}
/* `pre-wrap` SO THE LINE BREAKS SOMEBODY TYPED SURVIVE. People write lists. This is CSS acting on
   text that `esc()` has already made inert - it is not markup being honoured. */
.msg p{margin:0;font-size:13.5px;line-height:1.5;color:var(--ink);
  white-space:pre-wrap;overflow-wrap:anywhere}

/* ============================================================================================
   A FORM THAT LIVES INSIDE A CARD - the thread's composer and the first delivery, 2026-09-21.
   ============================================================================================
   `.card` IS A PAGE'S FORM AND THESE TWO ARE NOT ON A PAGE. Its measurements - 18px of padding
   with 22px under it, a 14px gap above every label, a 92px textarea and a 46px button pushed 18px
   clear - are right for a screen whose whole purpose is that form (sign in, new order, new
   client). Measured at 390px, the message box built out of them was 237px: the single largest
   element in the 817px thread, for one line somebody types.

   NOTHING IS REMOVED - NOT THE LABEL, NOT THE FILE INPUT, NOT THE CEILING LINE. Every control the
   form had it still has, and the label above each one is what makes it usable without a
   placeholder standing in for a name. What went is air: 14px gaps to 8, a 92px box to 58 (it is
   `resize:vertical`, so a person writing five paragraphs still drags it open), and the button's
   18px shove to 10.

   THE TAP TARGETS DO NOT SHRINK WITH IT. The button keeps 44px and the inputs keep 44px, which is
   the platform floor this file holds everything else to - `.bx button`'s own note. A compact form
   and a fiddly one are not the same thing. */
.card.cbox{max-width:none;padding:13px 14px 15px}
.card.cbox label{margin:8px 0 4px;font-size:9.5px}
.card.cbox > label:first-of-type{margin-top:0}
.card.cbox input,.card.cbox textarea{font-size:14px;padding:9px 11px;min-height:44px}
.card.cbox textarea{min-height:58px;line-height:1.45}
.card.cbox button{margin-top:10px;min-height:44px;padding:10px 16px}
.card.cbox .hint{margin-top:4px}

/* THE ARCHIVE LINE - a link under the thread's heading, not a card containing a link. See
   `archiveRow`. `min-height` is the finger, `.acts > a` does the rest. */
.arow{display:flex;align-items:center;min-height:30px;margin:0 0 8px;padding:0 2px}

/* the dashboard's own tiles */
/* Asked of the space, not of the window - the same fix and the same reason as `.jobs` above.
   A dashboard tile carries a row of figures; two of them in 560px of iPad column is a figure per
   135px, which is where `.figs`' own `minmax(150px,1fr)` starts stacking. */
.dash{display:grid;gap:14px;grid-template-columns:repeat(auto-fill,minmax(min(330px,100%),1fr))}
.tile{border-radius:18px;box-shadow:var(--shadow);background:var(--card);
  border:1px solid var(--rule);padding:15px 16px}
.tile h3{margin:0 0 11px;font-family:var(--ui);font-size:11px;font-weight:750;
  letter-spacing:.09em;text-transform:uppercase;color:var(--ink3)}
/* 150px AND NOT 140px, WHICH IS THE ONE CHARACTER THAT DECIDES WHETHER MONEY WRAPS ON A DESKTOP.
   `IQD 114,665,000` is 186px at this weight. A 463px tile takes THREE 147px tracks at a 140px
   minimum and TWO 226px tracks at 150px, and only the second of those holds the figure on one
   line - so the number that used to be painted over its neighbour is now simply printed, and the
   Today dashboard at 1280 is the same height it was before any of this. A phone is untouched:
   328px of tile is two 158.5px tracks either way, four small figures stay two-up, and a long
   figure there still wraps, which is the honest answer on a 390px screen. */
.figs{display:grid;gap:11px;grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}
/* ============================================================================================
   A FIGURE IS A COLUMN THAT ENDS ON THE SAME LINE AS ITS NEIGHBOURS, AND NEVER LEAVES ITS TRACK.
   ============================================================================================
   TWO WAYS A `.figs` ROW USED TO PRINT A NUMBER A PERSON READS WRONG, both found by the page walk
   of 2026-09-17 and neither by any test:

   THE FLOAT. `.fig` was a plain block, so `.v` started wherever `.k` finished - and `.k` wraps to
   one line or two depending on its own words and the track's width. Two figures side by side with
   labels of different heights therefore put their VALUES on different lines, and where the left
   value was wide and the right one narrow (`Quoted and unanswered, USD` beside `Filed in all`) the
   short value landed immediately right of the long one, one line-height up. It read as
   `USD 7,140.90²`. `display:flex` + `margin-top:auto` on the value drops every value in a grid row
   onto one bottom line; labels stay top-aligned, which is how a row of labels is scanned.

   THE OVERPRINT, WHICH WAS WORSE AND WAS ON THE MORNING PAGE. `.mny` held `white-space:nowrap`,
   `.fig .v` overflows visibly, and a 1fr track at 1280 is 147px - so `IQD 114,665,000` (186px)
   was painted 39px into the next track, on top of `IQD 76,019,500`, which was painted on top of
   `IQD 38,645,500`. Three money figures overprinted on Today. A figure is allowed to WRAP between
   its currency and its amount when the track is too narrow for both; it is not allowed to be drawn
   over another figure, and it must not be clipped either, because a truncated number still reads
   as a number. The amount itself has no space in it, so it never breaks.
   ============================================================================================ */
.fig{min-width:0;display:flex;flex-direction:column}
.fig .k{display:block;font-family:var(--ui);font-size:10px;font-weight:750;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink3);margin-bottom:5px}
.fig .v{display:block;font-family:var(--ui);font-size:23px;font-weight:800;letter-spacing:-.03em;
  color:var(--ink);overflow-wrap:normal;margin-top:auto}
.fig .v .mny{white-space:normal}
.fig.pos .v{color:var(--ok)} .fig.neg .v{color:var(--late)}

/* ---------- the search box ---------- */
.tools{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px}
.tools input{flex:1 1 220px;min-width:0;font:inherit;font-size:15px;padding:0 13px;height:42px;
  border-radius:14px;border:1px solid var(--rule);background:var(--card);color:var(--ink)}
.chip{display:inline-flex;align-items:center;text-decoration:none;border-radius:13px;height:42px;
  font-family:var(--ui);font-size:12px;font-weight:680;padding:0 14px;
  border:1px solid var(--rule);background:var(--card);color:var(--ink2);cursor:pointer}
.chip[aria-pressed="true"]{background:var(--ink);border-color:var(--ink);color:var(--paper)}

/* ============================================================================================
   NAVIGATION - eight destinations at the bottom on a phone, a rail from 768 up.
   The pipeline stages ride inside Orders as chips.

   ============================================================================================
   THE BAR SCROLLS SIDEWAYS SINCE 2026-09-19, AND IT IS THE OWNER'S OWN CHOICE.

   EIGHT WILL NOT FIT 390px. Four options were put to him - fold the overflow behind a `More`,
   shrink the type, drop to icons with no labels, or let the row scroll - and he chose the last
   one in as many words: "بدي الشريط ينسحب يمين و شمال". What follows is that bar with its two
   real faults closed, which was the condition the recommendation came with.

   TWO ELEMENTS, AND THE SPLIT IS THE WHOLE DESIGN. `.nav` is the PILL: fixed to the bottom of
   the viewport, bordered, rounded, blurred, and it does not move. `.navrow` is the ROW: a grid
   of one column per link that slides inside it. They were one element until today. A scroller
   cannot also draw the fade over its own trailing edge - a pseudo-element or a background on a
   scrolling box scrolls WITH the content and would ride off the right-hand edge along with the
   last link, which is the opposite of what a trailing fade is for. So the chrome stays on the
   box that is still, and only the links move.

   ============================================================================================
   THE ARITHMETIC, MEASURED RATHER THAN ESTIMATED - headless Chrome, deviceScaleFactor 1, the
   real stylesheet, `.nb`'s own 10.5px/500 in this app's own `--ui` stack.

   INK, IN THE BAR'S ORDER: `Dashboard` 55.86, `Admin` 32.39, `Sales` 27.75, `Creative` 42.86,
   `Technical` 48.70, `Production` 56.31, `Inventory` 48.30, `Finance` 40.05. Every one of the
   eight is A SINGLE WORD, which is why none of them can wrap: the widest is `Production` at
   56.31 and the column floor below is 78. `Creative Design` was 81.38 and wrapped at every share
   this bar has ever offered - 12.07px of extra height on every page in the app - and the two
   long spellings of `Technical` the owner turned down measure 83.13 and 112.86, which is what
   the note on that entry in `views/layout.js` means by "both wrap".

   THE PILL'S CONTENT BOX: `left:12px;right:12px` off the viewport, a 1px border a side, 4px of
   padding a side. So 390 -> 356, 320 -> 286, 767 -> 733.

   `--nav-col` IS 90 AND THE FADE IS 16, AND THE TWO NUMBERS ARE CHOSEN TOGETHER. The floor has
   to clear the widest label plus `.nb`'s 2px of padding a side (56.31 + 4 = 60.31), and it has
   to leave the LAST label enough room that the fade never touches its ink. A label is centred in
   its column, so its slack a side is (column - its ink) / 2, and the clearance is that minus 16.

   IT WAS 84 UNTIL 2026-09-20 AND THE SENTENCE UNDER IT WAS "the last entry is always `Finance`,
   the narrowest of the eight - and it is last for EVERY role, since nothing after it on the list
   is gated". THAT SENTENCE IS NOW FALSE and the number moved because of it. `Finance` got a page
   that day - the accountant's Waiting List - and with a page it got `billing.read`, so the bar
   has TWO endings: `Finance` (40.05 of ink) for the admin and sales, and `Inventory` (48.30) for
   production, design and pricing, which is the WIDER word.

   SO THE FLOOR IS MEASURED AGAINST THE WIDEST LABEL THAT CAN BE LAST, NOT AGAINST THE NARROWEST
   ENTRY ON THE LIST, which is the assumption that just cost a number:
     Finance    (90 - 40.05) / 2 - 16  =  8.98  clearance
     Inventory  (90 - 48.30) / 2 - 16  =  4.85  clearance
   At 84 the second of those was 1.85 - arithmetically clear of the fade and NOT clear of a
   different font: this app ships no webfont, so the same word is wider in Roboto on an Android
   than in SF on the owner's iPhone, and 1.85px is not room for that. Measured in a real browser
   at 1.86 before the change, which is the arithmetic above confirming itself to two decimals.
   `test/nav-bar.test.js` now signs in as BOTH ends and holds the margin at 4px.

   WHAT THE EXTRA 6px COSTS: the admin's eight columns are 720 wide instead of 672, so the band in
   which a full bar stops overflowing moves up - it fits from about 754px of viewport instead of
   706. That band is inside the phone range and the bar scrolls there, which is the owner's own
   ruling for it ("بدي الشريط ينسحب يمين و شمال"), so what changes is which widths see a fade over
   empty column rather than whether anything is reachable.

   WHO SCROLLS AND WHERE:
     390   8 links 720 wide in 356    scrolls (admin)
           7 links 630 in 356         scrolls (sales)
           6 links 540 in 356         scrolls (pricing)
           5 links 450 in 356         scrolls (design)
           4 links 360 in 356         scrolls (production)
     320   every role, every count    scrolls - 4 links is already 360 in 286
     767   8 links 720 in 733         FITS; columns stretch to 91.6 and the fade falls on the
                                      26px of empty column beside `Finance`, not on the word
   768+  there is no bar at all: `.nav{display:none}` and the rail takes over.

   SO ON A PHONE - which is the only place this bar is ever drawn in portrait - EVERY ROLE'S BAR
   REALLY DOES HAVE MORE IN IT THAN FITS, and the fade is never claiming something that is not
   there. Between about 480 and 767 a short bar stops overflowing and the fade lands on empty
   column. That is the one width band where it is decoration, and it is decoration over blank
   space rather than over a word.

   `scroll-padding-inline` IS WHAT MAKES THE SCROLLED-TO LINK LAND CLEAR OF THE FADE. `finder.js`
   brings the current entry into view with `scrollIntoView({inline:'nearest'})`, and `nearest`
   stops the moment the element is whole - which, with no scroll padding, is flush against the
   right edge and therefore half under the gradient. 20px at the end is the 16px fade plus 4px, so
   a lit entry in the MIDDLE of the row always finishes in clear air. The last entry is the one
   case it cannot help with - there is nothing left to scroll - and it does not need to: the 16px
   of gradient lands on the 21.98px of empty column beside `Finance`, which is the arithmetic
   above and is why that arithmetic is the guarantee rather than this line.
   ============================================================================================ */
.nav{position:fixed;left:12px;right:12px;bottom:10px;z-index:40;
  background:var(--navBg);
  border:1px solid var(--rule);border-radius:21px;
  /* 5 AND NOT 7 VERTICALLY, WITH THE OTHER 2 MOVED ONTO `.navrow` BELOW. The bar is the same
     height it was; what the 2px buys is room INSIDE the scroll box for a focus ring, because a
     scroller clips at its padding edge and `:focus-visible` draws just outside the link's border
     box. Keyboard focus on the bottom bar is rare and a ring sliced in half is still wrong. */
  padding:5px 4px calc(5px + env(safe-area-inset-bottom));
  box-shadow:var(--e-3)}
@supports (backdrop-filter:blur(8px)) or (-webkit-backdrop-filter:blur(8px)){
  .nav{-webkit-backdrop-filter:saturate(180%) blur(14px);
    backdrop-filter:saturate(180%) blur(14px)}
}
/* ONE COLUMN PER LINK, WHATEVER THE COUNT - unchanged in mechanism since the bar shipped. The
   original hard-codes repeat(5,1fr) and then renders six destinations into it, so two of them
   share one column's width; auto-flow takes the count from the links that are actually there,
   and the count is not a constant at all (five roles, four different bars).

   ============================================================================================
   `minmax(min-content, 1fr)` AND NOT THE PLAIN `1fr` THIS ROW HAD, AND THE DIFFERENCE IS A BUG
   THAT WAS IN IT FOR ONE MEASUREMENT.

   `1fr` IS `minmax(auto, 1fr)`, and for a grid item `auto` means "the content-based minimum" -
   BUT ONLY WHILE THE ITEM'S OWN `min-width` IS ALSO `auto`. `.nb` carries an explicit
   `min-width:var(--nav-col)` below, which is what makes eight columns wider than a phone and
   therefore what makes the row scroll at all; and an explicit `min-width` REPLACES the
   content-based minimum rather than raising it. So with `1fr` a label wider than 84px did not
   widen its column - it overflowed the column, visibly, on top of the link beside it. Measured:
   `Technical Department` (112.86 of ink) in an 84px track, with the row's scrollWidth unchanged
   at 672. The bar would have looked broken and nothing would have said so.

   `min-content` AS THE FLOOR ASKS THE QUESTION THE OTHER WAY. A track's minimum is now the
   item's min-content CONTRIBUTION, which is its min-content size clamped into its own
   [min-width, max-width] range - so it is 84 for every label narrower than that (the floor still
   holds, and the row still overflows a phone) and the label's full ink for one that is wider
   (it widens its column, and the bar scrolls further). Both behaviours, one declaration, and the
   failure mode for the next long label is more scrolling instead of overlapping text.

   `1fr` IS STILL THE MAXIMUM, so where the links FIT they stretch to fill the pill exactly as
   they always did. Only the minimum changed. */
.navrow{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(min-content,1fr);
  overflow-x:auto;overscroll-behavior-x:contain;
  scroll-padding-inline:4px 20px;
  padding-block:2px;
  /* NO SCROLLBAR. A 15px scrollbar under a 48px target would be a quarter of the bar's height
     spent on a control nobody can hit with a thumb, and the fade is what says the row moves. */
  scrollbar-width:none;
  /* THE TRAILING FADE, AS A MASK AND NOT AS A PAINTED GRADIENT. A gradient in `background`
     paints BEHIND the links and would fade nothing; a mask applies to this box's painted
     result, links and all, and reveals the pill's own surface underneath - so it needs no
     colour of its own and is correct in light, dark and auto without a second declaration.
     `--navBg` is translucent (.82 / .80) and a gradient TO it would have left the last link
     18% visible and the right-hand end of the bar a shade denser than the rest of it. */
  -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - var(--nav-fade)),transparent);
  mask-image:linear-gradient(90deg,#000 calc(100% - var(--nav-fade)),transparent)}
.navrow::-webkit-scrollbar{display:none}
.nb{display:flex;flex-direction:column;align-items:center;gap:4px;padding:6px 2px;min-height:48px;
  /* THE COLUMN FLOOR - see the arithmetic above. It is what turns eight links into a row that is
     wider than the phone, and therefore what makes the bar scroll instead of squeezing eight
     labels into 44px each. */
  min-width:var(--nav-col);
  border-radius:15px;text-decoration:none;color:var(--ink3);font-family:var(--ui);
  font-size:10.5px;font-weight:500;position:relative;text-align:center}
.nb[aria-current="page"]{background:var(--priBg);color:var(--priInk)}
/* AND THE BAR'S LABELS DO NOT WRAP, which is a promise rather than an observation. All eight are
   one word and all eight clear the floor above, so nothing wraps today; this line is what keeps
   a two-word label added later from silently costing every page a line of bar height, as
   `Creative Design` did. It takes a wider column and more scrolling instead - which is what
   `minmax(min-content, 1fr)` above is for, and the two lines only work as a pair: nowrap without
   the min-content floor is a label that overflows onto its neighbour, and the floor without
   nowrap is a label that wraps. `test/nav-bar.test.js` draws `Technical Department` into a real
   bar and measures both halves, because with today's eight one-word labels neither line does
   anything and a mutation run proved it.
   THE RAIL IS UNAFFECTED - `.rb` wraps as it always has, because a rail row is as wide as the
   rail and has nowhere to scroll to. */
.nav .rlabel{white-space:nowrap}
/* Every other destination is one word; `New order` is two, so it wraps on a phone. Tightened
   so the wrap costs a couple of pixels of bar height rather than a whole line of leading. */
.nb .rlabel,.rb .rlabel{line-height:1.15}
.nb .pip,.rb .pip{position:absolute;top:2px;right:calc(50% - 20px);min-width:16px;height:16px;
  border-radius:8px;background:var(--late);color:#fff;font-size:9.5px;font-weight:700;
  display:flex;align-items:center;justify-content:center;padding:0 4px;line-height:1}
.railnav{display:none}

/* ============================================================
   A REFUSED SAVE, SAID WHERE HE WILL SEE IT.

   `press.js` paints a chip the instant it is tapped and sends the write behind it. When the server
   refuses — a stale token, a role that may not, a value the writer will not take — the chip goes
   back to what it was and this bar carries the server's own sentence. It is the only element on
   these pages the server never renders; the script builds it and hangs it off <body>.

   OFF <body>, AND THAT IS LOAD-BEARING RATHER THAN TIDY. `.job` is a CSS container
   (`container-type:inline-size`), and a container is a containing block for `position:fixed` — a
   bar appended inside a card would be pinned to the CARD and could be scrolled off the screen with
   it. At the end of <body> there is nothing between it and the viewport.

   It sits above the bottom nav on a phone, on the same 12px gutter and the same rhythm, and it
   stays until it is dismissed or the page is loaded again: a message about a save that did not
   happen is not something to take away after three seconds.
   ============================================================ */
.flash{position:fixed;left:12px;right:12px;bottom:calc(78px + env(safe-area-inset-bottom));
  z-index:60;display:flex;align-items:center;gap:11px;padding:11px 13px;border-radius:16px;
  background:var(--lateBg);border:1px solid var(--late);color:var(--late);
  font-family:var(--ui);font-size:12.5px;font-weight:600;line-height:1.35;
  box-shadow:var(--e-3)}
.flasht{flex:1 1 auto;min-width:0}
.flash > button{flex:0 0 auto;font:inherit;font-size:10px;font-weight:750;letter-spacing:.09em;
  text-transform:uppercase;color:inherit;background:none;border:1px solid currentColor;
  border-radius:10px;padding:6px 9px;min-height:32px;cursor:pointer}
@media (min-width:768px){
  /* From 768px up the bottom bar becomes the left rail, so there is nothing to clear underneath. */
  .flash{left:auto;right:22px;bottom:22px;max-width:420px}
}

/* the stage tabs, as a chip row inside a nav destination */
.tabs{margin-bottom:10px}
.tabs.solo{display:none}
/* LONGHANDS, AND THAT IS THE WHOLE POINT OF THIS LINE. `.tabsbar` rides on `.wrap` for its gutter
   - see `tabChips` in views/layout.js, which emits `class="wrap tabsbar"` - and the shorthand that
   was here (`padding:2px 0 8px`) reset the horizontal padding `.wrap` had just set. Same
   specificity, later in the file, so it won: the stage chip row began at x=0 while the breadcrumb,
   the page header and every card on the same page began at 14. On a phone the first chip's left
   corner was sliced off by the viewport edge; at 1280 the row sat 14px left of everything under
   it. The two chip rows in this app are supposed to be the same control, and `pageTabs`' row -
   which is inside `<main>` and never touched `.wrap` - was the one drawing it correctly. */
.tabsbar{padding-top:2px;padding-bottom:8px}
/* ============================================================================================
   THE CHIP ROW WRAPS. IT DOES NOT SCROLL, AND THAT IS THE WHOLE OF THIS RULE.

   IT WAS `overflow-x:auto` WITH THE SCROLLBAR HIDDEN UNTIL 2026-09-17, and measured at 390px that
   row hid 477px of its own content on the orders page (eight chips) and 142px on the five sales
   pages (five chips) - with no scrollbar, no fade and no edge, so nothing on the screen said the
   rest existed. Worse, the chip carrying `aria-current` was itself cut in half on `?tab=ready`,
   `?tab=confirmed`, `/quotations` and `/won`: the reader could not see WHERE THEY WERE.

   A SCROLLER CANNOT FIX THE SECOND HALF OF THAT WITHOUT SCRIPT. A scroll container starts at
   `scrollLeft: 0` and only script moves it, so "scroll the current chip into view" is a line of
   JavaScript - and this app links exactly three scripts, each enhancing something that already
   works without it, under `script-src 'self'` with no inline. A chip row that is WRONG until a
   script runs is not that bargain. There is no CSS that scrolls a box to one of its children;
   `order:-1` on the current chip would do it by reordering, and the order of these five is the
   owner's own ruling (see `SALES_PAGES` in views/layout.js) and not ours to shuffle per page.

   SO IT WRAPS, at every width. Nothing is ever hidden, so there is nothing to advertise with a
   fade, and the rule "the current chip is never clipped" holds by construction rather than by
   arithmetic that could stop being true when a page grows a seventh chip - the orders row already
   has eight. It costs height on a phone and nothing at all from 700px up, where every row fits on
   one line: measured after this change, three lines at 390 for the orders row, two for the sales
   row, one line everywhere at 1280.
   ============================================================================================ */
/* ONE LINE, ALWAYS - the owner's ruling of 2026-09-17, looking at the five Sales chips broken
   over two rows: "بدي ياهن على صف واحد و بخط اصغر / دائما يظهرو على سطر واحد".

   IT WRAPPED FOR A REASON AND THE REASON IS ANSWERED, NOT IGNORED. Wrapping replaced a clipping
   scroller a round earlier, because a scroller starts at scrollLeft 0 and the chip you are ON
   could sit off the right edge - a reader who cannot see where they are. No CSS scrolls a box to
   one of its children, so the fix is four lines in `finder.js`, the script this app already puts
   on every signed-in page, and it is progressive: with no script the row still scrolls by hand
   and nothing is hidden, which is what wrapping was protecting against.

   SMALLER, AS ASKED: 12px to 11, the pill 41px high to 34, its padding 14 to 11. The five Sales
   chips come to ~430px against 362 of phone, so they do not all fit at any size worth reading -
   the row scrolls, and the one that matters is scrolled to. `scroll-padding-inline` leaves the
   page's own 14px gutter beside the chip when it is scrolled into place, so it never lands flush
   against the edge. The scrollbar is hidden on every engine: this is chrome, not a pane. */
.tabsrow{display:flex;flex-wrap:nowrap;gap:6px;min-width:0;padding:2px 0 4px;
  overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none;
  -webkit-overflow-scrolling:touch;scroll-padding-inline:14px;overscroll-behavior-x:contain}
.tabsrow::-webkit-scrollbar{display:none}
.tab{display:inline-flex;align-items:center;gap:5px;flex:0 0 auto;text-decoration:none;
  border:1px solid var(--rule);background:var(--card);border-radius:11px;font-family:var(--ui);
  font-size:11px;font-weight:680;color:var(--ink2);padding:0 11px;min-height:34px;
  white-space:nowrap;box-shadow:var(--e-1)}
.tab:hover{border-color:var(--brassInk);color:var(--ink)}
.tab[aria-current="page"]{background:var(--priGrad);
  border-color:transparent;color:var(--priOn);box-shadow:var(--e-2)}
.tab .c{color:var(--ink3);font-variant-numeric:tabular-nums}
.tab[aria-current="page"] .c{color:#fff;opacity:.75}

/* ============================================================================================
   THE WORKFLOW TABS - the three lists on Creative Request, one drawn at a time. 2026-09-18.

   WHY THEY ARE NOT `.tab`, WHICH WAS THE FIRST THING TRIED. This page already carries a `.tabsrow`
   of five pill chips - the SALES pages - directly under its header. A second pill row under that
   one would be two identical-looking controls stacked two lines apart doing two unrelated things:
   the first changes which PAGE you are on, the second which LIST of this page you are reading. A
   reader who learns that a pill navigates has learned the wrong thing about half of them.

   SO THE SECOND CONTROL IS DRAWN AS THE SECOND CONTROL IN THE REFERENCE: underline tabs. A rule
   along the bottom of the strip, the active one marked by a heavier segment of that rule under its
   own label. It is the oldest tab idiom there is and it cannot be mistaken for a chip.

   THE INDICATOR IS `--priInk` AND NOT GOLD, IN BOTH THEMES, AND THAT IS A DECISION RATHER THAN A
   CONSTRAINT. The owner's ruling is that gold lives on the navy rail and nothing on a light surface
   is painted with it: `#D4A15A` is 2.32:1 on white and has no legal use there, which settles the
   light theme on its own. The DARK theme is the interesting half - the workspace card is `#141C2E`,
   a navy, and the dark gold `#E7BE7E` measures 9.77:1 against it, so gold would have been legal
   here. It is still not used, because the alternative is one control with two identities: navy in
   light, gold in dark, for no reason a reader could name. `--priInk` already flips per theme
   (`#14417E` on white, `#A8C4EC` on the dark card) and passes in both, so the tab is the brand
   colour in the sense the rest of this app means it. Gold stays the rail's.

   ==========================================================================================
   IT WRAPS, AND THE FIRST BUILD OF IT SCROLLED. That version was written believing three tabs
   would fit a phone; the walk measured them at 158 + 133 + 85 px plus two 16px gaps = 408 against
   the 362 a 390px phone has, and on `?tab=delivered` the tab carrying `aria-current` was itself
   cut off the right edge. That is the exact fault `.tabsrow` above was corrected for in September
   - THE READER CANNOT SEE WHERE THEY ARE - and it is worse here, because this strip is the only
   thing on the page that says which of the three lists is drawn. It was not visible in a
   screenshot: the shot is taken at `captureBeyondViewport` and the clipped tab is simply absent.

   TIGHTENING IT TO FIT WAS COSTED AND REFUSED. Gap 16->12, inner gap 8->6, 12px type to 11, a
   smaller badge: 39 of the 46 pixels needed, on a font stack that is SF on his phone and something
   else on this machine. A layout that fits by four pixels on one device does not fit.

   THE SCRIPT ANSWER WAS ALSO REFUSED. `finder.js` already scrolls `.tabsrow`'s current chip into
   view and is linked on every signed-in page, so pointing it at a second selector would have
   worked. This slice's constraint is no JavaScript beyond what exists, and a row that is WRONG
   until a script runs is the bargain that rule exists to refuse.

   SO THE BASELINE RULE MOVED ONTO THE TABS THEMSELVES, which is what makes wrapping legible. A
   rule on the STRIP with wrapped children leaves the first line's tabs floating 38px above it and
   the active indicator pointing at nothing. Each tab carrying its own 2px bottom border instead -
   `--rule` when it is not current, `--priInk` when it is - draws one continuous baseline on one
   line and one per line when it wraps, with the indicator always directly under its own label.
   The gaps are PADDING and not `gap` for exactly this: a flex gap would break the baseline into
   three dashes. Measured after: one line from 834 up, two lines at 390, nothing ever hidden.

   ==========================================================================================
   AND THEN THERE WERE FOUR, AND THEY FIT - 2026-09-18, the same day, later.

   THE OWNER REPLACED THE THREE TABS WITH FOUR (To assign / Active / Paused / Delivered) and his
   standing requirement about this strip is the one sentence he has repeated: "اهم شي يكونو دائما
   بسطر واحد و بشكل انيق" - above all, always on one line, and elegant. The three that were here
   never were: 174.4 + 148.9 + 106.4 = 429.7px against 362, wrapping since the hour they shipped.

   SO THE FOUR ARE SHORT BECAUSE THE COLUMN IS 362px AND THE CHROME IS HALF OF IT. Per tab: 18px
   of padding, an 8px gap, and a badge that is 17px at one digit and 23.8px at two - about 190px
   before a letter of a label is drawn. What is left for four words is roughly 180px, which is why
   "Waiting to assigned" (160.5px on its own) could not be one of them.

   MEASURED IN HEADLESS CHROME ON `-apple-system`, WHICH ON A MAC IS SF - the same face the phone
   draws, so this is no longer the "SF on his phone and something else on this machine" the note
   above had to discount. At 390px, in the 362px column:

       To assign 98.0 + Active 87.2 + Paused 93.6 + Delivered 106.4  = 385.2 at two digits each
       the same four at the counts this page shows today             = 349.0   ONE LINE
       and with the Delivered count past 99                          = 362.7   two lines

   THE LAST-CHILD RULE IS WHAT BOUGHT THE LAST NINE PIXELS, and it is a correction rather than a
   shave: `:first-child` has had `padding-left:0` since this strip was written, so that the first
   tab's label and its 2px baseline start flush with the content column. The last tab's padding
   was ending the baseline 9px short of the same column on the right. Mirroring it makes the strip
   symmetrical with the list under it AND is the only tightening taken - no gap, no type size and
   no badge was touched, because the note above is right that a layout which fits by four pixels
   does not fit.

   WHAT IS STILL TRUE IS THAT THIS IS THE CEILING. A fifth tab does not fit at any wording, and
   the four wrap the day the Delivered count reaches three digits. That is a real date rather than
   a hypothetical - the delivered list is "has ever been delivered" and only grows - and the fix
   when it arrives is a shorter first label (`Waiting` buys 11px, `Assign` 16px), measured the
   same way, and not another two pixels off the padding.
   ============================================================================================ */
/* ONE LINE, AND IT SCROLLS IF IT HAS TO - the owner, 2026-09-18, looking at four tabs broken
   over two rows on his own phone: "بدي هدول يكونو بسطر واحد حتى لو بدي مررن تمرير إذا ماكفو".

   THE MEASUREMENT SAID THEY FIT AND HIS PHONE SAID THEY DID NOT. 355.8px of a 362px column, in
   headless Chrome on this Mac - and then a wrap on the device, because Safari's own text size,
   the zoom he happens to be at and a hairline of scrollbar gutter are not the harness's. Six
   pixels of headroom was never a fit; it was a coin toss that the harness kept winning.

   SO THE ROW STOPS DEPENDING ON THE ARITHMETIC. It scrolls, exactly as `.tabsrow` above it does
   and for the same reason, and `finder.js` scrolls the lit tab into view so the one that matters
   is never the one off the edge. That is what makes a non-wrapping row safe here; without it a
   reader on `Delivered` opens the page looking at `To assign`.

   THE FLUSH EDGES SURVIVE: `:first-child` and `:last-child` still strip their outer padding, so
   the underline starts and ends with the column. `scroll-padding-inline` keeps the page's gutter
   beside a tab that has just been scrolled to. */
.wtabs{display:flex;flex-wrap:nowrap;align-items:stretch;margin:0 0 var(--sp-3);
  overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none;
  -webkit-overflow-scrolling:touch;scroll-padding-inline:14px;overscroll-behavior-x:contain}
.wtabs::-webkit-scrollbar{display:none}
.wtab{display:inline-flex;align-items:center;gap:var(--sp-2);flex:0 0 auto;text-decoration:none;
  font-family:var(--ui);font-size:var(--t-xs);font-weight:var(--w-semi);color:var(--ink3);
  padding:var(--sp-2) 9px;min-height:38px;white-space:nowrap;
  border-bottom:2px solid var(--rule)}
.wtab:first-child{padding-left:0}
.wtab:last-child{padding-right:0}
.wtab:hover{color:var(--ink)}
.wtab[aria-current="page"]{color:var(--priInk);border-bottom-color:var(--priInk);
  font-weight:var(--w-bold)}
/* THE COUNT AS A SMALL BADGE, which is what the reference draws and what tells a reader the tab
   they are NOT on has work on it. `min-width` plus tabular figures so one digit and two digits are
   the same pill; `line-height` and not padding sets its height, so it cannot push the tab taller
   than the 38px the strip is. */
.wtab .c{min-width:17px;padding:0 5px;border-radius:999px;background:var(--paper2);
  color:var(--ink3);font-size:var(--t-micro);font-weight:var(--w-bold);line-height:16px;
  text-align:center;font-variant-numeric:tabular-nums}
/* ============================================================================================
   THE ACTIVE BADGE IS `--pri` AND NOT `--priInk`, AND THE WALK IS WHY.

   IT SHIPPED AS `--priInk` WITH `--priOn` AND MEASURED 1.78:1 IN THE DARK THEME on every one of
   the six shots of this page. `--priInk` is the INK token - `#14417E` on white in light, and in
   dark it inverts to `#A8C4EC`, a pale blue meant to be read ON the dark card. Filling a badge
   with an ink token and then writing white on it is asking the palette to work upside down, and
   in exactly one of the two themes it does not. Nothing in the light theme showed it: there the
   same rule measures 8.8:1, which is why this was found by the probe and not by looking.

   `--pri` IS THE FILL TOKEN AND IT FLIPS THE RIGHT WAY - `#0B1F44` in light, `#25426F` in dark,
   both of them deep enough for `--priOn` white to sit on: measured 15.1:1 and 10.0:1. It is the
   same pairing `.make` and `.tab[aria-current]` already use, so the active badge is now the one
   filled brand mark on the page rather than a fourth idea about what navy means.

   THE TINTED ALTERNATIVE WAS MEASURED AND REJECTED: `--priBg` with `--priInk` passes in both
   themes (8.8 and 8.1) and is nearly the same VALUE as the inactive badge's `--paper2`, so active
   and inactive would have been two pale pills a shade apart. Contrast against the text is not the
   only thing a badge has to do.
   ============================================================================================ */
.wtab[aria-current="page"] .c{background:var(--pri);color:var(--priOn)}

footer{font-family:var(--ui);font-size:10px;color:var(--mute);text-align:center;margin-top:26px;
  line-height:1.7}
.health{font:11px/1.6 var(--mono);color:var(--ink3);margin-top:20px;overflow-wrap:anywhere}

/* ---------- forms (sign in, first admin, accounts) ---------- */
.card{max-width:30rem;border-radius:18px;box-shadow:var(--shadow);background:var(--card);
  border:1px solid var(--rule);padding:18px 18px 22px}
.card label{display:block;margin:14px 0 5px;font-family:var(--ui);font-size:10px;font-weight:750;
  letter-spacing:.09em;text-transform:uppercase;color:var(--ink3)}
.card input,.card select,.card textarea{width:100%;font:inherit;font-size:15px;padding:11px 12px;
  min-height:46px;border-radius:12px;border:1px solid var(--rule);background:var(--paper);
  color:var(--ink)}
/* The quotation note is the first multi-line field in this app. It takes the same box as every
   other control above and adds only what a textarea needs: room for three lines, a vertical
   handle, and a line height a paragraph can be read at. Horizontal resizing is off because the
   card is already the width the page decided. */
.card textarea{min-height:92px;resize:vertical;line-height:1.5;font-family:inherit}
.card button{margin-top:18px;font:inherit;font-size:13.5px;font-weight:750;padding:12px 18px;
  min-height:46px;border-radius:13px;cursor:pointer;border:1px solid transparent;
  background:var(--priGrad);color:var(--priOn)}
.hint{font-size:11.5px;color:var(--ink3);margin:6px 0 0;line-height:1.5}

/* THE NEW-ORDER FORM, on the sign-in card's own bones.
   Wider than that card because half of it is two or three columns of dates and dropdowns, and a
   phone still gets one column - `auto-fit` over a minimum decides that, not a breakpoint, so the
   same rule covers a phone, a tablet held either way and the rail-width desktop. */
.card.wide{max-width:46rem}
.nogrid{display:grid;gap:0 16px;grid-template-columns:repeat(auto-fit,minmax(13.5rem,1fr))}
.nofoot{margin-top:20px;padding-top:15px;border-top:1px solid var(--rule2)}
/* ============================================================================================
   THE ONE FORM WHOSE VALUES HAVE TO BE READ BACK CHARACTER BY CHARACTER.
   ============================================================================================
   `/admin/document` holds the bank block that prints on every quotation a client receives. A
   46rem card at 1280 left 272px of page empty beside it and gave each of its three columns 222px,
   in which `IQ31NBIQ857002000204312` was sliced four pixels short of its last digit and the
   company's registered Arabic name lost its trailing 138px. An IBAN a person cannot read back is
   the whole point of the page failing: the blast radius of a wrong digit is money arriving at the
   wrong account, which is the reason `quote-doc.js` gives for the page being admin-only.

   SO IT TAKES THE ROOM THAT WAS ALREADY THERE, and the two lines that are genuinely long - a
   registered company name in Arabic, a street address that names two other businesses as
   landmarks - take a whole row instead of a third of one. Everything is still `auto-fit` over a
   minimum, so a phone still gets one column and nothing here is a breakpoint. `.widest` is not
   `.wide` because every other wide form in the app is a form somebody FILLS IN, where a measured
   column is easier to read down; this is a form somebody CHECKS. */
.card.widest{max-width:none}
.nogrid > .long{grid-column:1/-1}

/* ONE LINE OF A QUOTATION. A block per line, never a row of a six-column table of inputs: this
   form is filled in on a phone, and `.nogrid` already lays a pair of fields out as one column under
   13.5rem and two above it, decided by the content rather than by a breakpoint. The number is drawn
   rather than typed - it is the position in the form, renumbered from 1 on the way in - and it sits
   in the corner rather than on a line of its own so a line costs no extra height. */
.qline{position:relative;margin-top:12px;padding:4px 13px 13px;border-radius:16px;
  border:1px solid var(--rule2);background:var(--paper2)}
.qline > .qlno{position:absolute;top:10px;right:13px;font-family:var(--ui);font-size:10px;
  font-weight:750;letter-spacing:.06em;color:var(--ink3)}
/* ============================================================================================
   A `<summary>` WEARING `.qline`, WHICH IS A DIFFERENT SHAPE FROM A FORM LINE WEARING IT.

   FOUND ON 2026-09-17 BY WALKING EVERY PAGE AT 390px, AND IT IS OLDER THAN THIS SLICE - the same
   collision is in the screenshots taken at the commit this work started from. The quotation
   RECORD page reuses `.qline` for the `PRICING` fold (`<summary class="qline"><b class="qlno">`),
   and `.qlno` above is absolutely positioned into the block's top-right corner because on the
   quotation FORM it is a line number over a stack of fields with nothing else on that line. On
   the record the badge is a LABEL and the summary's own sentence is long: at 390px "…sent by Anas
   Rahmon on 14 Sep 2026" reaches the corner and runs straight under the word PRICING, which then
   reads as grey mush over black text.

   The fix is to let the badge join the flow wherever it is a summary's label rather than a
   block's corner number. The form is untouched - `.qline` there is a `<div>`, never a `<summary>`,
   so the rule above still owns it and the numbers still sit in their corners.

   `nowrap` AND NOT `wrap`, which took one more look at a browser to get right. The summary's
   three parts are the badge, a BARE TEXT NODE and the chevron, and an anonymous flex item takes
   `flex:0 1 auto` - so on a wrapping line the sentence claimed the whole remaining width and
   pushed the chevron onto a third line of its own. Told not to wrap, the sentence shrinks and
   breaks inside itself instead, and the three parts stay one row at every width.
   ============================================================================================ */
summary.qline{display:flex;align-items:center;gap:var(--sp-3);flex-wrap:nowrap;
  padding:11px 13px;font-size:var(--t-sm)}
summary.qline > .qlno{position:static;flex:0 0 auto}
summary.qline > .tchev{margin-left:auto}
.qline label{margin-top:10px}
.qline textarea{min-height:56px}
/* The totals block reads as a summary and not as another list of things to tap. Two of its seven
   rows are heavier - what the client pays, and what is left to pay - and WHICH two is decided by
   the view, which has the figures, rather than by a `:nth-child` here that would move the emphasis
   onto the wrong line the day a row is inserted. `.qtot` rides on `.rows`/`.row`, the surface every
   list in this app already uses. */
.qtot .row.big{background:var(--paper2)}
.qtot .row.big .v{font-size:15px;font-weight:800}

/* Tick-boxes as pills. NATIVE CHECKBOXES under the styling, never buttons toggling a class: this
   app serves a policy that forbids script outright, and a checkbox already submits once per ticked
   box and nothing for the rest - which is exactly the list the server wants. Both overrides below
   are there to beat `.card label` and `.card input`, which style the stacked fields above. */
.nopick{display:flex;flex-wrap:wrap;gap:8px;margin-top:7px}
.card .nopick label{display:inline-flex;align-items:center;gap:8px;margin:0;min-height:42px;
  padding:0 13px;border:1px solid var(--rule);border-radius:13px;background:var(--paper);
  font-family:var(--ui);font-size:12px;font-weight:640;letter-spacing:0;text-transform:none;
  color:var(--ink2);cursor:pointer}
.card .nopick label:hover{border-color:var(--brassInk);color:var(--ink)}
.card .nopick input[type=checkbox]{width:auto;min-width:0;min-height:0;padding:0;margin:0;
  accent-color:var(--pri)}
/* The ticked pill, tinted. `:has()` and nothing else - progressive: where it is unsupported the
   checkbox itself still shows the state, which is the whole answer and not merely a fallback. */
.card .nopick label:has(input:checked){border-color:var(--brassInk);color:var(--ink);
  background:var(--priBg)}
/* THE PO DOCUMENT FIELD. A file input is the one control a browser will not let a page fake without
   script, and on an iPhone or iPad the native one is what opens Files, iCloud and the camera - so
   only the box around it is ours. The overrides beat `.card input`, whose 46px and vertical padding
   are meant for a text field and leave the button sitting low inside it. */
.card input[type=file]{padding:9px 12px;line-height:1.7}
.card input[type=file]::file-selector-button{font:inherit;font-size:12.5px;font-weight:640;
  margin:0 11px 0 0;padding:7px 12px;border-radius:10px;border:1px solid var(--rule);
  background:var(--card);color:var(--ink2);cursor:pointer}
/* THE TWO NOTICE BANNERS - the red one over a form that was refused, the green one over a form that
   worked. `p.` AND NOT A BARE CLASS, and the element is doing real work in that selector.
   `.ok` is also the tone word this stylesheet uses for "good" on the CHIPS - `.pchip.ok` is the
   preparation chip reading Done, and every `Done` row inside a picker is one. A bare `.ok` here
   matches all of them, and since `border` and `padding` are properties `.pchip` sets at the same
   specificity and EARLIER in the file, this rule won them: every green chip on every card came out
   with a green outline and a banner's padding while its neighbours had neither. Both banners are
   `<p>` in `pages.js` - all six of them, login, setup, new order, users - so naming the element
   costs nothing and takes the rule off every chip in the app. */
p.err{background:var(--lateBg);border:1px solid var(--late);color:var(--ink);padding:11px 13px;
  border-radius:13px;margin:0 0 16px;font-size:13px}
p.ok{background:var(--okBg);border:1px solid var(--ok);color:var(--ink);padding:11px 13px;
  border-radius:13px;margin:0 0 16px;font-size:13px}
/* THE SAME RED BOX, UNDER THE FIELD THAT CAUSED IT - 2026-09-17, the owner's instruction that a
   refusal is "رسالة حمراء بمكانها" and never a whole page. Nothing about the colour, the border or
   the radius changes: a reader has already learned what this box means at the top of a form, and
   it must mean the same thing six fields down. What changes is only the MARGIN - a banner needs
   room under it and a field error needs room ABOVE it, tight against the input it belongs to. */
p.err.efld{margin:7px 0 0;font-size:12.5px;padding:9px 12px}

/* ==============================================================================================
   THE THREE FORMS BUILT OUT OF ONE VOCABULARY - Create RFQ, Add client, New creative request.

   `.qform` IS THE LAYOUT ELEMENT AND `.qsec` IS A SECTION CARD INSIDE IT. That much is shared by
   all three pages and is what this small block holds; what each page then adds is its own, scoped
   to a class it invented - `.rsec` and the line table below, `.csec` and the tinted strip near the
   foot of this file.

   THE ONE THING BORROWED AND NOT RESTATED IS `.card`: every input rule in this stylesheet is
   scoped under it - the label, the box, the radius, the textarea - so a section that was not a card
   would have had to copy all of them.

   `container` ON THE FORM is what lets a line table and a field pair ask how wide THEY are rather
   than how wide the window is. This app's navy sidebar is 192px from 768 up and `.wrap` caps at
   760 until 1200, so a "768px tablet" is 528px of actual page and an 834px iPad in portrait is
   594px; asking the window would give the one device these forms are most filled in on the phone
   shape. Every breakpoint below is a `@container` for that reason.
   ============================================================================================== */
.qform{container:rfqform / inline-size}

/* ONE SECTION HEADING, AND THE NUMBERED BADGE THE TWO CLIENT FORMS PUT IN IT. `.qsn` is `.ptile`'s
   shape one step down - same tint family, same radius - so a section number reads as the same
   family of mark as the page's own icon and never as a status. `/rfqs/new` draws `.rsn` in that
   slot instead: an icon rather than a digit, because its three cards are not a sequence. There is
   no sentence under the heading on any of the three: see `HEAD_SUB_MAX` in `views/layout.js` for
   the instruction that governs that, given three times in the same words. */
.qsh{display:flex;align-items:center;gap:var(--sp-3);margin:0;font-family:var(--ui);
  font-size:var(--t-title);font-weight:var(--w-heavy);letter-spacing:var(--ls-title);color:var(--ink)}
.qsn{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:28px;height:28px;border-radius:var(--r-sm);background:var(--priGrad);color:var(--priOn);
  font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-heavy);letter-spacing:0}
/* The first label of a section sits under the heading rather than against it - `.card label`'s own
   14px top margin is measured against a preceding INPUT, not against a 28px tile. Both pages that
   tighten this - `.qsec.csec` and `.qsec.rsec` - override it at two classes, which is written out
   at each of them rather than left to source order. */
.qsec > .qsh + label,.qsec > .qsh + .nogrid,.qsec > .qsh + .qtab{margin-top:var(--sp-4)}

/* ==============================================================================================
   CREATE RFQ, 2026-09-21 - THREE CARDS, A TABLE OF LIVE ROWS, AND A BOTTOM BAR.

   WHAT WENT, AND IT IS MORE THAN WHAT ARRIVED. The two-column layout (`.qcols`, `.qmain`,
   `.qside`), the two utility panels (`.qpanel`, `.qph`, `.qtipsbox`, `.qtips`) and the whole
   fold-per-line editor (`.qsum`, `.qcn` cells, `.qchev`, `.qedit`, `.qrm`) are deleted rather than
   overridden: the owner's picture has one column of three cards and a line that is a row of live
   boxes, and a stylesheet that kept the old rules beside the new ones would be two designs for one
   page waiting to be confused. `.qsh`, `.qsn`, `.qadd`, `.qcount`, `.qfoot`, `.nogrid` and
   `p.err.efld` stayed because OTHER pages draw them - `/clients/new` and `/creative-request/new`
   are built out of exactly that vocabulary, and this block was written so that not one of their
   bytes moved.

   EVERY RULE BELOW IS SCOPED TO A CLASS THIS PAGE INVENTED - `.rsec`, `.rsn`, `.rfill`, `.rauto`,
   `.rq` (shared with the two client forms, and unchanged), `.qtab` and its `.r*` children. The
   working-days chips are NOT in that list: they are `.wdays` at the foot of this file, drawn by
   `deliveryTimeField` in `views/quotations.js` for this page and for the quotation form both.
   The two exceptions are named where they are made: `.ficon > select` extends an existing rule by
   one element, and `.qfoot.rfoot > a` joins `.qfoot.sfirst > a`'s selector rather than copying its
   four declarations.

   AND WHAT IT COST, MEASURED RATHER THAN CLAIMED. The commit that landed this block said the
   stylesheet had got smaller. It had not, and the correction belongs where the next reader will
   be standing: the RULES grew by about 2.1KB (81.4KB to 83.5KB with comments stripped and
   whitespace collapsed) and the FILE grew by 11.7KB, because the block that went carried less
   prose than the block that arrived. Over the wire - `asset.js` serves these bytes compressed and
   never minified - that is about 3.9KB of gzip on a ~90KB file.

   THE TRADE IS STATED SO THAT IT CAN BE DISAGREED WITH. Seven live cells per line need more rules
   than five read-only ones behind a fold, and two of the seven (BRIEF and ATTACHMENTS) are new.
   The one place this block could be made smaller without losing a rule is its own commentary, and
   this file's standing bet - written at the top of it and at every block since - is that a rule
   whose reason is not beside it comes back wrong.

   `@container` AND NOT `@media`, which is this file's standing bias and is load-bearing here rather
   than stylistic. This app's navy sidebar is 192px wide from 768 up and `.wrap` caps at 760 until
   1200 - so a "768px tablet" is 528px of actual page and an 834px iPad in portrait is 594px. Asking
   the WINDOW how many columns a line has would have given the one device this form is most filled
   in on the phone shape. The form asks itself instead.
   ============================================================================================== */

/* ----------------------------------------------------------------------------------------------
   THE CARD, AND ITS DENSITY IS NOT A NEW OPINION.

   14/16/16 with a 16px gap under it, a 10/4 label block and a 40px control are `/clients/new`'s own
   numbers, measured and tightened on this same day after the owner opened that page on a 1900px
   window and asked for "a little smaller the ratio so the fields and info visual better". He then
   asked for THIS page in the same picture language, so the density is copied deliberately and not
   re-decided. The density block at the foot of this file is where those numbers are set for
   `.csec`; they are restated here rather than shared through one selector because the two pages
   differ in the one thing that would have to be undone - `.csec` bleeds a tinted strip across the
   card's header and this page's picture has none.
   ------------------------------------------------------------------------------------------- */
.qsec.rsec{padding:14px 16px 16px;margin-bottom:var(--sp-4)}
/* The first row sits under the heading rather than against it. 12 against a 36px tile is the same
   optical 18px the `.qsh + label` case reaches through the label's own top margin. */
.qsec.rsec > .qsh + .nogrid,.qsec.rsec > .qsh + .qtab{margin-top:var(--sp-3)}

/* THE ICON TILE, WHICH IS WHERE THE SECTION NUMBER USED TO BE. `.qsn` is a navy gradient badge
   holding a digit; this is the same square one shade paler holding the glyph for what the card is
   about, which is what the owner drew. `--priBg` over `--priInk` is the existing pale-blue-on-blue
   pair and is the one that inverts correctly in the dark theme, where `--priBg` becomes a deep
   panel and `--priInk` a light blue. */
.rsn{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:36px;height:36px;border-radius:var(--r-sm);background:var(--priBg);color:var(--priInk)}
.rsn > svg{width:19px;height:19px}

/* THE LABEL - navy, 13px, sentence case, and this is the one typographic departure from the two
   client forms. Theirs are 10px uppercase with .09em of tracking, which is this app's older field
   label; his Create RFQ picture draws `Company Name *`, `Requested By`, `Delivery Time` in a
   readable navy sentence case, and the brief's own words for it are "compact navy labels". The
   SPACING is the client forms' - 10 above, 4 below - so the rhythm of the two pages is identical
   even where the type is not. `--priInk` and not `--pri`: `--pri` is near-black in the light theme
   and a mid navy in the dark one, which would put a dark label on a dark card. */
.qsec.rsec label{display:block;margin:10px 0 4px;font-family:var(--ui);font-size:var(--t-sm);
  font-weight:var(--w-semi);letter-spacing:0;text-transform:none;color:var(--priInk)}

/* THE CONTROL - 40px, which is the floor of the 40-46 range his own earlier spec named and the
   height his picture draws. Checkboxes and radios are excluded by attribute, as they are in the
   density block: a `min-height` on either is how this rule would silently break a control it was
   not written about. This page has none of either today; the exclusion is a guard against the day
   it does. */
.qsec.rsec input:not([type=checkbox]):not([type=radio]),.qsec.rsec select{min-height:40px}
.qsec.rsec input:not([type=checkbox]):not([type=radio]),.qsec.rsec select,
.qsec.rsec textarea{padding-top:8px;padding-bottom:8px}

/* A GLYPH INSIDE A BOX - `.ficon`, which already exists for the chain against the creative form's
   reference link, reused rather than copied. All this block adds is the one element that rule did
   not cover: a `<select>` needs the same left inset as an `<input>` when a clock or a tag sits in
   front of it. Two of the five boxes that wear it on this page ARE selects. */
.ficon > select{padding-left:40px}

/* THE AUTO-FILLED BOX - who is asking, filled in and read-only.
   THE PADDING ON THE RIGHT IS THE BADGE'S OWN WIDTH plus its inset, so a long name is clipped by
   the box rather than running under a floating pill. The badge is `pointer-events:none` because it
   is a statement and not a control: a tap on it should land on the box underneath, which is where
   a reader's finger is aiming. */
.rfill > input{background:var(--panel);color:var(--ink2);padding-right:100px}
.rauto{position:absolute;right:7px;top:50%;margin-top:-11px;height:22px;
  display:inline-flex;align-items:center;padding:0 9px;border-radius:var(--r-pill);
  background:var(--priBg);color:var(--priInk);font-family:var(--ui);font-size:var(--t-micro);
  font-weight:var(--w-bold);letter-spacing:.02em;pointer-events:none}

/* ==============================================================================================
   THE LINE TABLE - one line is one item, and every box on it is standing.

   THE SEVEN COLUMNS AND THEIR PROPORTIONS ARE THE OWNER'S: `# · CATEGORY · ITEM · QTY · BRIEF ·
   ATTACHMENTS · ACTIONS` at roughly 4/15/18/7/28/18/10 per cent. They are declared ONCE, in
   `--rcols`, and used by the heading row and by every line - the heading and the rows must agree
   about where a column starts or the alignment is a lie, and one custom property is one edit
   rather than two that drift.

   THREE OF THE SEVEN ARE FIXED AND FOUR ARE FLEXIBLE, and the split is a measurement rather than a
   preference. `#` holds one or two digits, `QTY` holds up to four, and `ACTIONS` holds two 44px
   targets side by side - none of the three gets wider with the page, so an `fr` on any of them
   takes room from the four that need it. `minmax(0,…)` ON EVERY FLEXIBLE TRACK, and the zero is
   load-bearing: a grid hands out `fr` space AFTER every track's minimum is satisfied, and an input's
   default intrinsic minimum is its `size` attribute's width - about 170px - which at four tracks is
   680px of floor on a 424px table. That is the exact shape of the overflow `figures.test.js`
   guards the whole app against.

   ATTACHMENTS IS THE ONE TRACK WITH A FLOOR, AND 100 IS THE WORD `2 files` PLUS ITS CLIP AND ITS
   INSETS. It is `minmax(100px,15fr)` rather than a flat 100 because his picture gives it real
   width at a desk, and rather than `minmax(0,15fr)` because a chip narrower than its own word
   would be the one cell in the row whose CONTENT is a fixed string. The floor is affordable
   because this template only ever applies from 700px of table, where the four flexible tracks
   still share 384px after it is taken.
   ============================================================================================== */
.qtab{--rcols:28px minmax(0,16fr) minmax(0,18fr) 48px minmax(0,27fr) minmax(100px,15fr) 92px;
  margin-top:var(--sp-3);border:1px solid var(--rule);border-radius:var(--r-md);
  background:var(--card);overflow:hidden}

/* THE HEADING ROW IS OFF ON A PHONE, which is the half of "compact rows, not a squeezed table"
   that a template change cannot do on its own: seven headings over a four-line stacked row would
   be labelling positions that are no longer there. */
.qth{display:none}

/* ONE LINE, AND THE DEFAULT TEMPLATE IS THE STACKED ONE - `Category | Item`, `Qty | Attachment`,
   `Brief`, which is the owner's own phone shape said in his own order. The ACTIONS column is a
   fourth track that SPANS ALL THREE ROWS rather than a row of its own under them: a 44px strip
   down the right is the same two targets in a third of the height, and a stacked line has to stay
   far smaller than the expanded editor this redesign removed or it has bought nothing.

   `min-height:44px` is the floor for the targets inside the row rather than for the row itself;
   the boxes are 38, which is the bottom of the 38-42 band the owner's spec names for a line
   control and is what keeps six lines on one screen. */
.qln{display:grid;align-items:center;gap:6px;padding:5px 6px;
  grid-template-columns:22px minmax(0,1fr) minmax(0,1fr) 44px;
  grid-template-areas:"n cat it ac" "n qty at ac" "n br br ac";
  border-top:1px solid var(--rule2);min-height:44px}
.qln:first-of-type{border-top:0}
.qtab > .qth + .qln{border-top:0}

/* THE ENTRY ROW - always there, always empty, and the last one. A different surface so that "this
   one is not filed yet" is visible without a word, which is what his picture does with it. */
.qln.rnew{background:var(--paper2)}

.rcn{grid-area:n;display:flex;align-items:center;justify-content:center;
  font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-bold);color:var(--ink2);
  font-variant-numeric:tabular-nums}
/* THE `+` WHERE A NUMBER WOULD BE, drawn as a CSS mark rather than an element: a row is rendered up
   to sixty times and every element in it is paid sixty times. The number is still in the markup on
   this row, hidden - `views/lines.js` reads a line's position out of `b.qcn`, so a row without one
   would be a row the script cannot renumber. */
.rnew > .rcn > .qcn{display:none}
.rnew > .rcn::before{content:"+";font-size:20px;line-height:1;font-weight:var(--w-bold);
  color:var(--priInk)}

.rc{min-width:0;display:block}
.rcat{grid-area:cat;position:relative}
.rit{grid-area:it}
.rqt{grid-area:qty}
.rbr{grid-area:br}
.rat{grid-area:at}
.rac{grid-area:ac}

/* THE BOXES IN A ROW. 38px rather than the 40 the header fields carry, which is the one place this
   page is denser than the card above it and is the owner's own number for a line control. The
   radius is one step down from a header box for the same reason. */
.qln input,.qln select{min-height:38px;padding:6px 10px;
  border-radius:var(--r-xs);font-size:var(--t-sm)}
/* A LINE'S REFUSAL, INSIDE ITS OWN CELL - the red line has to sit under the box it names and the
   box is 15% of a row, so it wraps rather than pushing the table sideways. */
.qln .err.efld{margin:4px 0 0;font-size:var(--t-xs);overflow-wrap:anywhere}

/* THE CHEVRON AGAINST THE CATEGORY BOX. The control is an `<input list>` - free text with
   suggestions, because the column it posts to is free text and a closed menu would be a rule
   invented in a view and enforced nowhere - so the affordance that says "there is a list here" has
   to be drawn. Two borders and a rotation, `.qchev`'s own drawing, which cost this file no icon
   entry when the fold editor used it and costs none now. */
.rcat::after{content:"";position:absolute;right:10px;top:50%;margin-top:-4px;
  width:7px;height:7px;border-right:2px solid var(--ink3);border-bottom:2px solid var(--ink3);
  transform:rotate(45deg);pointer-events:none}
/* 24 AND NOT 28, WHICH IS A MEASUREMENT. `Select category` at this file's 13px is 103px wide and
   the CATEGORY track at the owner's own 15 per cent is 149px at 1280; 10 + 24 of inset leaves 115,
   so the placeholder fits whole rather than being clipped to `Select categor`. Measured at 1280
   and again at 700, which is the narrowest width this table is drawn as seven columns at.

   AT 390 IT DOES NOT FIT AND THAT IS ACCEPTED RATHER THAN SOLVED. A phone gives each of the two
   stacked columns about 131px, which holds every VALUE this box takes - `Signage`, `Printing`,
   `Shop Decoration` all measure under 100 - and clips the last two characters of the PLACEHOLDER.
   The alternatives were a 12px control (under this file's smallest body size), a second shorter
   placeholder that the owner's picture does not say, or giving Category a row of its own and
   making every line a quarter taller on the device where height costs most. A word nobody has
   typed yet losing its tail is the cheapest of the four. */
.rcat > input{padding-right:22px}

/* THE ATTACHMENTS CELL. It is a box the size of the boxes beside it so that the row reads as seven
   cells rather than five and a gap, and it is drawn in the muted ink of something not yet done.
   `.on` is the state the other half of this slice turns on when a line has files staged against it;
   both states are here so that the day it is wired nothing in this block moves. */
.ratt{display:flex;align-items:center;gap:6px;min-height:38px;padding:0 10px;
  border:1px solid var(--rule);border-radius:var(--r-xs);background:var(--card);
  font-family:var(--ui);font-size:var(--t-sm);color:var(--ink3);
  text-decoration:none;overflow:hidden;white-space:nowrap}
.ratt > svg{flex:0 0 auto;width:15px;height:15px}
.ratt.on{background:var(--priBg);border-color:var(--priRule);color:var(--priInk);
  font-weight:var(--w-semi)}
a.ratt:hover{border-color:var(--ink3);color:var(--ink)}

/* THE ACTIONS. Two 44px targets and a 44px button in a 96px track - which is why that track is 96
   and not the 10% his proportions give it at the narrowest width this table is drawn at. A finger
   presses all three, so all three are at the floor whatever the glyph inside them measures. */
.rac{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px}
.ract{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;
  border-radius:var(--r-xs);color:var(--ink3);text-decoration:none}
.ract:hover{background:var(--paper2);color:var(--ink)}
.ract.rdel:hover{color:var(--late)}
.rnew > .rac > .ract{display:none}
/* THE NAVY `+`. `.card button` gives every button inside a card the primary gradient AND an 18px
   top margin AND a 46px height - the first is exactly right here and the other two are measured
   against a form whose last element is one Submit. Taking two of the three back is what these
   three declarations are; the same collision is written up against `.qrm` in the file this block
   replaced, and it is worth keeping the note because it will happen again. */
/* TWO CLASSES, BECAUSE IT HAS TO BE. `.card button` is a class AND an element, so `.radd` alone
   loses every declaration it tries to take back - which is how the first cut of this rule shipped
   a 44px button that measured 46. The same collision is recorded against `.qrm` in the block this
   one replaced and against `.cbx` in the density pass; it is worth the third telling. */
.qln .radd{display:none;align-items:center;justify-content:center;width:44px;height:44px;
  min-height:44px;margin-top:0;padding:0;border:0;border-radius:var(--r-xs);
  font-family:var(--ui);font-size:20px;font-weight:var(--w-bold);line-height:1}
.qln .rnew > .rac > .radd,.rnew > .rac > .radd{display:inline-flex}

/* ----------------------------------------------------------------------------------------------
   WHAT DOES NOT FIT THE ROW - the description behind the pencil, and the confirm behind the bin.

   `:target` AND NOT `<details>`, and the choice is forced by the grid: a `<details>` lays its open
   panel out inside the box its `<summary>` sits in, and that box here is a 96px Actions cell. A
   panel the width of the row has to BE a child of the row, which `grid-column:1/-1` makes it, and
   the only no-script way to open a plain child from an anchor elsewhere is the fragment.

   ONE OPEN AT A TIME COMES FREE. `:target` is one element per document by construction, so opening
   a second panel closes the first - the property the `<details name="rfqline">` accordion this
   block replaced had to ask the browser for.

   THE PANEL IS DISPLAY:NONE AND NEVER `disabled`. Its box is in the document and is posted by
   every submit this form has, which is what lets the `+`, a removal and a refusal all carry a
   description nobody has opened the panel to look at. `rfq-lines.test.js` sweeps the whole page
   for a `disabled` control precisely so that this cannot be "tidied" later.
   ------------------------------------------------------------------------------------------- */
.rmore{display:none;grid-column:1/-1;margin:2px 0 6px;padding:10px 12px;
  border-radius:var(--r-xs);background:var(--panel);border:1px solid var(--panelRule)}
.rmore:target{display:block}
.rmore > label{margin-top:0}
.rshut{display:inline-flex;align-items:center;min-height:44px;
  font-family:var(--ui);font-size:var(--t-xs);font-weight:var(--w-semi);color:var(--ink3)}
.rshut:hover{color:var(--ink)}
.rgone{display:none;align-items:center;gap:var(--sp-3);flex-wrap:wrap}
.rgone:target{display:flex}
.rgone > .linkbtn{min-height:44px;display:inline-flex;align-items:center;background:none;border:0;
  margin-top:0;padding:0;font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-semi);
  color:var(--late)}

/* ----------------------------------------------------------------------------------------------
   §3 - THE DROP AREA, WHICH IS A STATEMENT AND NOT A CONTROL.

   No `<input type="file">`, no `enctype`, no drag handler: a file cannot ride this POST, and the
   three reasons are in `attachCell`'s note in `views/rfq-new.js`. What is drawn is the shape of his
   panel with the supported types and the one sentence that answers "where do the photos go?".
   `cursor` IS DELIBERATELY NOT `pointer` - a hand cursor over something that cannot be pressed is
   the page making the promise the markup was careful not to make.
   ------------------------------------------------------------------------------------------- */
.rdz{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
  min-height:96px;margin-top:22px;padding:var(--sp-4);text-align:center;
  border:1px dashed var(--rule);border-radius:var(--r-md);background:var(--panel);color:var(--ink3)}
.rdz > svg{width:22px;height:22px}
.rdz1{margin:2px 0 0;font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-semi);
  color:var(--ink2)}
.rdz2{margin:0;font-size:var(--t-xs);color:var(--ink3)}

/* ----------------------------------------------------------------------------------------------
   FROM 700 OF CONTENT THE LINE IS A ROW OF SEVEN, with a heading row over it and the SAME `--rcols`
   for both so the two cannot come apart.

   700 WAS MEASURED AND NOT CHOSEN, AND IT IS HIGHER THAN THE 480 THE FIVE-COLUMN TABLE THIS BLOCK
   REPLACED USED. Seven columns is two more than five, and the two that arrived - BRIEF and
   ATTACHMENTS - are the widest of the seven. Walked at 834 with the old number, an iPad in
   portrait (594px of page behind this app's 192px sidebar) drew `Si` in the category box and
   `LED Li` in the item: a table whose VALUES are clipped is worse than no table, because the row
   then has to be tapped to be read, which is the exact defect this redesign exists to remove.

   SO THE BAND FROM 480 TO 699 GETS THE OWNER'S OWN STACK instead - `Category | Item`,
   `Qty | Attachment`, `Brief`, which is what his spec asks for on a phone and which at 594px is
   two comfortable 270px columns rather than a squeeze. An iPad in LANDSCAPE is 728px of page and
   gets the full seven; a 1280 window is 1012 and gets them with room. What this costs is the
   seven-column table on a 768px window, and that is the honest reading of "from tablet up" on a
   shell that spends 212px before the page starts.
   ------------------------------------------------------------------------------------------- */
@container rfqform (min-width:700px){
  .qth{display:grid;grid-template-columns:var(--rcols);gap:var(--sp-2);align-items:center;
    padding:7px 8px;background:var(--paper2);border-bottom:1px solid var(--rule2);
    font-family:var(--ui);font-size:var(--t-micro);font-weight:var(--w-bold);
    letter-spacing:var(--ls-caps);text-transform:uppercase;color:var(--ink3)}
  .qth > span:nth-child(1),.qth > span:nth-child(4){text-align:center}
  .qth > span:nth-child(7){text-align:right}
  .qln{grid-template-columns:var(--rcols);
    grid-template-areas:"n cat it qty br at ac";gap:4px var(--sp-2)}
  /* SIDE BY SIDE ONCE THERE IS A COLUMN FOR THEM, rather than stacked down a 44px strip. */
  .rac{flex-direction:row;justify-content:flex-end}
  .rqt > input{text-align:center}
}

/* A FULL-WIDTH `+` AT THE FOOT OF A REPEATING BLOCK, AND THE COUNT UNDER IT.

   `.qadd` IS `/clients/new`'s ALONE SINCE 2026-09-21. Create RFQ drew it as `+ Add Item` under its
   line table until that page's redesign moved the act into the entry row's own navy `+`; the Add
   client form still ends its contacts block with one, so the rule stays exactly as it was and the
   selector did not have to change.

   `.qcount` IS STILL BOTH PAGES'. It is two numbers in one phrase - "5 of 60 items" - and never a
   sentence beside the control: what the ceiling is is a fact about the form rather than a warning
   about the press, and it is the only place either page states its cap out loud. */
.qadd{margin:0;border-top:1px solid var(--rule2);padding:0}
.qadd > .linkbtn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;
  min-height:48px;background:none;border:0;margin-top:0;border-radius:0;
  font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-bold);
  color:var(--brassInk)}
.qadd > .linkbtn:hover{background:var(--priBg)}
.qcount{margin:var(--sp-3) 0 0;font-family:var(--ui);font-size:var(--t-xs);
  font-weight:var(--w-semi);color:var(--ink3);font-variant-numeric:tabular-nums}

/* THE FOOTER - Cancel and the one act, and nothing else on the row. It is a `.card` so that `.card
   button` styles the one button on it; the margin that rule gives a stacked form is the one thing
   taken back, because here the button is beside something rather than under it.

   THREE PAGES, TWO ORDERS. `.qfoot` alone is Cancel-left / primary-right, which is Add Client's
   and Create RFQ's; `.sfirst` puts Send first with Cancel after it, which is the creative form's
   own written-down order. `.rfoot` is Create RFQ asking for `.sfirst`'s OUTLINED Cancel without
   its reordering - his picture draws the link as a pill - so it joins that selector rather than
   copying its four declarations. */
.qfoot{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);
  padding:var(--sp-3) var(--sp-4);margin-top:var(--sp-4)}
.qfoot > a{font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-semi);
  text-decoration:none;color:var(--ink3);min-height:44px;display:inline-flex;align-items:center;
  padding:0 var(--sp-3)}
.qfoot > a:hover{color:var(--ink)}
.qfoot > button{margin-top:0;min-height:48px;min-width:9.5rem}
/* =============================================================================================
   NEW CREATIVE REQUEST, 2026-09-21. Six selectors, and the count is the point of this block.

   The owner sent this form back as a picture - "هي فورم الزبون بدي نفسها ذاتها", this is the
   client form, I want it exactly the same - and what the picture shows is the visual language
   this stylesheet already speaks: numbered cards with a navy badge, a tinted header strip,
   `.nogrid` pairs, thin borders, red asterisks on the required fields only. `/rfqs/new` draws
   that today and the Add Client page was rebuilt in it on the 21st. So the page is composed out
   of `.qform`, `.card widest qsec`, `.qsh`, `.qsn`, `.nogrid`, `.qfoot` and `p.err.efld`, and
   what is below is only the details of his picture that had no expression anywhere in this file.

   TWO OF THE SIX ARE ADD CLIENT'S OWN BYTES AND NOT A SECOND COPY OF THE IDEA. `.csec > .qsh`
   and `.rq` are that slice's rules character for character, so the two pages tint one strip and
   draw one asterisk; the day the branches meet, one of the two blocks is deleted and nothing on
   either page moves. A second card vocabulary for the same shape is the drift this file argues
   against in every block it has.

   WHAT IS *NOT* HERE IS THE ARGUMENT. No new card, no new input rule, no new label rule, no new
   button rule, no second grid, no breakpoint. The stylesheet is served at a content-addressed
   `immutable` URL and is sha-pinned in eleven test files - one byte is a fresh ~92KB download on
   every phone on shop-floor wifi - so a redesign that could be composed was composed. */
/* THE TINTED HEADER STRIP - a full-bleed band across the top of the card, so the negative margins
   undo `.card`'s padding exactly; `--panel` is the existing "a surface inside a surface" token and
   inverts correctly. SCOPED TO `.csec`, which `/rfqs/new` does not carry, so its own section
   headers are untouched - checked by that page's byte and layout tests rather than by eye. */
/* THE NEGATIVE MARGINS ARE `.qsec.csec`'s OWN PADDING NEGATED, and the two move together: see the
   density block at the foot of this file, which is where that padding is now set. -14/-16 rather
   than -18/-18 since 2026-09-21. */
.csec > .qsh{margin:-14px -16px var(--sp-2);padding:var(--sp-2) var(--sp-4);
  background:var(--panel);border-bottom:1px solid var(--panelRule);
  border-radius:17px 17px 0 0;flex-wrap:wrap}
/* THE RED ASTERISK, AND IT IS THE ONLY NEW COLOUR USE IN THIS BLOCK. `--late` is the existing
   refusal red and is the one that meets contrast in both themes; a literal would have been a
   fourth red in a palette that deliberately has one. */
.rq{color:var(--late);font-weight:var(--w-bold);margin-left:3px}
/* HOW MANY CHARACTERS ARE IN THE BOX, INSIDE THE BOX'S OWN LOWER EDGE - his `28/2000`.
   IT IS THE SERVER'S COUNT AND NOT A LIVE ONE, and that is the whole of what is here: no page in
   this app may REQUIRE JavaScript, and a live tally is the one thing on his picture that cannot
   be drawn without one. So the number is true at render - `0` on a fresh form, and the length of
   the paragraph that came back on a refusal - and the ceiling beside it is the one `maxlength`
   enforces. `.cwrap` is its positioning parent and exists only for that.
   `right:22px` CLEARS THE RESIZE GRIP. `.card textarea` is `resize:vertical`, and Chrome draws
   its handle in exactly the corner his counter sits in; measured at 390 and 1280. */
.cwrap{position:relative;display:block}
.ccnt{position:absolute;right:22px;bottom:8px;font-family:var(--ui);font-size:var(--t-xs);
  font-variant-numeric:tabular-nums;color:var(--ink3);background:var(--paper);
  padding:0 2px;pointer-events:none}
/* THE CHAIN AGAINST THE REFERENCE LINK - his icon at the left edge of that one box. An inline
   `<svg>` rather than a background image, `views/icons.js`'s own standing reason: a glyph in the
   markup takes `currentColor` and inverts with the theme, and `style-src 'self'` has no
   `'unsafe-inline'` for a background to be set from anywhere else. */
.ficon{position:relative;display:block}
.ficon > svg{position:absolute;left:13px;top:50%;margin-top:-9px;width:18px;height:18px;
  color:var(--ink3);pointer-events:none}
.ficon > input{padding-left:40px}
/* SEND FIRST, CANCEL AFTER, BOTH ON THE LEFT - his footer, and this form's own written-down
   order. `sendRow`'s note has said since the page shipped that Send is first because it is the
   act; `.qfoot` alone is Cancel-left/primary-right, which is Add Client's order and the opposite
   of what he drew here. One modifier, so the two pages still share the footer's surface, its
   padding and its 48px button. Cancel is outlined here and a bare link there, which is the other
   half of the same picture. */
.qfoot.sfirst{justify-content:flex-start;gap:var(--sp-3)}
.qfoot.sfirst > button{display:inline-flex;align-items:center;justify-content:center;
  gap:var(--sp-2)}
.qfoot.sfirst > button > svg{flex:0 0 auto;width:16px;height:16px}
.qfoot.sfirst > a,.qfoot.rfoot > a{border:1px solid var(--rule);border-radius:var(--r-md);
  min-height:48px;padding:0 var(--sp-4);color:var(--ink2)}
.qfoot.sfirst > a:hover,.qfoot.rfoot > a:hover{border-color:var(--ink3);color:var(--ink)}
/* HOW MUCH ROOM IS LEFT ON THIS MACHINE'S DISK, drawn only when there is little enough to say so.
   It is a `.hint` because it is a number and not a refusal - nothing has been refused yet - and it
   is amber rather than grey because it is the one hint on either creative page that is a warning.
   See `diskNote` in `src/disk.js` for why it is a number and not a sentence about disks. */
.hint.low{color:var(--risk);font-weight:600}
table{border-collapse:collapse;width:100%;font-size:13px}
th,td{border-bottom:1px solid var(--rule2);padding:9px 11px;text-align:left;vertical-align:top}
th{font-family:var(--ui);font-size:10px;font-weight:750;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink3)}
.wrapx{overflow-x:auto;border-radius:18px;border:1px solid var(--rule);background:var(--card);
  box-shadow:var(--shadow)}
.wrapx table{margin:0}

/* THE TWO ACCOUNT PAGES' LINKS - `Change password` on the Accounts row it
   belongs to, and `Back to accounts` under the form it leads to, 2026-09-26.
   Two selectors, both needing `.upwl`, which `adminUsersPage()` and
   `adminPasswordPage()` in `views/pages.js` are the only things that emit.

   IT EXISTS FOR THE 44px AND NOTHING ELSE. A bare `<a>` - in a `<td>`, or in a
   `<p class="hint">` under a form - is a line of text about 13px tall, and this
   tree's floor for anything a finger presses is 44: the same floor `.cbx`/`.smi`
   were raised to in the density pass of 2026-09-21 and the one every browser
   test in `test/` asserts. `inline-flex` is what lets a link have a height at
   all; the padding and the hover tint are the `.rb` rail entry's, so a link
   somebody presses reads as this app's own vocabulary.

   THE `Cancel` ON THE CREATE-ACCOUNT FORM IS DELIBERATELY NOT GIVEN THIS. It is
   13px today and was before this slice; raising it is a change to a control
   nobody asked about, on a form nobody was told would move. When it is measured
   it joins this selector. */
/* THE NEGATIVE MARGINS EAT THE CELL'S OWN PADDING, and without them the row is
   62px tall with this link's text sitting 13px below the username beside it:
   `th,td` is `vertical-align:top` with 9px of padding, so a 44px box in a cell
   grows the row by its whole height and centres its text inside itself. Cancelling
   the padding makes the ROW 44px - the target's own height, which is the height it
   should have been - and leaves the two baselines 4px apart, which is the closest
   they get without editing `th,td`. That selector is shared with every table in the
   app and is not being touched for one link. The `<p class="hint nofoot">` on the
   password form has no such padding to cancel, so the same rule simply sits there. */
.upwl{display:inline-flex;align-items:center;min-height:44px;margin:-9px 0;
  padding:0 10px;border-radius:11px;
  font-family:var(--ui);font-size:12px;font-weight:650;text-decoration:none;color:var(--ink3)}
.upwl:hover{color:var(--ink);background:var(--paper2)}

/* ============================================================
   THE OWNER'S PERSONAL "HIDE IQD" SWITCH.

   READ THIS BEFORE REUSING THE PATTERN. This IS a CSS class over a figure that was sent to the
   browser, and here that is correct: it is Anas hiding numbers he is allowed to see from someone
   reading over his shoulder on the shop floor. It is cosmetic, it is his own choice, and View
   Source showing the figure underneath costs him nothing.

   It is worthless as a PERMISSION and must never be pressed into service as one. A role that may
   not see a figure never receives it - see `src/redact.js`. If you ever find yourself adding a
   role name to a rule in this block, the change belongs in redact.js instead.
   ============================================================ */
.mny{font-variant-numeric:tabular-nums}
body.nomoney .mny{color:transparent;position:relative}
body.nomoney .mny::after{content:"·····";position:absolute;left:0;top:0;color:var(--ink3);
  letter-spacing:.12em}

@media(min-width:620px){
  body{font-size:15.5px}
  /* THE ONE TOKEN THAT IS NOT A FIXED NUMBER. A 26px page title over a 44px tile leaves about
     300px of line on a 390px phone, which is two or three lines for any heading carrying a project
     code. Everything else in the type scale holds at every width; the display step is the only one
     big enough for the difference to matter. */
  :root{--t-display:26px}
}

/* ============================================================================================
   iPad AND UP - 700: the bottom bar becomes the branded sidebar down the left edge.

   FLUSH TO THE EDGE AND FULL HEIGHT, which is the change of shape in the 2026-09-17 shell and not
   a change of colour. It was a floating pill - inset 14px on all four sides, a 22px radius and
   the page colour showing all round it - and a floating pill is a PANEL OF THE PAGE. The
   reference draws the company's own edge of the window: navy, flush, top to bottom, with the
   wordmark at the head and the line at the foot. There is no gap for the page to show through
   because the sidebar is not standing on the page.

   IT IS NAVY IN LIGHT, DARK AND AUTO. See `--side-*` in the token block at the top of this file
   for why that is deliberate rather than an oversight, and why dark still gets its own deeper
   value rather than the same one.

   `body{padding-left}` IS STILL WHAT MAKES ROOM, unchanged in mechanism - the sidebar is
   `position:fixed` and is out of flow, so the page has to be told how wide it is. The two
   numbers are the token and the token plus the gutter, so widening the sidebar is one edit.
   ============================================================================================ */
/* ============================================================================================
   768 AND UP - THE TABLET. One labelled navy sidebar, at the reference's own breakpoint.

   IT WAS TWO RAILS AND IS NOW ONE. Until 2026-09-18 this file drew a 76px ICON-ONLY rail from
   700px and only gave it labels from 820 - so a device between 700 and 819 got a column of
   unlabelled glyphs, and the 820 number was itself a guess at "about where an iPad is". The
   reference names the boundary instead: mobile 320-767, tablet 768-1199, desktop 1200+. There is
   no device in that scheme that wants an unlabelled rail, so the icon-only form is gone and with
   it the 76px width token, the column-direction `.rb`, and an entire second block of overrides.

   WHAT A READER GAINS: at 768-819 - a small tablet, and an iPhone in landscape - the six
   destinations are now words rather than glyphs. What a reader loses: nothing. The bottom bar
   still serves every width below 768, which is every phone in portrait.
   ============================================================================================ */
@media(min-width:768px){
  .nav{display:none}
  .brandm{display:none}
  .railnav{display:flex;flex-direction:column;align-items:stretch;
    position:fixed;left:0;top:0;bottom:0;width:var(--side-w-md);z-index:40;
    padding:var(--sp-5) var(--sp-3) var(--sp-4);
    background:linear-gradient(180deg,var(--side-bg),var(--side-bg2));
    border-right:1px solid var(--side-rule);overflow:hidden}
  /* THE DIAGONAL AT THE FOOT, drawn with a gradient and not with a picture. Two hairlines at 45
     degrees across the bottom corner - the reference's own mark, and the one piece of decoration
     in this shell. A gradient because an SVG in CSS needs a namespace URL, and the note at the
     top of `views/icons.js` records what that costs this project; there is no <img> anywhere in
     this sidebar either, by the owner's rule for this round. */
  .railnav::after{content:"";position:absolute;right:-24px;bottom:-24px;width:118px;height:118px;
    opacity:.75;
    background:repeating-linear-gradient(135deg,transparent 0 11px,var(--side-mark) 11px 16px);
    pointer-events:none}

  /* THE WORDMARK. The company's NAME as text - there is no logo here and that is the owner's own
     rule for this round, not a thing left to do later. */
  .brand{display:block;text-decoration:none;padding:var(--sp-2) var(--sp-3) var(--sp-6);
    color:var(--side-ink)}
  .bname{display:block;font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-heavy);
    letter-spacing:.08em;line-height:1.2;color:var(--side-ink);text-align:left;
    overflow-wrap:anywhere}
  .bsub{display:block;margin-top:3px;font-family:var(--ui);font-size:9px;
    font-weight:var(--w-semi);letter-spacing:.07em;color:var(--side-ink3);text-align:left}

  .raillinks{display:flex;flex-direction:column;gap:var(--sp-1);align-items:stretch;
    min-height:0;overflow-y:auto;scrollbar-width:none}
  .raillinks::-webkit-scrollbar{display:none}
  .rb{display:flex;flex-direction:row;align-items:center;justify-content:flex-start;
    gap:var(--sp-3);width:100%;min-height:44px;
    padding:0 var(--sp-3);border-radius:var(--r-md);text-decoration:none;
    color:var(--side-ink2);font-family:var(--ui);font-size:var(--t-sm);
    font-weight:var(--w-med);position:relative;border:1px solid transparent;text-align:left;
    transition:background .14s ease,color .14s ease}
  .rb:hover{background:var(--side-hover);color:var(--side-ink)}
  .rb[aria-current="page"]{background:var(--side-on-bg);color:var(--side-on-ink);
    border-color:transparent;font-weight:var(--w-bold);
    box-shadow:0 2px 8px rgba(0,0,0,.25)}
  /* A WHITE RING AND NOT THE PAGE'S OWN. `:focus-visible` at the top of this file draws
     `2px solid var(--cool)` - a mid blue picked to be seen on paper - and on a #111C33 sidebar it
     is very nearly invisible. Every focusable thing inside the rail gets the ring that works on
     the surface it is actually sitting on, which is the whole reason the rail's colours are their
     own token family rather than the page's. */
  .rb:focus-visible,.brand:focus-visible{outline:2px solid #fff;outline-offset:-2px;
    border-radius:var(--r-md)}
  .rb .pip{position:static;margin-left:auto}

  /* THE LINE AT THE FOOT. `margin-top:auto` pins it to the bottom whatever the link count is -
     four doors for production, six for the admin - so it never floats mid-rail on a short bar. */
  .railfoot{margin:auto 0 0;padding:var(--sp-4) var(--sp-3) 0;font-family:var(--ui);
    font-size:var(--t-micro);font-weight:var(--w-semi);line-height:1.45;
    letter-spacing:.04em;color:var(--side-ink3);text-align:left;position:relative;z-index:1}

  body{padding-left:calc(var(--side-w-md) + var(--sp-5));padding-bottom:var(--sp-8)}

  /* THE PAGE TITLE AT ITS DESKTOP STEP. The reference asks for 24-26 on a phone and 28-32 from a
     tablet up; this is the only type token that is restated anywhere, and the note in THE NAMED
     SYSTEM says why it is the only one allowed to be. */
  :root{--t-display:29px}

  /* THE ACTION STOPS WRAPPING and takes its place at the right-hand end of the header's line. */
  .pact{flex:0 0 auto;margin-left:auto;padding-top:2px}
  .pact > .make{flex:0 0 auto;min-height:40px}
}

/* ============================================================================================
   1200 AND UP - THE DESKTOP. A wider rail, and more room for the page.

   THE RAIL GOES 192 -> 216, which is the reference's two bands (tablet 180-210, desktop 200-230)
   and the only thing that changes about it: same links, same labels, same gold active door.

   AND THE PAGE GETS WIDER, BUT NOT TO 1600. The brief asks for "fluid main content, max width
   ~1600px". What fills 1600px in the reference is the DESKTOP TABLE, and the table is a later
   stage - it is on this slice's do-not-build list. A single column of cards and list rows
   stretched to 1376px of main content is the "giant blank area" the same brief's QA list forbids,
   so the cap moves one honest step - 1040 -> 1280 - which is measured room for two card columns
   plus their gutter, and stops there until there is content shaped like 1600px to put in it.
   ============================================================================================ */
@media(min-width:1200px){
  .railnav{width:var(--side-w)}
  body{padding-left:calc(var(--side-w) + var(--sp-6))}
  .wrap{max-width:1280px}
}

/* ============================================================
   CARD EDITING — the deadline, the status, the delivery status, the shipping
   date, each station, each preparation task, and the PO file link.
   Present ONLY when the deployment has editing switched on AND the reader's
   role may write the field; otherwise the server sends none of this markup
   (see redact/app.js — the control is a courtesy, the server refusal is the
   enforcement). No script anywhere: native inputs and plain POST forms, the
   same no-JS ethos as the rest of the page.

   EVERY CONTROL SITS BESIDE THE THING IT CHANGES, and each is its own form with
   its own Save: the server writes one cell per request, so one Save per cell is
   the only shape that can tell the truth about what it did.
   ============================================================ */
.jedit{margin:9px 15px 0;padding-top:11px;border-top:1px solid var(--rule2);
  display:flex;flex-wrap:wrap;gap:8px 10px;align-items:center}
.jeditl{font-size:9px;font-weight:750;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink3);flex:0 0 auto}
/* NO PICKER IS STACKED IN A `.jedit` BOX ANY MORE, AND ONLY ONE CONTROL IS STILL A `.jedit`. One box
   held the status and the delivery pickers and another the three preparation ones, which was worth a
   rule and 11px of padding while each panel inside carried a visible header row. None does: every
   panel is opened by the chip, the bar or the tile that shows its state.
   THE TWO DATE FIELDS LEFT THIS SECTION ON 2026-09-11. They were `<input type="date">` + Save, which
   is the one thing on the card the owner came back on — "مابدي يكون هيك التسجيل التاريخ" — and they
   are now calendars, under `THE DATE PICKER` further down. What is left of `.jedit` is the PO file
   link, which is genuinely typed: collapsing a URL into a chip would remove the only way to enter
   one. */
/* 44px SINCE 2026-09-26, AND IT WAS 38 - the floor for anything a finger presses, which this
   control had been under since it was written. It was defensible on a CARD, where it was one of
   two dozen things competing for the height of a list item; §3 moved it into a details panel
   where it is one of three controls on the whole surface, so there is nothing left for it to be
   short on behalf of. Measured at 390 in a real browser, which is where the rule is enforced. */
.jedit input[type=url]{font:inherit;font-size:12.5px;
  color:var(--ink);background:var(--card);border:1px solid var(--rule);border-radius:11px;
  height:44px;padding:0 11px;min-width:0}

/* ---------- THE PICKER ----------
   One panel per writable field. Inside is one row per option, each row a submit button carrying
   that option's value — so a change is ONE TAP, with no select wheel to open and no Save to find
   afterwards.

   A PANEL'S OPENER IS NEVER ON THE PANEL. Every one of the twelve used to draw its own header row —
   the field's name and the chip it currently wears — and that row was the link. All twelve are gone,
   in two goes on 2026-09-11: first the seven stations, then the status, the delivery and the three
   preparation tasks. Each of those rows said in words, 40px lower, what the chip above it already
   said in colour, and the owner asked for the same treatment across the card. THE OPENER IS THE THING
   THAT SHOWS THE STATE — the bar over a station, the delivery chip in the header, a preparation chip,
   the status chip inside the card's own <summary> — so a panel at rest is an empty box with no
   header, and a card at rest is its chips and nothing else.

   THE FOLD IS `:target`, NOT `<details>`, AND THAT IS THE WHOLE MECHANISM OF THIS SECTION.
   The owner taps the coloured bar over CNC and expects CNC's list. A `<details>` cannot be opened
   by a link, so the panel carries an `id` and the bar is an `<a>` at it: the four rules below are
   the entire opening and closing behaviour, with no script on a page whose policy forbids one.

   WHAT CLOSES A PICKER, and there are four ways, all of them the same rule seen from a different
   angle:
     - the CLOSE row, which every panel has open — it goes to the block the panel belongs to,
       `#land-<stem><po>-<kind>`, which is where the chip or the bar that opened it sits;
     - another bar, another chip — a document has ONE :target, so opening the next picker closes
       this one with no extra rule to write;
     - a save, because the 303 lands on that same block anchor;
     - folding the card, which puts the whole panel inside a closed <details>.
   `scroll-margin` is the last part: a fragment jump puts the panel at the very top of the viewport,
   and this keeps the strip or the chip that opened it on screen above it.

   THE ROWS ARE `.pchip`s, deliberately. The preparation chip is already "coloured dot, word,
   tinted pill", which is exactly what an option in this list has to be, and reusing it means the
   row you press looks like the chip you will get. The four extra state names below are the
   station strip's and the delivery chip's own — same tokens, so there is one palette on this card
   and not two that drift.

   NO SCRIPT AND NO POPOVER LAYER: the list is in flow, directly under the chip or the bar that
   opened it, which is what keeps this working under `default-src 'none'`. */
.pick{margin:0;min-width:0;scroll-margin:96px 0 26px}
/* WHERE A SAVE LANDS, AND WHERE "Close" GOES — the block holding the control that was used,
   never the top of a card that can be nine hundred pixels tall. The same scroll-margin as the
   panel above, for the same reason: the masthead is sticky and a bare fragment jump would put
   the block under it. One attribute selector because the ids are generated per field per card
   (see `land` in card.js) and there is no class worth inventing for six of them. */
[id^="land-"]{scroll-margin:96px 0 26px}
/* A CLOSED PANEL IS AN EMPTY BOX, AND THAT IS THE WHOLE SAVING. Its two children — the close row and
   the list — are both out of flow until `:target` names it, and there is no third child left to hold
   a height: the header row that used to be here is gone from all twelve. So `.pick` itself is given
   no padding, no border and no vertical margin, and twelve of them cost a writable card nothing. */
.pickx{display:none;align-items:center;gap:9px;min-height:40px;padding:1px 0;
  text-decoration:none;color:inherit;cursor:pointer}
.pick:target > .pickx{display:flex}
/* WHERE EACH PANEL SITS IS WHERE ITS OPENER IS, and the vertical separation is put on only when the
   panel is OPEN — twelve collapsed panels each carrying a margin would put back most of what taking
   the rows off just saved.
     - the status and delivery panels are direct children of the CARD, so they take the card's own
       15px gutter: the status under the <summary> whose chip opens it, the delivery under `.jdd`;
     - the station and preparation panels are inside their own tinted block and take its padding;
   `.pickx` is every panel's only header, which is why it carries the field's NAME and not just a
   cross: with the rows gone it is the one place an open panel says whose it is. */
.job > .pick{margin:0 15px}
.job > .pick:target{margin:6px 15px 0;padding-top:9px;border-top:1px solid var(--panelRule)}
.jprod > .pick:target,.jprep > .pick:target{margin-top:9px;padding-top:9px;
  border-top:1px solid var(--panelRule)}
.pickl{flex:0 0 84px;font-size:12.5px;font-weight:650;color:var(--ink);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pickx > em{margin-left:auto;font-style:normal;font-size:9.5px;font-weight:750;
  letter-spacing:.11em;text-transform:uppercase;color:var(--ink3)}
/* The cross is drawn by the stylesheet and not written into the markup, for two reasons that agree:
   it is decoration, so it belongs out of the accessible name the word "Close" already provides —
   and a card carries fourteen pickers. Since 2026-09-11 only ONE card on a page carries any (the
   `?open=` card; see `panels` in card.js), so this is paid fourteen times rather than five hundred
   — but it is still markup, and markup is what the DOM is built from whatever the wire costs. */
.pickx::after{content:"\00D7";flex:0 0 auto;display:inline-flex;align-items:center;
  justify-content:center;width:20px;height:20px;font-size:17px;line-height:1;color:var(--ink3)}
.pickx:hover > em,.pickx:hover::after{color:var(--brassInk)}
.prows{display:grid;gap:5px;margin:3px 0 9px;padding:7px;border-radius:14px;
  background:var(--panel);border:1px solid var(--panelRule)}
/* The list exists on every card and is in flow only for the one picker the fragment names. Both
   rules name `.pick` so they outrank `.prows` above, which keeps `display:grid` as the open value
   and `.prows.two`'s columns working inside it. */
.pick > .prows{display:none}
.pick:target > .prows{display:grid;box-shadow:0 0 0 2px var(--brassBg)}
/* The delivery vocabulary is the only seven-entry one, and seven full-width rows is a scroll on a
   phone. Two columns as soon as the card is wide enough to read two labels. */
@container job (min-width:340px){.prows.two{grid-template-columns:1fr 1fr}}
.prow{width:100%;text-align:left;cursor:pointer;min-height:42px;justify-content:flex-start;
  font-size:12.5px}
.prow > span{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.prow > em{flex:0 0 auto;margin-left:auto;text-transform:uppercase;letter-spacing:.08em;
  font-size:9px}
/* The row that is already true: outlined rather than pressable, and it is NOT a button — writing a
   cell the value it already holds costs a round trip and an edit in the sheet's history for
   nothing. */
.prow.now{cursor:default;box-shadow:inset 0 0 0 1.5px currentColor}
/* …and the same line for a cell holding a word none of this app's lists has. It keeps the grey
   dashed "not set" dot, because what it is showing is the cell, not an option. */
.prow.odd{font-style:italic}
.prow.odd > span{font-style:normal}

/* The chip vocabulary, extended to the words the station strip and the delivery chip already use,
   in the colours those already carry. `.pchip` itself paints the dot from `currentColor`. */
.pchip.done,.pchip.dlv{background:var(--okBg);color:var(--ok)}
.pchip.active{background:var(--brassBg);color:var(--brassInk)}
.pchip.rdy{background:var(--shipBg);color:var(--ship)}
.pchip.hold{background:var(--riskBg);color:var(--risk)}
.pchip.iss{background:var(--lateBg);color:var(--late)}
.pchip.na{background:var(--paper2);color:var(--ink2)}
/* STATUS IS THE ONE VOCABULARY WHERE `dlv` IS NOT GREEN. `.stat.dlv` — the chip in the card's own
   header — draws a finished job in neutral grey, because on that row green is spoken for by the
   delivery column. The picker that sets column I has to agree with the chip it sets, summary and
   rows alike, which is why the vocabulary's name is on the panel and not only on the list. */
.pick.status .pchip.dlv{background:var(--paper2);color:var(--ink2)}

/* THE CHIPS THAT ARE ALSO THE WAY IN — the status chip in the card's <summary>, the delivery chip on
   the header row, and each preparation chip the reader may write. They are the same element they
   always were with a fragment on them, so the tint, the box and the row height do not change with the
   role; `.go` adds the press affordance and nothing else, and a reader who may not write that column
   gets the identical span.
   THE STATUS CHIP IS HERE NOW, AND IT IS THE ONE INSIDE THE FOLD CONTROL. A click is dispatched to
   the deepest element under the finger, and DOM's dispatch gives `activationTarget` to the event
   target when that target has activation behaviour — an <a href> does, so the link takes the tap and
   <summary>'s toggle never runs. Measured in Chrome with trusted mouse events before this shipped;
   see `statusChip` in card.js for the measurement and the one residual it leaves.
   THE TWO DATE TILES ARE HERE FOR THE SAME REASON, since 2026-09-11: the red `Deadline · yours`
   half of the header row, and the `Shipping date` row inside the dates fold. Each is the same tile
   or row it was with a fragment on it — `color:inherit` because a tile's text colour is its own
   (`.ddb.own .ddx b` is red) and a link must not repaint it. */
a.stat,a.dstat,a.pchip{text-decoration:none}
a.ddb,a.dline{text-decoration:none;color:inherit}
.stat.go,.dstat.go,.pchip.go,.ddb.go,.dline.go{cursor:pointer;
  -webkit-tap-highlight-color:transparent}
.stat.go:hover,.dstat.go:hover,.pchip.go:hover{box-shadow:0 0 0 1.5px currentColor}
.ddb.go:hover,.dline.go:hover{box-shadow:0 0 0 1.5px var(--brassInk)}
.stat.go:focus-visible,.dstat.go:focus-visible,.pchip.go:focus-visible,
.ddb.go:focus-visible,.dline.go:focus-visible{outline:2px solid var(--brassInk);
  outline-offset:2px}
/* AND ITS SAVE BUTTON WITH IT. The two sit on one row and a 44px box beside a 38px button reads
   as a mistake before it reads as a target that is too small. */
.jedit .chip{height:44px;padding:0 17px;flex:0 0 auto;font-weight:720;
  background:var(--brass);border-color:var(--brass);color:var(--priOn)}
.jedit .chip:hover{filter:brightness(1.06)}

/* THE PO FILE LINK, under the drawing frame. The box takes the row and the Save
   button sits beside it; the note below runs full width, because it is a
   sentence and a sentence squeezed into a flex gap is unreadable. */
.jedit.filelink input[type=url]{flex:1 1 200px}
.jeditn{flex:1 1 100%;font-size:10.5px;line-height:1.45;color:var(--ink3)}

/* ============================================================
   THE PROJECT IMAGE ON THE ORDER CARD, AND THE ONE TAP THAT ASKS
   FIRST — 2026-09-26. Seven selectors, every one of them needing
   `.jpic` or `.jpicc`, which `pictureEditor()` in `views/card.js`
   is the only thing in this tree that emits. Nothing else on this
   card, on `/orders/new` or on any other page moves by a pixel.

   WHY IT IS HERE AT ALL, when the note above says `.jedit` costs
   no CSS. Two reasons, and both are floors rather than taste:

   THE FILE INPUT WAS THE BROWSER'S OWN, unstyled, in a row of
   38px chips — so it read as a foreign control and, on a phone,
   was under the 44px this tree puts under anything a finger
   presses. `.card input[type=file]` two thousand lines up is the
   same treatment for the same reason on the forms; this is that
   rule's shape on the card's own surface.

   AND A BARE `<summary>` IS A LINE OF TEXT. The confirm has to be
   a target before it is a sentence, so it gets the 44px floor and
   the pill the rest of the card is made of. `list-style:none` +
   the WebKit pseudo-element takes the disclosure triangle off in
   both engines; the fold still works with every stylesheet in
   this app blocked, because it is a `<details>` and not a script.

   THE BUTTON WEARS THE RED ONLY WHERE THE ACT IS DESTRUCTIVE.
   `.warn` is added by the view exactly when the order HAS a
   picture, which is the only case where pressing this takes
   something away; on an order with none it is an ordinary brass
   Save, because nothing is being replaced and a red button would
   be a warning about nothing. Same two colours `p.err` uses, so
   no new colour enters the stylesheet.
   ============================================================ */
.jedit.jpic input[type=file]{flex:1 1 200px;min-width:0;min-height:44px;font:inherit;
  font-size:12.5px;line-height:1.6;color:var(--ink);background:var(--card);
  border:1px solid var(--rule);border-radius:11px;padding:9px 10px}
.jedit.jpic input[type=file]::file-selector-button{font:inherit;font-size:12px;font-weight:640;
  margin:0 10px 0 0;padding:6px 11px;border-radius:9px;border:1px solid var(--rule);
  background:var(--paper);color:var(--ink2);cursor:pointer}
.jpicc{flex:1 1 100%;margin:0}
.jpicc > summary{display:inline-flex;align-items:center;min-height:44px;padding:0 15px;
  border:1px solid var(--rule);border-radius:13px;background:var(--paper);color:var(--ink2);
  font-family:var(--ui);font-size:12px;font-weight:700;cursor:pointer;list-style:none}
.jpicc > summary::-webkit-details-marker{display:none}
.jpicc > summary:hover{border-color:var(--brassInk);color:var(--ink)}
.jpicc > p{margin:9px 0 0;font-size:10.5px;line-height:1.45;color:var(--ink3)}
.jpicc > button{font:inherit;font-size:12.5px;font-weight:750;margin:9px 0 0;min-height:44px;
  padding:0 16px;border-radius:13px;cursor:pointer;border:1px solid var(--brass);
  background:var(--brass);color:var(--priOn)}
.jpicc > button.warn{background:var(--lateBg);border-color:var(--late);color:var(--ink)}

/* PREPARATION AND STATIONS, inside their own rounded panels. The label on a
   close row is a task or a station name, and those are longer than `Status`, so
   the column is wider. The picker's own list sits on the CARD's colour rather
   than the panel's, or two tinted boxes end up inside one another. */
.jprep .pickl,.jprod .pickl{flex:0 0 104px}
.jprep .prows,.jprod .prows{background:var(--card);border-color:var(--rule)}

/* ---------- THE DATE PICKER: a month sheet, and every day on it is a save ----------
   The deadline's panel hangs off the card like the status and delivery ones; the shipping date's
   sits inside the date rail, on the rail's own colour, because that is where its row is.

   THE GRID IS ALWAYS 42 CELLS — six rows of seven, Sunday first — which is what makes the two
   `:nth-child` rules below possible: with a fixed count, columns six and seven ARE Friday and
   Saturday in every month, so the weekend is tinted without one byte of markup asking for it. It is
   also what keeps the panel one height: a grid that grew a row between March and April would move
   the Clear row under the reader's thumb mid-navigation. */
.prows.cal{gap:6px}
/* ‹ month › — the arrows are page links (there is no script to swap a grid with), so each is a real
   40px target rather than a glyph. At the edge of the 2024–2028 window there is nothing to link to
   and the arrow becomes a dashed stub: a gap there would let the title slide across the row. */
.cnav{display:flex;align-items:center;gap:6px;min-height:38px}
.cnav > b{flex:1 1 auto;min-width:0;text-align:center;font-size:12.5px;font-weight:750;
  color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cnv{flex:0 0 38px;height:38px;display:inline-flex;align-items:center;justify-content:center;
  border-radius:11px;text-decoration:none;font-size:19px;line-height:1;color:var(--ink2);
  background:var(--card);border:1px solid var(--rule);-webkit-tap-highlight-color:transparent}
.cnv:hover{border-color:var(--brassInk);color:var(--brassInk)}
.cnv:focus-visible{outline:2px solid var(--brassInk);outline-offset:2px}
.cnv.off{color:var(--ink3);background:none;border-style:dashed;font-size:12px}
.cwk,.cald{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:3px}
.cwk > b{text-align:center;font-family:var(--ui);font-size:8.5px;font-weight:700;
  letter-spacing:.04em;text-transform:uppercase;color:var(--ink3);overflow:hidden}
/* One rule for all 42, whatever element the cell turned out to be — a button for a day that can be
   written, a span for the one already written, an `<i>` for a day outside the window. Styling by
   ELEMENT and not by a class on each cell is worth ~500 bytes a calendar, and 42 fewer class
   attributes for the parser to build; see the note on `.pickx::after`. */
.cald > *{display:flex;align-items:center;justify-content:center;min-height:40px;padding:0;
  font:inherit;font-size:12.5px;font-weight:650;color:var(--ink);background:var(--card);
  border:1px solid transparent;border-radius:11px}
.cald > i{background:none}
.cald > button{cursor:pointer;-webkit-tap-highlight-color:transparent}
.cald > button:hover{border-color:var(--brassInk);color:var(--brassInk)}
.cald > button:focus-visible{outline:2px solid var(--brassInk);outline-offset:-2px}
/* Friday and Saturday, by position. The working week here is Sunday to Thursday. */
.cwk > b:nth-child(n+6),.cald > *:nth-child(7n+6),.cald > *:nth-child(7n){color:var(--ink3)}
.cald > *:nth-child(7n+6),.cald > *:nth-child(7n){background:var(--paper2)}
.cald > i:nth-child(7n+6),.cald > i:nth-child(7n){background:none}
/* A day belonging to the month either side of the title — real, pressable, and quieter. */
.cald > .oth{color:var(--ink3);font-weight:500;opacity:.75}
/* TODAY, ringed in the app's accent. */
.cald > .tdy{border-color:var(--brassInk);color:var(--brassInk);font-weight:800}
/* THE DAY THE CELL ALREADY HOLDS: not a button, in the colour that field wears on the card — red
   for his deadline, blue for the shipping date — so the mark in the grid and the tile that opened
   it are the same thing said twice in one colour, not two. */
.cald > .now{font-weight:800;box-shadow:inset 0 0 0 1.5px currentColor;cursor:default}
.pick.dd .cald > .now{background:var(--lateBg);color:var(--late)}
.pick.ship .cald > .now{background:var(--shipBg);color:var(--ship)}
/* The shipping date's panel lives in the rail, which is `--panel`, so its box takes the card's
   colour exactly as the station and preparation panels do. */
.jdates .pick:target{margin:2px 0 0}
.jdates .prows{background:var(--card);border-color:var(--rule)}
/* AND THE RAIL TAKES THE ROOM IT NEEDS WHILE THAT PANEL IS OPEN. On a card wide enough to put the
   drawing beside the dates, the rail is 212–272px — seven columns of about 32px, which is a thumb
   target the owner would miss. Widening the rail for as long as its picker is targeted takes the
   cells to ~50px and gives the drawing back its width the moment the panel closes; nothing has to
   be reverted, because the row is a flex box and `.shot` already takes what is left. Measured on a
   644px card: 32px → 51px per day. */
@container job (min-width:470px){
  .jshow:has(.pick:target) > .jdates{flex:0 0 clamp(320px,62%,440px);
    width:clamp(320px,62%,440px)}
}

/* ============================================================================================
   THE CRM PAGE: INDUSTRY GROUPS, AND A CORRECTION THIS APP HOLDS.
   ============================================================================================
   FOUR RULES, AND THREE OF THEM ARE ADJUSTMENTS TO THINGS THAT ALREADY EXIST. The client list, the
   client page and both of its correction panels are drawn out of the vocabulary above - `.rows`,
   `.row`, `.k`, `.v`, `a.qopen`, `.tchev`, `.pick`, `.pickx`, `.prows`, `.pchip`, `.prow`, `.stat`,
   `.empty`, `.costnote` - because a client row, an RFQ row and a Numbers row are one shape carrying
   different cells. What is genuinely new is a <summary> used as a list row and a box to type in
   inside a picker panel.

   THE GROUPS ARE <details> AND NOT `:target`, which is the opposite of the card's pickers and is
   the right way round for this one reason: a document has exactly ONE target, so `:target` groups
   would close each other, and the whole point of grouping 469 clients is that a reader can open
   Retail and Signage and compare them. The pickers want mutual exclusion; a list of folds does not.
   Neither needs a script. */

/* A GROUP HEADING IS A `.row` THAT OPENS. `summary{cursor:pointer;list-style:none}` and the marker
   suppression are already global, and `details[open] > summary .tchev` already turns the chevron,
   so this is the two things a heading needs that a row does not: the interface face, and a tint
   while it is open so the boundary between one group's rows and the next is visible while scrolling.
   The first heading loses its rule for the same reason `.row:first-child` does - it sits against the
   surface's own border. */
.rows > details:first-child > summary{border-top:0}
.rows > details > summary{font-family:var(--ui);font-weight:700}
.rows > details[open] > summary{background:var(--paper2)}

/* THE BOX INSIDE A CORRECTION PANEL. `.card input` is the same shape and is scoped to a form card;
   a panel is not one, and widening that selector would restyle every picker in the app. `min-width:0`
   because `.prows` is a grid and an input's default intrinsic width would otherwise push the panel
   wider than the row it hangs under. */
.prows .ptxt{width:100%;min-width:0;font:inherit;font-size:15px;padding:10px 12px;
  border-radius:11px;border:1px solid var(--rule);background:var(--card);color:var(--ink)}

/* ==============================================================================================
   THE DROP ZONE AND THE UPLOAD BAR - 2026-09-18.

   EVERY SELECTOR BELOW NAMES A CLASS NOTHING ELSE IN THIS APP USES, and every one of those classes
   is created by `views/upload.js` at run time rather than rendered by a view. That is the whole of
   why this block cannot reach another page: with the script blocked not one of these elements
   exists, so not one of these rules matches anything. The diff that added it is insertions only -
   no existing selector, token, theme block, media query or `:root` entry was touched.

   THERE IS NO `.dz` ON A PHONE, and that is not done here. A media query hiding the zone would
   still have put the element in the document, where a screen reader would read a drop target that
   cannot be dropped on; `roomToDrop()` in `upload.js` decides whether the element is MADE at all,
   and it is the same `(min-width:768px) and (min-height:600px)` written once, in JavaScript, where
   the decision is. Duplicating it here would be a second place for it to be wrong.
   ============================================================================================ */

/* THE ZONE. It sits directly under the picker it belongs to and never in place of it - `.card
   input[type=file]` is untouched above and is still the first control in the tab order.

   44px IS THE FLOOR AND THIS IS WELL OVER IT. A drop target that is also tappable (it opens the
   picker) has to be a real touch target on the iPad it is drawn for; `min-height:96px` is the
   smallest that still reads as an area rather than as a button. */
.dz{margin:9px 0 0;padding:var(--sp-4);min-height:96px;display:flex;flex-direction:column;
  gap:var(--sp-2);justify-content:center;cursor:pointer;
  border:2px dashed var(--rule);border-radius:var(--r-lg);background:var(--surface2);
  transition:border-color .14s ease,background .14s ease}
.dz:hover{border-color:var(--priRule);background:var(--paper2)}
/* WHILE A FILE IS OVER IT. The only state this zone has, and it is set by the drag handlers rather
   than by `:hover` - a drag does not hover. */
.dz.on{border-color:var(--priInk);background:var(--priBg);border-style:solid}

/* "Drop files here", and the types after it. Two weights on one line, because the owner has asked
   seven times for less explanatory text and this is the least that still says what the zone takes. */
.dzh{margin:0;font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-semi);
  color:var(--ink2);text-align:center}
.dzk{display:block;margin-top:2px;font-weight:400;font-size:var(--t-micro);
  letter-spacing:.06em;text-transform:uppercase;color:var(--ink3)}

/* WHAT IT IS HOLDING - one line per file, the name on the left and the size hard right. No count,
   no total, no instruction. `min-width:0` + `overflow:hidden` so a long filename shortens instead
   of pushing the size off the card. */
.dzl{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:2px}
.dzl li{display:flex;align-items:center;gap:var(--sp-2);min-width:0;
  padding:7px var(--sp-3);border-radius:var(--r-sm);background:var(--card);
  border:1px solid var(--line2);
  font-family:var(--ui);font-size:var(--t-xs);color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dzs{margin-left:auto;flex:0 0 auto;color:var(--ink3);font-variant-numeric:tabular-nums}

/* A FILE OVER THE CEILING THE SERVER PUT ON THE PAGE. IT IS A MARK AND NOT A REFUSAL: there is no
   sentence here, nothing is disabled, and the file is still in the input. It says only that this
   size is over that number, both of which came off the server - and when the press comes,
   `upload.js` hands it to the browser and the server writes the refusal in its own words. */
.dzl li.over{border-color:var(--late);background:var(--lateBg)}
.dzl li.over .dzs{color:var(--late);font-weight:var(--w-semi)}

/* THE X THAT TAKES ONE FILE BACK OFF - 2026-09-22, and the owner's own words for why it is here:
   "اذا بدي الغي يلي رفعتو، مافي اشارة سلة او X اقدر الغي". There was no way to unchoose a file
   short of starting the pick over, which on a list of five is four files re-chosen to lose one.

   THE CROSS IS THIS RULE AND NOT A GLYPH, and that is deliberate rather than clever. `upload.js`
   creates the button with NO CHILD NODES AT ALL - two rotated bars out of `::before`/`::after`
   are the whole icon - so three things stay true at once: the script builds no markup from a
   string (it has never had an `innerHTML` and this did not give it one), the row's `textContent`
   is STILL the file's name and its size and nothing else, which is what the oversize mark is read
   out of, and the accessible name is the button's `aria-label` - "Remove <the file's name>" -
   rather than a word sitting in the row a reader has to skip past. No sentence anywhere.

   44px IS THE FLOOR AND THIS IS EXACTLY IT, on the control most likely to miss it: a 16px cross
   at the end of a 30px row is the thumb target that gets this wrong everywhere. The negative
   margins spend the row's own 7px of padding instead of adding to it, so the button's box is a
   full 44 while the row grows to 44 and not to 58 - and it stays inside the li's border box, so
   `overflow:hidden` above has nothing to clip.

   EVERY DECLARATION AFTER `background` IS AN UNDO OF `.card button`, which reaches this element
   because these lists are drawn inside cards: an 18px top margin, 12/18 padding and the primary
   gradient would have made a submit button out of a remove icon. Two classes beat one class and
   one type, so this wins without an `!important` and without touching the rule above it.

   NO `:focus-visible` OF ITS OWN. The stylesheet has one at the top for every focusable thing on
   every page, and a second here would be a second place for the focus ring to be wrong. */
.dzl .dzx{position:relative;flex:0 0 auto;
  width:44px;min-width:44px;height:44px;min-height:44px;
  margin:-7px calc(var(--sp-3) * -1) -7px 0;padding:0;
  display:inline-flex;align-items:center;justify-content:center;
  border:0;border-radius:var(--r-sm);background:none;color:var(--ink3);
  cursor:pointer;-webkit-appearance:none;appearance:none}
.dzl .dzx::before,.dzl .dzx::after{content:"";position:absolute;
  width:13px;height:2px;border-radius:1px;background:currentColor}
.dzl .dzx::before{transform:rotate(45deg)}
.dzl .dzx::after{transform:rotate(-45deg)}
.dzl .dzx:hover{color:var(--ink);background:var(--paper2)}
/* ON A ROW THAT IS ALREADY MARKED, the X takes the mark's colour - it is the control that answers
   the mark, and a grey X on a red row reads as the one part of it that is not actionable. */
.dzl li.over .dzx{color:var(--late)}

/* ----------------------------------------------------------------------------------------------
   THE BAR. Appended to the form when the bytes start moving and taken away if the form is handed
   back to the browser.
   ------------------------------------------------------------------------------------------- */
.upw{margin:var(--sp-4) 0 0;display:flex;flex-direction:column;gap:var(--sp-2)}

/* A REAL `<progress>`, WHICH IS WHY THERE ARE THREE RULES FOR ONE ELEMENT. Chrome and Safari paint
   the bar with `::-webkit-progress-bar` / `::-webkit-progress-value`, Firefox with
   `::-moz-progress-bar`, and neither vendor reads the other's. `appearance:none` is what stops the
   platform's own lozenge being drawn underneath.

   IT IS THE BROWSER'S CONTROL AND NOT A DIV WITH A WIDTH, because the width would have had to be
   an inline style and the policy is `style-src 'self'` with no `unsafe-inline` - and because a
   `<progress>` announces itself to a screen reader with its value and needs no ARIA written here. */
.upm{appearance:none;-webkit-appearance:none;display:block;width:100%;height:10px;border:0;
  border-radius:var(--r-pill);background:var(--paper2);overflow:hidden}
.upm::-webkit-progress-bar{background:var(--paper2);border-radius:var(--r-pill)}
.upm::-webkit-progress-value{background:var(--priInk);border-radius:var(--r-pill);
  transition:width .12s linear}
.upm::-moz-progress-bar{background:var(--priInk);border-radius:var(--r-pill)}

/* "148.2MB of 201.4MB" - the figures off the socket. Tabular so the digits do not jitter as they
   climb, which on a four-minute upload is the difference between a bar you trust and one you watch. */
.upn{margin:0;font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-semi);
  color:var(--ink);font-variant-numeric:tabular-nums}

/* THE UNWELCOME LINE, AND IT IS DELIBERATELY QUIET. It is true for the whole four minutes and a
   reader should be able to stop reading it after the first time; drawn at banner weight it would
   read as something going wrong. */
.upr{margin:0;font-family:var(--ui);font-size:var(--t-xs);color:var(--ink3)}

/* THE UPLOAD DIED. The same red box `p.err.efld` is, with its own class because the sentence in it
   is this file's own and not the server's - see `lost()` in `upload.js`. Kept in step with
   `p.err.efld` above by eye and by the walk; it is deliberately not a shared selector, so that
   nobody can later widen one and silently move the other. */
.upe{background:var(--lateBg);border:1px solid var(--late);color:var(--ink);
  padding:9px 12px;border-radius:13px;margin:0;font-size:12.5px}

/* =============================================================================================
   ADD CLIENT, 2026-09-21 - and what is left of its block after the merge.

   Two redesigns landed the same day. Both copied `.csec > .qsh` and `.rq` - the tinted section
   header and the red asterisk - so that whichever merged second could delete its own copy and
   move nothing. That is what happened: those two rules are in the New creative request block
   above, byte for byte, and only the thirteen selectors this page alone invented are here.

   EVERY ONE IS SCOPED TO A CLASS THIS PAGE INVENTED. No new card, no new input rule, no new
   label rule, no new button rule, no second grid, no breakpoint. The stylesheet is served at a
   content-addressed `immutable` URL and is sha-pinned in eleven test files - one byte is a fresh
   ~92KB download on every phone on shop-floor wifi - so a redesign that could be composed was
   composed.
   ============================================================================================= */
.qsx{margin-left:auto;font-family:var(--ui);font-size:var(--t-xs);font-weight:var(--w-med);
  letter-spacing:0;color:var(--ink3);white-space:nowrap}
/* THE STATUS DOT - his "small green status dot". `--ok` is the existing green.
   IT IS POSITIONED AGAINST `.sdw` AND NOT AGAINST THE PAGE, which is the whole of why `.sdw`
   exists. An `position:absolute` element whose ancestors are all static resolves against the
   INITIAL CONTAINING BLOCK - it leaves the card, leaves the column, and at 390px it made the
   document 609px wide. Measured, not reasoned about: see the walk in `client-add.test.js`. */
.sdw{position:relative;display:block}
.sdot{position:absolute;right:32px;top:50%;margin-top:-4px;width:8px;height:8px;
  border-radius:999px;background:var(--ok);pointer-events:none}
/* THE SEGMENTED RADIO GROUP. `.seg` draws this shape already but is built of links, because
   choosing a theme is navigation; choosing a contact method is one answer on a form that is
   posted once. The radio is `.vh` - visually hidden, NOT `display:none` - so it is still
   focusable, still announced, and still posts when the stylesheet never loads. */
/* `10px` SINCE 2026-09-21, THE SAME NUMBER A FIELD LABEL NOW CARRIES. This group IS a field - a
   label over a control - and 16px above it read as a gap in a rhythm rather than as a group. */
.smeth{margin:10px 0 0}
.smel{display:block;margin:0 0 4px;font-family:var(--ui);font-size:10px;font-weight:750;
  letter-spacing:.09em;text-transform:uppercase;color:var(--ink3)}
.smg{display:inline-flex;flex-wrap:wrap;gap:2px;padding:3px;border:1px solid var(--line);
  border-radius:var(--r-md);background:var(--surface);max-width:100%}
/* `position:relative` IS LOAD-BEARING AND NOT TIDINESS. The radio inside is `.vh`, which is
   `position:absolute` - with no positioned ancestor it resolves against the initial containing
   block, escapes the card entirely and widens the DOCUMENT. At 390px the three radios in §2 made
   the page 609px wide and it scrolled sideways. Measured; see `client-add.test.js`. */
/* `44px` SINCE 2026-09-21. 38 was under the floor for something a finger presses, and the group
   is SHORTER at 44 than it was at 38 - the 19px of `.card label` margin each pill was carrying
   comes off in the density block at the foot of this file, which is where a rule that has to beat
   `.card label` can be written at a specificity that actually does. */
.smi{position:relative;display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;padding:0 14px;
  border-radius:var(--r-xs);font-family:var(--ui);font-size:var(--t-sm);
  font-weight:var(--w-semi);color:var(--ink3);cursor:pointer;white-space:nowrap}
.smi:hover{color:var(--ink)}
.smi:has(input:checked){background:var(--pri);color:#fff}
.smi:has(input:focus-visible){outline:2px solid var(--cool);outline-offset:2px}
/* THE CHECKBOX GRID - his multi-select, as seven visible tap targets rather than a scrolling
   `<select multiple>` nobody can operate on a phone. The box is a real checkbox, left alone. */
.cbxs{display:grid;gap:var(--sp-2) var(--sp-4);
  grid-template-columns:repeat(auto-fit,minmax(11rem,1fr))}
/* `44px` SINCE 2026-09-21 AND FOR `.smi`'s REASON: the whole row is the target - it is a `<label>`
   for the box beside it - and 40 was under the floor. The row is still SHORTER than it was, because
   the 19px of `.card label` margin it was carrying comes off in the density block below. */
.cbx{display:flex;align-items:center;gap:var(--sp-2);min-height:44px;font-size:var(--t-sm);
  color:var(--ink);cursor:pointer}
.cbx > input{width:18px;height:18px;min-height:0;flex:0 0 auto;margin:0;accent-color:var(--pri)}
/* ONE EXTRA CONTACT - a panel inside the card, the same surface `.prows` uses for the same job. */
.ctc{margin:var(--sp-4) 0 0;padding:var(--sp-3) var(--sp-4) var(--sp-4);border-radius:var(--r-md);
  background:var(--panel);border:1px solid var(--panelRule)}
.ctch{display:flex;align-items:center;gap:var(--sp-3);margin:0;font-family:var(--ui);
  font-size:var(--t-xs);font-weight:var(--w-bold);letter-spacing:var(--ls-caps);
  text-transform:uppercase;color:var(--ink3)}
.ctcx{margin-left:auto;font-family:var(--ui);font-size:var(--t-xs);font-weight:var(--w-semi);
  letter-spacing:0;text-transform:none;color:var(--late)}

/* =============================================================================================
   THE DENSITY PASS, 2026-09-21 - THE TWO FORMS AT THE RATIO HE ASKED FOR, AND NOTHING ELSE MOVED.

   He opened `/clients/new` on a 1900px window and said "can you make it a little smaller the
   ratio so the fields and info visual better". What he was looking at, measured: a 1252px card in
   which a CLIENT CODE box was 599px wide, 46px controls, a label block costing 33.5px before the
   box it names, a 53px header strip and 24px between cards. Section 1 alone was 418.8px and
   exactly ONE of the five sections fitted above the fold.

   A LITTLE SMALLER, AND THE NUMBERS ARE HIS OWN. `views/client-new.js` records his spec for this
   page as "40-46px controls" - so 40 is the bottom of the range he wrote, not a new opinion, and
   the 44px floor is kept for everything a finger PRESSES: `.card button` is 46, `.qfoot > button`
   is 48, and `.smi` went UP to 44 in this same pass. No font, no colour, no radius, no border and
   no weight is touched here; this is spacing and one width.

   THE WIDTH IS THE ONE HE COMPLAINED ABOUT AND IT IS A NUMBER THIS APP ALREADY SHIPS. At 1280 the
   form is 1012px and each half of a `.nogrid` pair is 479px, which is the layout that has been in
   front of him since the page shipped and is not what he wrote in about. `63.25rem` is exactly
   that 1012px, so from 1280 up the page is the 1280 page and nothing below 1280 moves at all. It
   is left-aligned and not centred because `.card.wide` - `/quotations/new`, `/admin/document` -
   has been left-aligned under a left-aligned page header since this stylesheet had a card in it.

   EVERY SELECTOR IS SCOPED TO `.cform` OR `.csec`, WHICH ONLY THESE TWO PAGES CARRY.
   `.card`, `.card label`, `.card input`, `.card button`, `.nogrid`, `.qsec`, `.qsh`, `.qfoot` and
   `section` are shared with Orders, Quotations, RFQs, Cost Review and the creative record pages,
   and not one of them is edited - which is why `.qsec.csec` and `.qsec.csec label` are written at
   two classes rather than one: they have to BEAT `.card`'s rules on this page without being able
   to reach any other page, and a specificity tie decided by source order is not a thing to leave
   a later reader to work out.

   `/rfqs/new` CARRIES `.qform` AND `.card widest qsec` AND NEITHER `.cform` NOR `.csec`, so it is
   untouched here - asserted by rendering it either side of this change, not by eye.
   ============================================================================================= */
/* THE CAP. On the FORM and not on the card, so the footer under the five sections is capped with
   them rather than stretching out from under the last one. */
.qform.cform{max-width:63.25rem}
/* THE CARD. 18/18/22 -> 14/16/16, and `margin-bottom` takes `section`'s own 24px down to 16.
   `.csec > .qsh` NEGATES THIS PADDING to bleed the tinted strip to the card's edges, so the two
   are one measurement in two places; if this moves, that moves. */
.qsec.csec{padding:14px 16px 16px;margin-bottom:var(--sp-4)}
/* `.qsec > .qsh + .nogrid` GIVES THE FIRST ROW 16px AGAINST A 28px TILE. The tile is still 28px
   and still needs clearing, but the strip below it now carries 8px of its own and the label
   inside the row carries 10, which is the same 18px answer the `.qsh + label` case reaches
   through the label rule below. Without this the two cases would be 18px and 34px apart. */
.qsec.csec > .qsh + .nogrid{margin-top:0}
/* THE LABEL BLOCK - 14/5 -> 10/4, which is the biggest single win because it is paid once per
   FIELD. The type is untouched: still 10px, still 750, still `.09em`, still uppercase.
   `.cbx` AND `.smi` ARE LABELS TOO and are excluded here: both are items of a box that already
   spaces itself (a grid `gap`, an `inline-flex`), and both now set `margin:0` at their own rules
   above. Giving them 10px would have put the margin straight back. */
.qsec.csec label:not(.cbx):not(.smi){margin:10px 0 4px}
/* AND THE TWO LABELS THAT ARE NOT FIELD LABELS GET NO MARGIN AT ALL - a checkbox in `.cbxs` and
   a pill in `.smg`. Both sit in a box that already spaces its own children (a grid `gap`, a
   flex `gap` over 3px of padding), and `.card label`'s `14px 0 5px` was being paid on top of it:
   19px on every row of the services grid, and 19px of height on a group of three pills that is
   supposed to be exactly as tall as one of them.
   THE SELECTOR IS THREE CLASSES BECAUSE IT HAS TO BE. `.cbx` alone is ONE class and `.card label`
   is a class AND a type, so `.cbx{margin:0}` at its own rule loses - which is how the first cut
   of this pass shipped a `margin:0` that changed nothing at all, found by measuring the group and
   not by reading the rule. */
.qsec.csec .cbx,.qsec.csec .smi{margin:0}
/* THE ONE BUTTON INSIDE A SECTION - `Propose a code`. `.card button`'s 18px top margin is
   measured against a stacked form where the button is the last thing on the card; here it is
   a field's own action, sitting under the box it fills in. The 46px target is untouched. */
.qsec.csec button{margin-top:10px}
/* THE CONTROL - 46 -> 40, the floor of the range his own spec names. Checkboxes and radios are
   excluded by attribute: `.cbx > input` is an 18px box and `.smi`'s radio is `.vh`, and a
   `min-height` on either is how this rule would silently break two controls to tighten a third.
   `textarea` keeps `.card textarea`'s 92px - it is one box per page, it is the box a brief is
   typed into, and three lines is what the note on that rule says it is for.
   `8px` AND NOT `9px`, WHICH IS A MEASUREMENT AND NOT A ROUND NUMBER. `.card input` is
   `font:inherit`, so an input inherits the body's 1.45 line-height as well as its size, and its
   content box is 21.75px rather than 18. At 9px a side the box computed to 41.8 and the
   `min-height` never applied at all; at 8px it computes to 39.75 and the 40 is what a reader
   gets. Measured, twice, because the first number was reasoned about. */
.qsec.csec input:not([type=checkbox]):not([type=radio]),.qsec.csec select{min-height:40px}
.qsec.csec input:not([type=checkbox]):not([type=radio]),.qsec.csec select,
.qsec.csec textarea{padding-top:8px;padding-bottom:8px}
/* =============================================================================================
   NEW QUOTATION, 2026-09-21 - three cards, one inline line table, and a footer.

   THE PAGE IS COMPOSED AND NOT INVENTED, which is the same account `/creative-request/new` gives
   one block up and for the same reason: this stylesheet is served at a content-addressed
   `immutable` URL and is sha-pinned in eleven test files, so a byte added here is a fresh ~93KB
   download on every phone on shop-floor wifi. `/quotations/new` therefore wears `.qform`,
   `.cform`, `.card widest qsec csec`, `.qsh`, `.qsn`, `.nogrid`, `.smg`, `.smi`, `.ficon`, `.rq`,
   `.hint`, `.qcount`, `.qmt`, `p.err.efld` and `.qfoot` - every one of them already in this file
   and four of them tuned by the owner's own density pass on the 21st. IT IS THE THIRD PAGE TO
   CARRY `.cform` AND `.csec`; the density block above names two, which was true when it was
   written and is the sentence this page is deliberately joining rather than working around.

   WHAT IS BELOW IS THE ONE THING WITH NO EXPRESSION ANYWHERE IN THIS FILE: a line of a quotation
   as a ROW OF LIVE BOXES nine columns wide. `.qtab`/`.qln` further up is the Create RFQ table and
   is NOT that - it is a row you READ with its editor behind a tap, which is the right answer for a
   page where nothing is priced and the wrong one here. The owner's instruction for this page is
   "compact and inline, no modal, no giant card": a person pricing a quotation is at a desk typing
   a figure into every line as they go, and a fold between them and the box is a tap per line for
   nothing - and it hides the TOTAL, which is the number they are watching.

   ==============================================================================================
   EVERY ROW RULE IS WRITTEN `.qlt .qline` AND THAT IS NOT DECORATION. `.qline` IS AN OLD CLASS.

   It has meant "one line of the quotation form, a bordered block with its number absolutely
   positioned into the top-right corner" since the form shipped, and `summary.qline` on the
   quotation RECORD page still leans on that block's border, tint and radius for the PRICING fold -
   see the two rules about 1,100 lines above. Renaming the row would have been the tidy move and
   is the wrong one twice over: it would take the badge off the record page's fold, and
   `rfq-create.test.js` counts `class="qline"` on THIS page against `class="qln"` on Create RFQ to
   hold the two forms to the same number of lines, which is a claim worth keeping.

   So the row keeps the name and this block out-specifies the old rules inside the table: `.qlt
   .qline` is two classes against one, `.qlt .qline > .qlnum` is three against `.qline > .qlno`'s
   two, and the number element is `.qlnum` rather than `.qlno` because that one is absolutely
   positioned and a `position:static` taking it back would be a rule fighting a rule. NOTHING
   OUTSIDE `.qlt` IS TOUCHED: the record page's fold is byte-identical.
   ============================================================================================= */

/* ----------------------------------------------------------------------------------------------
   THE STACKED SHAPE IS THE DEFAULT AND THE TABLE IS THE ENHANCEMENT, which is `.qtab`'s own order
   and the only one that is honest on a phone: a nine-column table is not something a 390px screen
   can be talked into, so what it gets is the same markup laid out as labelled fields, in the
   order his §34 gives - category, item, qty, unit price, the computed total, the brief, then the
   two controls.
   ------------------------------------------------------------------------------------------- */
.qlt{margin-top:var(--sp-3);border:1px solid var(--rule);border-radius:var(--r-lg);
  background:var(--card)}
/* THE HEADING ROW IS OFF UNTIL THERE IS A TABLE FOR IT TO HEAD. Nine headings over a stacked
   block would be labelling positions that are not there - `.qth`'s own reason. */
.qlh{display:none}
/* THE ROW. Half of this is TAKING THE OLD `.qline` BLOCK BACK - its `position:relative`, its
   12px top margin, its 16px radius, its border and its tint - because inside this table a line is
   a row of a surface and not a card of its own. See the header of this block. */
.qlt .qline{position:static;display:grid;gap:0 var(--sp-3);margin-top:0;padding:var(--sp-3);
  border:0;border-top:1px solid var(--rule2);border-radius:0;background:transparent;
  grid-template-columns:repeat(auto-fit,minmax(9.5rem,1fr))}
.qlt .qline:first-of-type{border-top:0}
.qlt .qlc{min-width:0;display:flex;flex-direction:column;justify-content:flex-end}
/* `.qline label{margin-top:10px}` IS THE OLD BLOCK'S AND IS MEASURED AGAINST A STACK OF FIELDS.
   In a cell the label is the cell's first child and the gap is the grid's. */
.qlt .qline label{margin-top:0}
/* THE NUMBER. Its own strip across the top of a stacked line, and a 26px cell in the table - the
   same element either way, because a second markup path for a phone is a second thing to keep
   true. It is the row's only drawn-not-typed fact: the position in the form, renumbered from 1 on
   the way in, so a line left blank in the middle leaves no hole in what the client reads. */
/* `justify-content` IS RESTATED AND IT IS NOT REDUNDANT: `.qlc` is a COLUMN flex justified to
   `flex-end` so that a label and its box sit on the cell's baseline, and turning this one cell
   into a ROW without restating it right-aligns the number - which is where the first render of
   the stacked shape put every one of them. */
.qlt .qline > .qlnum{grid-column:1/-1;flex-direction:row;align-items:center;
  justify-content:flex-start;min-height:26px;
  font-family:var(--ui);font-size:var(--t-xs);font-weight:var(--w-heavy);color:var(--ink3)}
/* AND IN THE STACK THE NUMBER SAYS WHAT IT IS. There is no heading row under 900, so a bare `2`
   over a column of labelled boxes is the one mark on the page a reader has to guess at. In the
   table the `#` is the column heading and repeating it in every cell would be the word twice. */
.qlt .qline > .qlnum > b::before{content:"#"}
/* THE COMPUTED TOTAL. A `<span>` and never an input, which is the whole of the rule it draws:
   Qty x Unit Price is arithmetic this app owns and not a figure anybody types. It wears the box's
   shape so the column lines up with the two live boxes beside it, and the muted surface is what
   says it is read-only without a `disabled` attribute nobody can see the effect of. */
.qlsum{display:flex;align-items:center;justify-content:flex-end;min-height:40px;
  padding:8px 12px;border-radius:12px;border:1px solid var(--rule);background:var(--paper2);
  color:var(--ink3);font-variant-numeric:tabular-nums;font-size:var(--t-sm);
  font-weight:var(--w-semi);overflow-wrap:anywhere}
/* `.qmt` COMES BACK, 2026-09-22, AND THE MERGE IS WHY IT HAD TO. It is the em dash a figure that
   does not exist yet is drawn as - the line total and the running total, both of which say "the
   two boxes on this line do not parse, so there is no amount" rather than guessing one. The rule
   had lived at the foot of Create RFQ's old fold-per-line block since that block existed, and it
   went with it when the 2026-09-21 redraw deleted the block whole; the quotation form was being
   rebuilt against the OLD stylesheet at the same hour and reached for it. A `<em>` with no rule
   behind it renders italic and in body colour, which reads as emphasis rather than as absence.
   `font-style:normal` because these are `<em>` for weight of meaning, not slant. */
.qmt{font-style:normal;color:var(--ink3)}
/* WHERE A LINE'S FILES GO, AS A FACT AND NOT AS A CONTROL. There is no RFQ Number until this form
   is saved - both identifiers are minted by the POST - so nothing on this page can carry a file to
   a record that does not exist yet. A button here would be a control that lies; `views/rfq-new.js`
   answers the identical question with the identical sentence one page over. */
.qlt .qlat{flex-direction:row;align-items:center;justify-content:flex-start;gap:6px;
  min-height:40px;font-family:var(--ui);font-size:var(--t-xs);color:var(--ink3)}
.qlat > svg{flex:0 0 auto;width:15px;height:15px}
.qlat > em{font-style:normal;overflow-wrap:anywhere}
/* THE TWO-TAP REMOVE, AS A BIN AND A CONFIRM. Both taps are the browser's own - a `<summary>`
   that asks and a real submit button that answers - which is `removeLine`'s written-down rule
   since the form shipped and is not relaxed by the row getting narrower. What changed is only
   where the second tap is DRAWN: in a 56px column the confirm cannot sit under the word, so it is
   anchored to the cell and grows leftwards, inside the card and never past its edge. */
.qlt .qlac{flex-direction:row;align-items:center;justify-content:flex-start;gap:var(--sp-1)}
.qlrm{position:relative}
.qlrm > summary{list-style:none;display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;border-radius:var(--r-sm);color:var(--ink3);cursor:pointer}
.qlrm > summary::-webkit-details-marker{display:none}
.qlrm > summary::marker{content:""}
.qlrm > summary:hover{color:var(--late);background:var(--paper2)}
.qlrm > summary:focus-visible{outline:2px solid var(--cool);outline-offset:-2px}
.qlpop{position:absolute;right:0;top:calc(100% + 4px);z-index:4;min-width:13.5rem;
  padding:var(--sp-2);border:1px solid var(--rule);border-radius:var(--r-md);
  background:var(--card);box-shadow:var(--shadow)}
/* `.card button` PAINTS EVERY BUTTON IN A CARD NAVY WITH 18px OF TOP MARGIN and `.qsec.csec
   button` takes that to 10 - both right for a button a card ends in and wrong for a confirm
   inside a popover and for a 44px `+` in a table cell. THREE CLASSES, because `.qsec.csec button`
   is two classes and an element and a tie decided by source order is exactly what the note beside
   `.qrm > button` records having got wrong once already. */
.qlt .qlrm > .qlpop > button{display:block;width:100%;margin-top:0;min-height:44px;
  padding:0 var(--sp-3);background:none;border:0;text-align:left;font-family:var(--ui);
  font-size:var(--t-xs);font-weight:var(--w-semi);color:var(--late)}
.qlt .qlrm > .qlpop > button:hover{background:var(--paper2)}
/* THE `+`. It is a real submit - `name="more" value="1"` - so it costs a round trip and works
   with every script on this page blocked, which is the page's whole claim. The server answers it
   with the line kept, a fresh empty line under it and `autofocus` in that line's first box. */
.qlt .qlac > .qladd{display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;margin-top:0;padding:0;border-radius:var(--r-sm);
  background:var(--priGrad);color:var(--priOn);font-size:20px;font-weight:var(--w-bold);
  line-height:1;border:1px solid transparent}
.qlt .qlac > .qladd:hover{filter:brightness(1.12)}
/* THE LINE THAT IS NOT A LINE YET. One tint so the eye can tell "type here" from "this is filed",
   and it is the row's own background rather than a per-cell one so it survives the stack. */
.qlt .qline.qlnew{background:var(--paper2);border-radius:0 0 15px 15px}
.qlt > .qline.qlnew:first-of-type{border-radius:15px}
/* ==============================================================================================
   `/orders/new`'s PROJECT REQUIREMENTS - 2026-09-25, and it is the only block `.qlt` has ever
   needed scoping for.

   EVERYTHING ABOVE THIS IS SHARED WITH `/quotations/new` AND STAYS SHARED. The surface, the row,
   the cell, the stacked-to-table switch, the bin and its popover are all `.qlt`'s and not one of
   them moved. What `.oreq` adds is the three things a REQUIREMENTS row is that a quotation line
   is not: five tracks instead of nine, a drawn TYPE instead of a typed one, and an add control
   that lives under the table rather than in a cell.

   THE NUMBER COLUMN IS OFF IN THE TABLE SHAPE AND ON IN THE STACK, which is his mock-up read
   exactly: he draws TYPE / ITEM OR SERVICE / QTY / UNIT / REMOVE and no `#`. It is `display:none`
   and never removed from the markup - at 390 there is no heading row, so the number is the only
   thing saying which line a stack of boxes belongs to, and `views/lines.js` reads its text to
   know which row it is holding. A column dropped from a design is not a fact dropped from a page.
   ============================================================================================== */
.oreq .qlkc{justify-content:center}
/* THE BADGE. Restrained, which is his word: a tinted pill with the row's own glyph, no state, no
   tick and nothing that could be read as "we have it" - `views/card.js`'s own rule for these
   chips, kept on the form they are typed on. It is NOT a control and takes no pointer: the type
   is decided by the button that added the row. */
.oqb{display:inline-flex;align-items:center;gap:6px;min-height:34px;padding:0 10px;
  border-radius:var(--r-pill);border:1px solid var(--rule);background:var(--paper2);
  font-family:var(--ui);font-size:var(--t-xs);font-weight:var(--w-bold);color:var(--ink2)}
.oqb > svg{flex:0 0 auto;width:14px;height:14px}
.oqb > em{font-style:normal;white-space:nowrap}
/* TWO TONES AND THEY ARE THE TWO THIS APP ALREADY USES FOR THESE TWO IDEAS. `--ok` is the costing
   workspace's MATERIAL dot and `--risk` is its OUTSOURCE dot (`.cwdot.cwmat`, `.cwdot.cwout`), so
   a reader who has learnt the colours on one page meets them on the other. Tinted background and
   coloured ink rather than a filled pill - a solid block of colour per row reads as a status. */
.oreqln[data-kind="material"] .oqb{border-color:var(--ok);color:var(--ok);
  background:color-mix(in srgb, var(--ok) 12%, transparent)}
.oreqln[data-kind="outsource"] .oqb{border-color:var(--risk);color:var(--risk);
  background:color-mix(in srgb, var(--risk) 12%, transparent)}

/* AND THE SAME TWO MARKS ON THE ORDER CARD, WHERE THESE ROWS ARE READ - 2026-09-25.
   `views/card.js`'s `materialsRow` draws one `.pchip.none` per line and the panel used to be
   headed `From the store`, which stopped being true the day a line could be an outsourced
   service. The chip carries the row's own glyph instead of a word - a word per chip is the
   panel's height again on a list of nine - and the two tones are the badge's, which are the
   costing workspace's, so a reader meets one pair of colours in three places. `.pchip` itself is
   the Documents row's and is not touched: these two rules add a glyph size and an ink colour. */
.pchip.oqk-material > svg,.pchip.oqk-outsource > svg{flex:0 0 auto;width:12px;height:12px;
  margin-right:4px;vertical-align:-2px}
.pchip.oqk-material > svg{color:var(--ok)}
.pchip.oqk-outsource > svg{color:var(--risk)}

/* ----------------------------------------------------------------------------------------------
   THE `+` AND THE `▾`, UNDER THE TABLE.

   ONE GROUP, TWO CONTROLS, AND THEY READ AS ONE CONTROL - his mock-up draws a split button. The
   `▾` is a `<details class="cwadd">`, which is `+ Add Cost`'s own menu on the costing workspace,
   so it needs no script, no dialog and no round trip to arm, and it carries the two fixes that
   menu cost us on 2026-09-24 for free: `.cwmenu` at `z-index:45` (over the phone's fixed bottom
   bar) and a 44px floor on `.cwadd > summary`. The surface this one opens inside is a `.qsec`
   card and not a `.rows` strip, so it needs no `noclip`.

   THE MENU OPENS TO THE LEFT AND NOT TO THE RIGHT of its summary, which is `.cwmenu`'s default
   (`right:0`) and is right here too: the group sits at the LEFT edge of a wide card, so a menu
   pinned to the summary's right edge grows back across the button rather than off the card.
   ------------------------------------------------------------------------------------------- */
.oqadd{display:flex;flex-wrap:wrap;align-items:center;gap:0;margin-top:var(--sp-3)}
/* THE COUNT AT THE FAR END OF THE SAME ROW - see the note beside it in `views/pages.js`. It
   wraps under the control at a width that cannot hold both, which is `flex-wrap` doing the only
   honest thing rather than a breakpoint deciding it. `margin:0` takes `.hint`'s own top margin
   off, which is sized for a sentence under a field and is wrong beside a button. */
.oqadd > .qcount{margin:0 0 0 auto;padding-left:var(--sp-3)}
/* `.card button` PAINTS EVERY BUTTON IN A CARD NAVY WITH 18px OF TOP MARGIN and `.qsec.csec
   button` takes that to 10 - both wrong for one half of a split control. Three classes, for the
   reason the note beside `.qlt .qlrm > .qlpop > button` records. */
.qlt ~ .oqadd > .oqmore,.oqadd > .oqmore{display:inline-flex;align-items:center;gap:var(--sp-2);
  min-height:44px;margin-top:0;padding:0 var(--sp-4);
  border:1px solid transparent;border-right:0;border-radius:var(--r-md) 0 0 var(--r-md);
  background:var(--priGrad);color:var(--priOn);font-family:var(--ui);font-size:var(--t-sm);
  font-weight:var(--w-bold);white-space:nowrap;cursor:pointer}
.oqadd > .oqmore:hover{filter:brightness(1.12)}
.oqp{font-size:18px;font-weight:var(--w-bold);line-height:1}
/* ONE LABEL SHOWN AND ONE HIDDEN, PICKED BY THE BUTTON'S OWN `data-kind`. Both sentences are
   rendered by the SERVER; `views/lines.js` only copies the token from the last row onto this
   button, because that file may not write a word a reader sees. `display:none` is what keeps the
   other one out of the button's accessible name as well as off the screen. */
.oqw{display:none}
.oqmore[data-kind="material"] > .oqw-material{display:inline}
.oqmore[data-kind="outsource"] > .oqw-outsource{display:inline}
/* AND THE OTHER HALF OF THE SPLIT. It is the same `.cwadd` summary as everywhere else and keeps
   the 44px floor from that rule; what is restated here is only the shape of a control that is
   joined to the one on its left. */
.oqpick{display:flex}
.oqadd > .oqpick > summary{min-width:40px;padding:0 var(--sp-2);
  border-radius:0 var(--r-md) var(--r-md) 0;
  border-left:1px solid color-mix(in srgb, var(--priOn) 30%, transparent)}
.oqadd > .oqpick > summary > svg{width:14px;height:14px;transform:rotate(90deg)}
.oqadd > .oqpick[open] > summary > svg{transform:rotate(-90deg)}
.oqpick > .cwmenu > button > svg{flex:0 0 auto;width:15px;height:15px;color:var(--ink3)}

/* ----------------------------------------------------------------------------------------------
   THE STORE'S LIST, UNDER THE TABLE AND ABOVE THE `+` - 2026-09-26.

   A LIST AND ITS BUTTON, WITH A GAP, AND NOT A SPLIT CONTROL - which is a correction made off a
   screenshot at 390 rather than a preference. The first cut joined them the way `+ ▾` below is
   joined: square inner corners, no column gap. At 390 the button wraps under the list, and two
   stacked controls each with one rounded end and one square end read as a control that has come
   apart. THE `+ ▾` IS JOINED BECAUSE IT IS ONE CONTROL - a button and the menu that switches what
   it adds. This is TWO things, a list and the press that acts on it, and it is drawn as two.
   Both rules below are the only thing this feature cost the stylesheet - the `<option>`s, the
   `<optgroup>`s and the resting option need no CSS at all, and `.oqp` (the `+` glyph) and `.vh`
   (the label the resting option already says out loud) are both already here.

   `select.oqcatl` AND NOT `.oqcatl`, WHICH IS A SPECIFICITY MEASUREMENT AND NOT A HABIT.
   `.qsec.csec select` is (0,2,1) and sets `min-height:40px` on every control in this form; a
   two-class selector is (0,2,0) and LOSES to it, so the 44px floor would never have applied and
   nothing on the page would have said why. The type selector makes it (0,2,1) and later in the
   file, which wins. 44 and not 40 because this is a control a finger presses on a phone, which is
   the same floor `.cwadd > summary` and `.oqmore` carry two rules up.
   ------------------------------------------------------------------------------------------- */
.oqcat{display:flex;flex-wrap:wrap;align-items:center;column-gap:var(--sp-3);
  row-gap:var(--sp-2);margin-top:var(--sp-3)}
.oqcat > select.oqcatl{flex:1 1 14rem;width:auto;min-width:0;max-width:26rem;min-height:44px}
/* `.card button` PAINTS EVERY BUTTON IN A CARD WITH 18px OF TOP MARGIN and `.qsec.csec button`
   takes it to 10 - both wrong for a button that sits beside a field rather than under a form,
   which is the note `.oqmore` above carries for the same three classes. */
.qlt ~ .oqcat > .oqcatb,.oqcat > .oqcatb{display:inline-flex;align-items:center;gap:var(--sp-2);
  min-height:44px;margin-top:0;padding:0 var(--sp-4);
  border:1px solid transparent;border-radius:var(--r-md);
  background:var(--priGrad);color:var(--priOn);font-family:var(--ui);font-size:var(--t-sm);
  font-weight:var(--w-bold);white-space:nowrap;cursor:pointer}
.oqcat > .oqcatb:hover{filter:brightness(1.12)}

/* THE TWO PANELS ON ONE ROW SIT AT THEIR OWN HEIGHTS - 2026-09-25, and it is `.nogrid`'s default
   `stretch` being taken back for one form. `Production project image` is a label, a picker and a
   hint; `Preparation & stations` is eleven pills. Stretched, the image card carried 230px of empty
   tinted surface under its own contents at 1280, which reads as a panel that has lost something.
   Scoped to `.oform`, which only `/orders/new` emits, so `/clients/new`'s own `.nogrid` rows and
   every other one in this app are untouched. It buys no page height - the row is as tall as the
   taller card either way - and it is not there for height. */
.oform > .nogrid{align-items:start}


/* ----------------------------------------------------------------------------------------------
   THE PROJECT IMAGE'S COMPACT PREVIEW.

   IT IS NOT A THUMBNAIL AND IT CANNOT BE ONE: `img-src 'self'` allows neither `blob:` nor
   `data:`, so bytes sitting in a file input have no way onto this page. The argument is written
   out beside the input in `views/pages.js`. What sits here is the row `upload.js` already draws
   for a `multiple` picker - the file's name, its size and an X - moved to the LEFT of the picker,
   which is where his mock-up puts the preview.

   `order:-1` AND NOT A REORDERED DOM, because the script inserts the list after the input (that
   is where it belongs for every other control it draws one for) and this is a presentation
   question. The reading order is picker-then-chosen-file either way, which is the order the two
   things happen in.
   ------------------------------------------------------------------------------------------- */
/* ----------------------------------------------------------------------------------------------
   AND `Order details` IS NOT TIGHTENED, WHICH IS A MEASUREMENT AND NOT A REFUSAL.

   "Tighter Order details" was asked for and a rule for it was written and then taken out again,
   because the block is ALREADY at this app's own floor and the rule made it worse. Measured:
   `.qsec.csec` is 14/16/16 with its boxes at 40px and its labels at 10/4 - the density pass of
   2026-09-21, which is the owner's own answer to "make it a little smaller the ratio" - and the
   two `.nogrid` rows of fields already sit at ZERO margin between them, their only separation
   being the 10px above each label. A `margin-top` of 8px added eight pixels at all four widths
   and was the opposite of what was asked.

   WHAT ACTUALLY MADE THAT BLOCK TIGHTER IS ITS ARRANGEMENT, and the page already had it: six
   boxes in two rows of three, which is his mock-up's own layout. Going below the density pass
   would be undoing a number he set, in a change he did not ask it in.
   ------------------------------------------------------------------------------------------- */

.opicrow{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-3)}
.opicrow > input[type=file]{flex:1 1 12rem;min-width:0}
.opicrow > .dzl{order:-1;flex:0 0 auto;margin:0}

/* THE RUNNING TOTAL, BOTTOM-RIGHT OF THE CARD - his own placement. The label carries the currency
   because the currency is a control on this same form; nothing here says IQD. */
.qlfoot{display:flex;align-items:center;justify-content:flex-end;gap:var(--sp-3);
  margin-top:var(--sp-4);flex-wrap:wrap}
.qlfl{font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-bold);color:var(--ink)}
.qlfv{min-width:9rem}
/* THE THREE ADJUSTMENT BOXES, TIGHT UNDER THE RUNNING TOTAL rather than in a section of their
   own - his instruction for this page in his own words. `.nogrid` does the columns. */
.qladj{margin-top:var(--sp-4)}
/* THE `Other` BOX BEHIND THE TERMS PICKER IS A CONTROL AND WAS 17px TALL. `<summary class="hint">`
   is the shape `views/cost.js` established and `.hint` carries no height at all, so the one thing
   on this card that reveals another box was under the floor for anything a finger presses. Scoped
   to `.qnew`, which only the quotation form carries, so `/rfqs/new`'s own copy of that control is
   left exactly as its owner has it. */
.qnew details > summary.hint{min-height:44px;display:inline-flex;align-items:center}

/* ----------------------------------------------------------------------------------------------
   FROM 900 OF FORM THE LINE IS A ROW - nine columns, one line, a heading row over them, and the
   SAME `--qlc` for both so the two cannot come apart.

   900 IS MEASURED AND NOT CHOSEN. Four of the nine tracks are fixed (26 + 56 + 72 + 56 = 210) and
   eight gaps take 64, which at 900 of form leaves 570 for five flexible tracks: 97px of Category,
   127 of Item, 107 of Unit Price, 97 of Total and 141 of Brief. Under that the Brief loses its
   last word before its first, and the Brief is the column a pricer actually reads.

   WHICH MEANS EVERYTHING BUT A DESKTOP GETS THE STACK, and that is the right answer rather than a
   compromise. This app's shell caps the page column at 760 until the window reaches 1200, so an
   iPad in portrait has 594px of form and one in landscape has 760 - nine live boxes across either
   is not a table, it is a table-shaped way of hiding four of them. `.cform` caps the form at 1012,
   so from 1200 up every window gets the same table.
   ------------------------------------------------------------------------------------------- */
@container rfqform (min-width:900px){
  /* THE LAST TRACK IS 92 AND NOT 56 BECAUSE THE LAST ROW HOLDS TWO CONTROLS: the bin every line
     carries, and the `+` that only the entry row does. Two 44px targets and the 4px between them
     is 92, and the 56 the word ACTIONS needs fits inside it. */
  /* CATEGORY AND ITEM SHARE EVENLY, WHICH IS A MEASUREMENT AND NOT A TIDY NUMBER. Category was
     the narrower of the two on the argument `.qtab` makes - it holds one or two short words while
     the item column holds a name somebody reads at a glance - and that is true of the VALUES and
     false of the control: a `<select>` cannot wrap and cannot ellipsise its own resting option, so
     at 110px the placeholder rendered as `Select cate`. The 1.15/1.15 split gives both 127px at
     the 1012px cap, which fits `Select category` with its chevron and still leaves the Brief the
     widest flexible track in the row. */
  .qlt{--qlc:26px minmax(0,1.15fr) minmax(0,1.15fr) 56px minmax(0,1.1fr) minmax(0,1fr)
    minmax(0,1.45fr) 72px 92px}
  .qlh{display:grid;grid-template-columns:var(--qlc);gap:var(--sp-2);align-items:center;
    padding:var(--sp-2) var(--sp-3);background:var(--paper2);border-bottom:1px solid var(--rule2);
    border-radius:15px 15px 0 0;font-family:var(--ui);font-size:var(--t-micro);
    font-weight:var(--w-bold);letter-spacing:var(--ls-caps);text-transform:uppercase;
    color:var(--ink3)}
  .qlh > span:nth-child(1){text-align:center}
  .qlh > span:nth-child(4),.qlh > span:nth-child(5),.qlh > span:nth-child(6){text-align:right}
  .qlh > span:nth-child(9){text-align:center}
  .qlt .qline{grid-template-columns:var(--qlc);gap:var(--sp-2);align-items:center;
    padding:var(--sp-2) var(--sp-3)}
  /* THE LABELS STAY IN THE DOCUMENT AND LEAVE THE SCREEN. `display:none` would take them out of
     the accessibility tree as well, and the heading row above is `aria-hidden` - so between them
     every box in this table would lose its name. This is `.vh`'s own displacement, written here
     rather than by putting `.vh` on nine labels that must be VISIBLE at 390. */
  .qlt .qline label{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
    clip-path:inset(50%);white-space:nowrap}
  .qlt .qlc{justify-content:center}
  .qlt .qline > .qlnum{grid-column:auto;justify-content:center;min-height:0}
  .qlt .qline > .qlnum > b::before{content:""}
  .qlt .qlat{justify-content:center;gap:4px;font-size:var(--t-micro)}
  .qlt .qlac{justify-content:center}
  .qlt .qline input,.qlt .qline select{min-height:40px;padding:8px 10px;font-size:var(--t-sm)}
  .qlt .qlsum{min-height:40px;padding:8px 10px}

  /* AND `/orders/new`'s OWN FIVE TRACKS - 2026-09-25. The nine above are the quotation line's and
     this row draws five cells plus a number it does not show, so it cannot take a prefix of them:
     `Item or service` is the one column that must be WIDE (it is the whole of what a line is
     called now that Specification has gone), `Qty` is the 56px track the block above sized for a
     quantity, `Unit` is a short word, and the last is 44 for one bin rather than 92 for a bin and
     a `+` - the `+` is not in a cell on this form. */
  /* THE LAST TRACK IS 72 AND NOT 44, WHICH IS THE BIN'S OWN WIDTH. The heading over it is the
     word REMOVE - his mock-up's, not a shortening of it - and at 10px uppercase with .09em of
     tracking that measures 52px. A 44px track drew it over the Unit column beside it; the bin
     stays 44 and centres in 72. Measured at 1024 and 1280, where this shape exists at all. */
  .qlt.oreq{--qlc:9.5rem minmax(0,1fr) 56px 6.5rem 72px}
  /* THE NUMBER IS OFF IN THE TABLE AND STAYS IN THE MARKUP - see the `.oreq` block above. */
  .qlt.oreq .qline > .qlnum{display:none}
  /* FOUR HEADINGS LEFT AND ONE CENTRED, WHICH IS WHERE THE THINGS UNDER THEM ACTUALLY ARE. The
     generic block above right-aligns headings 4, 5 and 6 because on a quotation line those
     tracks hold FIGURES and a column of right-aligned numbers wants a right-aligned heading.
     Nothing in this table is a figure - `Qty` is a typed string this app does no arithmetic on -
     and a right-aligned `UNIT` over a left-aligned box drew the word above the gap between two
     columns, reading as a heading for the bin. `REMOVE` stays centred over a centred bin. */
  .oreq .qlh > span{text-align:left}
  .oreq .qlh > span:nth-child(5){text-align:center}
}

/* ----------------------------------------------------------------------------------------------
   DELIVERY TIME - the chips, and the box behind `Custom`.

   THE CHIPS THEMSELVES COST NOTHING: `.smg` and `.smi` are Add Client's own pill group, radios
   inside `.vh` labels at the 44px floor, tuned in the owner's density pass on the 21st. What is
   here is only the reveal, plus taking back the one rule that makes a pill read as a field label.

   `:has()` AND THE FALLBACK RUNS THE SAFE WAY ROUND. The box is hidden when a PRESET is checked,
   not shown when `Custom` is - so a browser that does not know `:has()` simply always draws it,
   which is a form somebody can still fill in. The other polarity would have hidden the only way
   to type 12 on exactly those browsers, silently.
   ------------------------------------------------------------------------------------------- */
.wdays{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-2)}
.wdbox{flex:0 1 7.5rem;min-width:6rem}
.wdays:has(.wdp:checked) > .wdbox{display:none}
/* `.card label` IS A CLASS AND AN ELEMENT AND `.smi` IS ONE CLASS, so every pill in this group was
   drawn at 10px, uppercase, with .09em of tracking and in `--ink3` - which is the treatment for
   the WORD ABOVE a field and turns `10` into a caption and `Custom` into `CUSTOM`. The pills are
   the control here, so they take the control's type back. Scoped to `.wdays`, which only this one
   field emits, so Add Client's own three pills are untouched. */
.wdays .smi{font-size:var(--t-sm);font-weight:var(--w-semi);letter-spacing:0;
  text-transform:none;color:var(--ink2)}
.wdays .smi:has(input:checked){color:#fff}
/* SEVEN PILLS IN A THIRD OF THE FORM, ON ONE LINE, AND THE TWO NUMBERS ARE MEASURED. `.smi`'s own
   `0 14px` is right for three words in a row of its own (`WhatsApp / Phone / Email` on Add Client)
   and puts these seven over 315px, which is what a third of the 1012px form is - so the row wrapped
   and the field became 94px tall beside two 40px neighbours. At 8px a side with a 38px floor the
   seven measure 310 and the group is one line and 52px tall. THE FLOOR IS WHAT KEEPS `1` PRESSABLE:
   a pill holding one digit is 24px wide without it, and the height is already 44. */
.wdays .smi{padding:0 8px;min-width:38px}

/* ----------------------------------------------------------------------------------------------
   THE TWO SMALL MARKS ON THE DETAILS CARD, AND THE PANELS ON THE FILES ONE.
   ------------------------------------------------------------------------------------------- */
/* `Auto-filled`, INSIDE THE BOX IT IS ABOUT - his own badge, and it is a statement rather than a
   control: the name in that box is this session's and nobody asked for it. `.ccnt`'s positioning
   above is the same idea against a textarea's corner; this one clears a 12px radius. */
.qafw{position:relative;display:block}
.qafl{position:absolute;right:9px;top:50%;transform:translateY(-50%);pointer-events:none;
  padding:3px 8px;border-radius:var(--r-pill);background:var(--priBg);color:var(--brassInk);
  font-family:var(--ui);font-size:var(--t-micro);font-weight:var(--w-bold);white-space:nowrap}
/* THE BOX UNDER IT KEEPS ITS WORDS CLEAR OF THE BADGE. 6.5rem is the badge plus its own inset,
   measured at the 10px uppercase this file draws it in. */
.qafw > input{padding-right:6.5rem}
/* WHERE THE FILES AND THE REFERENCE LINK GO - a sentence and a dashed rule, which is what "drag
   and drop" honestly looks like on a form that cannot carry a file. See `filesCard` in
   `views/quotations.js` for why there is no control in either panel. */
.qdrop{display:flex;align-items:center;gap:var(--sp-3);min-height:88px;padding:var(--sp-4);
  border:1px dashed var(--rule);border-radius:var(--r-md);background:var(--paper);
  color:var(--ink3);margin-top:var(--sp-2)}
.qdrop > svg{flex:0 0 auto;width:22px;height:22px}
.qdrop p{margin:0;font-size:var(--t-sm);line-height:1.5}

/* ==============================================================================================
   PPPPPPPPPP THE SALES DASHBOARD, 2026-09-22 - /sales, REDRAWN PPPPPPPPPP

   WHAT MOVED IT: the owner commissioned `design-refs/sales-dashboard.png` and `/sales` stopped
   being a hub of doors. It is four sections - a header with a greeting, four Quick Action cards,
   five Sales Overview figures and a Recent Activity table under a tab strip - and three of those
   four are shapes this stylesheet did not have. `views/hub.js` is UNTOUCHED and still draws
   `Production`, `Admin` and `Dashboard` out of `.rows`/`.row.door`; not one byte of that block or
   of any other existing rule was edited by this round.

   EVERY SELECTOR BELOW NEEDS `.sdash`, `.qacts`, `.qact`, `.ovw`, `.ocard`, `.mom`, `.shello`,
   `.sday`, `.shi`, `.sline`, `.ssh`, `.sact`, `.vall`, `.atwrap` OR `.atab`, and `views/sales.js`
   is the only file in this app that emits any of them. The two that reach a control which was
   already there are named where they are made: `.ocard .fig .k`/`.v` retune the app's own figure
   INSIDE this page's card (the same two-class scoping `.qsec.csec` and `.qsec.rsec` use, and for
   the same reason - they have to beat `.fig`'s own rule and a specificity tie settled by source
   order is not a thing to leave to a later reader), and `.sact > .wtabs` zeroes the strip's bottom
   margin because here it sits INSIDE a heading row rather than above a list.

   THE ONE NEW COLOUR IS `--vio`/`--vioBg`, declared in all three theme states at the head of this
   file with both of its measurements. Everything else here resolves through tokens that already
   existed and already flip per theme; there is no literal colour, no inline style, no `!important`
   and no script anywhere in this block.

   @container AND NOT @media, WHICH IS LOAD-BEARING HERE AND NOT A STYLE PREFERENCE - the same
   argument the Create RFQ block makes one screen up. This app's navy rail is 192px from 768 and
   216px from 1200, and `.wrap` caps at 760 until 1200 and 1280 after it, so the COLUMN this page
   actually gets is 362px at a 390 phone, 594px on an 834 iPad in portrait, 732px on a 1024 iPad in
   landscape and 1012px at 1280. Asking the WINDOW how many columns a row has would have given the
   iPad in landscape - 1024, which sounds like a desktop - a desktop's five tracks inside 732px.
   So the page asks itself, and the four column counts below are the four real widths:

     container      device                    Quick Actions   Sales Overview   Recent Activity
     under 520      390 phone (362)                 1                1         compact rows
     520-699        834 iPad portrait (594)         2                2         table
     700-939        1024 iPad landscape (732)       4                3         table
     940 and up     1280 desktop (1012)             4                5         table

   which is the owner's spec read literally: "4-across and 5-across on desktop, wrapping to 2 on
   iPad portrait, stacked on a phone, and the activity table becoming compact rows".

   NO HORIZONTAL OVERFLOW AT ANY OF THE FOUR, and the table is where that had to be bought rather
   than assumed: six columns do not fit 594px however they are written, so `.atwrap` is its own
   `overflow-x:auto` box and the table scrolls INSIDE it - `test/figures.test.js` asserts the PAGE
   never scrolls sideways, and a scroller of its own is the one way a wide table keeps that true.
   Below 520 the table is not a table at all and `.atwrap` stops scrolling, because a stack of
   compact rows is already as narrow as the column.

   44px UNDER EVERYTHING PRESSED. The month `<select>`, its `Show` button, `View all`, every Quick
   Action card and the chevron in each table row are all at or above it; the tab strip is `.wtab`'s
   own 38px, which is the strip the owner ruled on separately and is not changed here.
   ============================================================================================== */

/* THE PAGE IS THE QUERY CONTAINER. `inline-size` and not `size`: nothing here asks about height,
   and a `size` container needs a height that does not come from its content. */
.sdash{container:sdash / inline-size}

/* ----------------------------------------------------------------------------------------------
   1. THE HEADER'S RIGHT-HAND BLOCK - today's date, the greeting, and one line under it.

   IT RIDES IN `pagehead`'s `action` SLOT rather than being a fourth thing in that header. `.pact`
   is already `flex:1 0 100%` on a phone and `margin-left:auto` from 768 up (see THE PAGE HEADER
   further up this file), which is exactly the two layouts the reference draws: under the title on
   a phone, opposite it on a tablet and a desktop. Nothing about `.phead`, `.ptile` or `.ptx`
   moved, so the other thirty-eight pages' headers are byte-identical.
   ------------------------------------------------------------------------------------------- */
.pact > .shello{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;
  align-items:flex-start;gap:2px}
.sday{font-family:var(--ui);font-size:var(--t-xs);font-weight:var(--w-semi);color:var(--ink2)}
.shi{display:flex;align-items:center;gap:var(--sp-2);min-width:0;
  font-family:var(--ui);font-size:var(--t-title);font-weight:var(--w-heavy);
  letter-spacing:var(--ls-title);color:var(--ink);overflow-wrap:anywhere}
/* THE SUN IS ONE STEP DOWN FROM A ROW MARK. `.tico` is 34px, which is a tile beside a 19px word;
   26px sits it on the cap height of the greeting, which is where the reference puts it. */
.shi > .tico{width:26px;height:26px;border-radius:9px}
.shi > .tico svg{width:16px;height:16px}
.sline{font-family:var(--ui);font-size:var(--t-sm);color:var(--ink3)}

/* ----------------------------------------------------------------------------------------------
   2. THE SECTION HEADING, AND THE CONTROL THAT SITS AT THE RIGHT-HAND END OF IT.

   `.ssh` IS `.qsh`'s TYPE ON A ROW THAT CAN CARRY A CONTROL. `.qsh` is the numbered-card heading
   on the three forms and is `display:flex` with a 12px gap; this is the same size, weight and
   tracking with `margin-left:auto` on whatever comes second, because the reference puts
   `This Month` and `View all` at the far end of the heading they belong to rather than on a row
   of their own. Reaching for `.qsh` itself would have meant either a third modifier on it or a
   bare `.qsh + *` rule that the two client forms would have picked up.
   ------------------------------------------------------------------------------------------- */
.ssh{display:flex;align-items:center;flex-wrap:wrap;gap:var(--sp-2) var(--sp-3);
  margin:0 0 var(--sp-3);font-family:var(--ui);font-size:var(--t-title);
  font-weight:var(--w-heavy);letter-spacing:var(--ls-title);color:var(--ink)}
.ssh > h3{margin:0;font:inherit;letter-spacing:inherit;color:inherit;flex:0 0 auto}
/* THE MONTH CONTROL. `.tools` carries a 12px bottom margin for the rows it normally sits above;
   inside a heading it has nothing under it, so the margin goes and `margin-left:auto` pushes it
   to the end of the line. It is `monthPick` from `views/layout.js` - the SAME control and the
   same `?month=` as `/rfqs` and `/quotations`, not a second mechanism. */
.ssh > .tools{margin:0 0 0 auto;flex:0 1 auto;gap:var(--sp-2)}
/* A BARE `<select>` HAS NO RULE IN THIS FILE and on the two pages that already draw this control
   it is the browser's own box. Here it is given the reference's pill, scoped to this heading so
   neither of those pages moves - and it is 44px rather than the reference's 38, because the
   standing floor under anything a finger presses is 44 and a control is not an exception to it. */
.ssh > .tools select{font:inherit;font-family:var(--ui);font-size:var(--t-sm);
  font-weight:var(--w-semi);min-height:44px;padding:0 var(--sp-3);
  border-radius:var(--r-md);border:1px solid var(--rule);background:var(--card);
  color:var(--ink);max-width:100%}
.ssh > .tools .chip{min-height:44px;height:auto;border-radius:var(--r-md);font-size:var(--t-sm)}

/* ----------------------------------------------------------------------------------------------
   THE FOUR QUICK ACTIONS.

   A CARD IS A LINK AND THE WHOLE CARD IS THE TARGET, which is this codebase's one rule about
   controls applied to a tile: there is nothing else on a Quick Action to press, so an inner
   anchor would have been a smaller target inside a box that looks pressable. It is `.qact` and
   not `.tile`: `.tile` is a PANEL with an `h3` and figures in it (the Money tile, the shop
   today), and giving it an `:hover` border and a text-decoration reset would have changed four
   panels on `/orders` to make one card on this page.
   ------------------------------------------------------------------------------------------- */
.qacts{display:grid;gap:var(--sp-3);grid-template-columns:minmax(0,1fr)}
.qact{display:flex;flex-direction:column;align-items:flex-start;gap:var(--sp-6);
  min-width:0;min-height:44px;padding:var(--sp-4);border-radius:var(--r-lg);
  background:var(--card);border:1px solid var(--rule);box-shadow:var(--e-1);
  text-decoration:none;color:var(--ink);
  transition:border-color .14s ease,box-shadow .14s ease}
.qact:hover{border-color:var(--brassInk);box-shadow:var(--e-2)}
.qact:focus-visible{outline:2px solid var(--brassInk);outline-offset:2px}
/* THE SQUARE IS A `.tico` ONE BAND UP - 34px is a row mark and the reference's action tile is 44.
   The tone is the destination's, and the glyph is the destination's own nav mark through
   `navIcon`, so the square on `New RFQ` is the price tag `/rfqs` wears on the rail. */
.qact > .tico{width:44px;height:44px;border-radius:var(--r-md)}
.qact > .tico svg{width:22px;height:22px}
.qatx{display:flex;flex-direction:column;gap:3px;min-width:0}
.qatx > b{font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-heavy);
  letter-spacing:-.01em;color:var(--ink);overflow-wrap:anywhere}
.qatx > em{font-style:normal;font-family:var(--ui);font-size:var(--t-xs);color:var(--ink3)}

/* ----------------------------------------------------------------------------------------------
   3. THE FIVE FIGURES.

   THE INSIDE OF A CARD IS THE APP'S OWN `.fig` - `<span class="k">` over `<span class="v">`, the
   same two elements `/orders`, `/won`, `/clients`, `/rfqs` and `/quotations` print every figure
   in this app with. What is retuned, scoped to `.ocard`, is the two type steps: `.fig .k` is
   10px uppercase at .09em tracking, which is right above a 23px number in a panel and reads as
   shouting beside a 26px number in a 193px card, and the reference's label is a plain small grey
   line. So inside this card it is `--t-xs`, sentence case, untracked - and on every other page in
   the app `.fig` is byte-identical.
   ------------------------------------------------------------------------------------------- */
.ovw{display:grid;gap:var(--sp-3);grid-template-columns:minmax(0,1fr)}
/* THE CARD IS THE DOOR - see the block above `card()` in `views/sales.js` for why the reference's
   plain tile had to become a link. `text-decoration:none` and the body ink, so it reads as a card
   and not as five blue headings; the hover is `.qact`'s, because they are the same kind of object. */
.ocard{display:flex;align-items:flex-start;gap:var(--sp-3);min-width:0;
  padding:var(--sp-3) var(--sp-4) var(--sp-4);border-radius:var(--r-lg);
  background:var(--card);border:1px solid var(--rule);box-shadow:var(--e-1);
  text-decoration:none;color:var(--ink);
  transition:border-color .14s ease,box-shadow .14s ease}
.ocard:hover{border-color:var(--brassInk);box-shadow:var(--e-2)}
.ocard:focus-visible{outline:2px solid var(--brassInk);outline-offset:2px}
.ocard > .tico{width:38px;height:38px;border-radius:var(--r-md);margin-top:2px}
.ocard > .tico svg{width:19px;height:19px}
/* `.fig` IS A `<span>` HERE AND NOT A `<div>`, because its parent is now an anchor - so the flex
   column it has always been has to be declared rather than inherited from `display:block`. */
.ocard > .fig{flex:1 1 0;display:flex;flex-direction:column;min-width:0}
.ocard .fig .k{font-size:var(--t-xs);font-weight:var(--w-semi);letter-spacing:0;
  text-transform:none;color:var(--ink2);margin-bottom:1px}
.ocard .fig .v{font-size:26px;line-height:1.16;margin-top:0}

/* THE MONTH-ON-MONTH LINE, AND IT IS DRAWN ONLY WHERE THE COMPARISON WAS ACTUALLY COMPUTED - see
   `figureOf` in `src/sales-hub.js`. Green up, red down, grey level; the words `vs last month` sit
   under the figure in the body grey, which is what the reference draws and what keeps the coloured
   part to the claim itself. `tabular-nums` so `↑ 8%` and `↑ 14%` start at the same pixel down a
   row of five cards. */
.mom{display:block;margin-top:5px;font-family:var(--ui);font-size:var(--t-xs);
  font-weight:var(--w-bold);color:var(--ink2);font-variant-numeric:tabular-nums}
.mom.up{color:var(--ok)}
.mom.down{color:var(--late)}
.mom > em{display:block;margin-top:1px;font-style:normal;font-weight:var(--w-reg);
  color:var(--ink3)}

/* ----------------------------------------------------------------------------------------------
   4. RECENT ACTIVITY - the heading row, and the table.

   THE HEADING, THE TAB STRIP AND `View all` SHARE ONE LINE, which is the reference's own
   arrangement and is why the strip is a flex ITEM here rather than a block above the list.
   `flex:1 1 240px` with `min-width:0` is what lets it keep its contract on a phone: the strip
   still does not wrap, it still scrolls, and `finder.js` still brings the lit tab into view -
   which is the owner's explicit ruling about this control and is measured on this page by
   `test/figures.test.js` at 390 and 320. On a phone the whole row wraps to three lines and the
   strip takes the full column.
   ------------------------------------------------------------------------------------------- */
.sact{display:flex;align-items:center;flex-wrap:wrap;gap:var(--sp-2) var(--sp-3);
  margin:0 0 var(--sp-3)}
.sact > h3{margin:0;flex:0 0 auto;font-family:var(--ui);font-size:var(--t-title);
  font-weight:var(--w-heavy);letter-spacing:var(--ls-title);color:var(--ink)}
.sact > .wtabs{flex:1 1 240px;min-width:0;margin:0;align-self:stretch}
.vall{flex:0 0 auto;margin-left:auto;display:inline-flex;align-items:center;gap:5px;
  min-height:44px;padding:0 var(--sp-2);font-family:var(--ui);font-size:var(--t-sm);
  font-weight:var(--w-semi);color:var(--priInk);text-decoration:none;white-space:nowrap}
.vall:hover{text-decoration:underline}

/* THE TABLE'S OWN SCROLLER. Six columns cannot fit 594px of iPad portrait at any honest wording,
   so the table is allowed to be wider than the column and to scroll inside this box - which is
   what keeps the PAGE from scrolling sideways. The rounded card is on the wrapper rather than on
   the table so the corners survive the scroll. */
.atwrap{overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;
  overscroll-behavior-x:contain;border-radius:var(--r-lg);background:var(--card);
  border:1px solid var(--rule);box-shadow:var(--e-1)}
.atab{width:100%;border-collapse:collapse;font-family:var(--ui);font-size:var(--t-sm)}
.atab th{font-family:var(--ui);font-size:var(--t-micro);font-weight:var(--w-bold);
  letter-spacing:var(--ls-caps);text-transform:uppercase;color:var(--ink3);
  text-align:left;white-space:nowrap;padding:var(--sp-3) var(--sp-4);
  background:var(--paper2);border-bottom:1px solid var(--rule)}
.atab td{padding:var(--sp-3) var(--sp-4);border-top:1px solid var(--rule2);
  vertical-align:middle;color:var(--ink2)}
.atab tbody tr:first-child td{border-top:0}
/* THE DATE AND THE ACTION NEVER WRAP AND NEVER SHRINK; the three text columns may. */
.atab td.ad{white-space:nowrap;color:var(--ink3);font-variant-numeric:tabular-nums}
.atab td.at{white-space:nowrap}
.atab td.as,.atab td.ax{white-space:nowrap}
.atab td.ax{text-align:right}
/* THE TYPE CELL: the kind's own mark, and the kind's own word beside it. The mark is the same
   glyph and the same tone as that kind's Overview card, so a reader who has learnt the yellow
   flag at the top of the page meets the yellow flag on the row. */
.atype{display:inline-flex;align-items:center;gap:var(--sp-2);min-width:0}
.atype > .tico{width:28px;height:28px;border-radius:9px}
.atype > .tico svg{width:15px;height:15px}
.atype > span{font-family:var(--ui);font-size:var(--t-sm);color:var(--ink2)}
/* THE IDENTIFIER OVER WHAT IT IS - the row shape every list in this app already uses (`.qno` over
   `em` on the RFQ and quotation rows), in a table cell. */
.atab b{display:block;font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-bold);
  color:var(--ink);overflow-wrap:anywhere}
.atab em{display:block;font-style:normal;font-size:var(--t-xs);color:var(--ink3);
  overflow-wrap:anywhere}
/* THE CHEVRON IS THE `ACTION`, and it is a real link with a 44px box round it rather than a mark
   beside one. Two rotated borders draw it, exactly as `a.qopen::after` draws the hub's - no
   markup, no glyph, and nothing added to the bytes of a page that renders on every visit. */
a.achev{display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;margin:-11px -8px -11px 0;color:var(--ink3);text-decoration:none}
a.achev::after{content:"";width:7px;height:7px;border-top:2px solid currentColor;
  border-right:2px solid currentColor;transform:rotate(45deg)}
a.achev:hover{color:var(--priInk)}
a.achev:focus-visible{outline:2px solid var(--brassInk);outline-offset:-2px;
  border-radius:var(--r-sm)}

/* ----------------------------------------------------------------------------------------------
   THE FOUR COLUMN COUNTS. See the table at the head of this block for which device each is.
   ------------------------------------------------------------------------------------------- */
@container sdash (min-width:520px){
  .qacts,.ovw{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@container sdash (min-width:700px){
  .qacts{grid-template-columns:repeat(4,minmax(0,1fr))}
  .ovw{grid-template-columns:repeat(3,minmax(0,1fr))}
}
@container sdash (min-width:940px){
  .ovw{grid-template-columns:repeat(5,minmax(0,1fr))}
}

/* ----------------------------------------------------------------------------------------------
   AND THE QUICK ACTION IS A ROW ON A PHONE RATHER THAN A COLUMN.

   MEASURED IN THE WALK AT 390: stacked, four cards are 480px of screen - the mark on its own line
   with 24px of air under it, four times over, before the reader reaches the figures. As a row -
   mark, then the two lines beside it - they are 288px and read as a list of four things to do,
   which is what they are. The 44px mark and the 44px minimum are untouched.
   ------------------------------------------------------------------------------------------- */
@container sdash (max-width:519px){
  .qact{flex-direction:row;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4)}
  .qatx{flex:1 1 auto}
}

/* ----------------------------------------------------------------------------------------------
   AND THE COMPACT ROW - the owner's spec for the activity table, below 700px of COLUMN.

   700 AND NOT 520, WHICH THE WALK DECIDED AND NOT A GUESS. Six columns of this table need about
   670px: DATE 100, TYPE 110, TITLE/REFERENCE 180, CLIENT 140, STATUS 90, ACTION 52. An 834px iPad
   in portrait gives this page 594px of column, and the shot at that width had `Granite Fit-Out`
   broken as `Granit / e Fit- / Out` across three lines with the chevron column scrolled off the
   right-hand edge. There is no wording of six honest columns that fits 594px. A 1024px iPad in
   LANDSCAPE gives 732px, which does fit - measured, with the chevron on screen - so the table
   starts there and everything below it is the compact row.

   THE HEADER ROW GOES AND THE CELLS ARE LABELLED BY POSITION, which is the one thing a
   responsive table has to decide. `display:none` on `<thead>` and not `.vh`: with the row turned
   into a grid there is no column for a header to head, so a screen reader hearing `DATE` before
   every date would be hearing a label that no longer describes a structure. The cells are ordered
   the way a phone row reads - what it is, then its number, then whose it is, then when - with the
   status at the top right and the chevron beside the middle of the block.
   ------------------------------------------------------------------------------------------- */
@container sdash (max-width:699px){
  .atwrap{overflow-x:visible}
  .atab,.atab tbody{display:block}
  .atab thead{display:none}
  .atab tbody tr{display:grid;grid-template-columns:minmax(0,1fr) auto;
    align-items:center;gap:3px var(--sp-3);padding:var(--sp-3) var(--sp-4);
    border-top:1px solid var(--rule2)}
  .atab tbody tr:first-child{border-top:0}
  .atab td{display:block;padding:0;border:0}
  .atab td.at{grid-area:1 / 1}
  .atab td.as{grid-area:1 / 2;justify-self:end}
  .atab td.ai{grid-area:2 / 1}
  .atab td.ac{grid-area:3 / 1}
  .atab td.ad{grid-area:4 / 1;margin-top:2px}
  .atab td.ax{grid-column:2;grid-row:2 / 5;justify-self:end;align-self:center;text-align:right}
  a.achev{margin:0}
}

/* THE GREETING BLOCK TAKES THE RIGHT-HAND END OF THE HEADER FROM 768 UP, which is the width at
   which `.pact` itself stops wrapping (see THE PAGE HEADER above). It is `@media` and not
   `@container` for exactly that reason: it is following the SHELL's own breakpoint, not a
   decision about how much room this page's column has. */
@media(min-width:768px){
  .pact > .shello{align-items:flex-end;text-align:right}
}

/* ==============================================================================================
   THE COSTING WORKSPACE — `/pricing/<key>`, 2026-09-23.
   ==============================================================================================
   The owner drew this one: three columns, the line selector on the left, one line's cost
   breakdown in the middle, the totals and his own notes on the right
   (`design-target/costing-workspace-mockup.png`, 1448x1086). What follows is that picture read
   against what this app actually is, and the four places the two disagree are named where they
   are resolved rather than in a report nobody will have to hand.

   `@container` AND NOT `@media`, WHICH IS LOAD-BEARING HERE AND IS THE SAME ARGUMENT THE SALES
   DASHBOARD AND CREATE RFQ BLOCKS ABOVE BOTH MAKE. This app's navy rail is 192px from 768 and
   216px from 1200, and `.wrap` caps at 760 until 1200 and 1280 after it, so the COLUMN this page
   actually gets is 362px at a 390 phone, 594px on an 834 iPad in portrait, 732px on a 1024 iPad
   in LANDSCAPE and 1012px at 1280. A 1024 iPad sounds like a desktop and is 732px of room; asked
   of the window it would have been handed a desktop's three tracks inside two thirds of the
   space. So the page asks itself.

   THERE ARE TWO QUERY CONTAINERS AND THEY ASK DIFFERENT QUESTIONS.

     `.cwork`  HOW MANY COLUMNS. Three from 700 up, one below it.
     `.cwc`    WHETHER THE COST BREAKDOWN IS STILL A TABLE. Six editable columns and an action
               need about 480px of honest width; under that it stops being a table and becomes
               the stack of labelled cells `.atab` already becomes on a phone.

   The four real widths, measured, and what each one gets:

     container   device                    columns   centre column   cost breakdown
     362         390 phone                 1         362             stacked cells
     594         834 iPad portrait         1         594             table
     732         1024 iPad landscape       3         ~423            stacked cells
     1012        1280 desktop              3         ~518            table

   THE IPAD-IN-LANDSCAPE CELL IS THE ONE DELIBERATE DEPARTURE FROM THE PICTURE AND IT IS THE DATA
   REFUSING, NOT A PREFERENCE. He asked for the same three columns there, tighter, and he has
   them. What a 423px middle column cannot also have is seven columns of editable boxes: the
   honest alternatives were to scroll the table sideways inside its own box - while somebody is
   typing into it - or to stack the cells. Stacked wins. Nothing is hidden either way and every
   figure is in the same place.

   NO SIDEWAYS PAGE SCROLL AT ANY WIDTH. Every track is `minmax(0, …)`, every text cell is
   `min-width:0`, the table has a scroller of its own for the band where it is still a table and
   still tight, and the references panel is `position:fixed` - which contributes nothing to the
   document's own width. `test/figures.test.js` measures the document, not an element.
   ============================================================================================== */

/* THE PAGE IS THE QUERY CONTAINER for how many columns there are. `inline-size` and not `size`:
   nothing here asks about height, and a `size` container needs a height its content does not give
   it. It is also the `<form>` on a page somebody may write to, which is what lets the line
   selector be a real submit button - see `.cwitem`. */
.cwork{container:cwork / inline-size;margin:0}
.cwgrid{display:grid;gap:var(--sp-4);grid-template-columns:minmax(0,1fr);align-items:start}
.cwl,.cwc,.cwr{min-width:0}
/* THE MIDDLE COLUMN IS A CONTAINER OF ITS OWN, and that is the second question - see the table
   above. It has to be asked of this element rather than of `.cwork`, because how wide the middle
   column is depends on how many columns there are. */
.cwc{container:cwc / inline-size}

/* THE METADATA LINE UNDER THE PAGE HEADER — client, project, pricing type, deadline.
   ----------------------------------------------------------------------------------------------
   IT IS NOT A `.sub` AND THAT IS NOT A STYLE DECISION. `pagehead`'s third slot is bounded at
   `HEAD_SUB_MAX` (64 characters) and `test/page-header.test.js` refuses anything in it that holds
   a sentence break - and a client legally called `Al Azd Trading Agencies Co.` puts a full stop
   followed by a space into the middle of the line the owner asked for. So the four facts are four
   facts on a line of their own, `pagehead` is passed `null`, and the rule that keeps prose off
   these headings is not weakened to let data through it. */
.cwmeta{margin:calc(var(--sp-5) * -1) 0 var(--sp-5);font-family:var(--ui);
  font-size:var(--t-sm);color:var(--ink2);line-height:1.5;overflow-wrap:anywhere}
.cwmeta b{font-weight:var(--w-semi);color:var(--ink)}
/* THE STATUS BADGE RIDES `pagehead`'s ACTION SLOT, which is `flex:1 0 100%` on a phone and
   `margin-left:auto` from 768 up - under the title on a phone, opposite it on a tablet and a
   desktop, which is exactly the two layouts the picture draws. `.pact` is a flex row and `.stat`
   is a pill with a fixed height, so the one thing it needs is not to stretch. */
.pact > .stat{flex:0 0 auto;align-self:flex-start;height:auto;min-height:26px;
  padding:5px var(--sp-3);font-size:var(--t-xs);border-radius:var(--r-pill)}

/* ----------------------------------------------------------------------------------------------
   THE PANEL. One white card, used by all three columns, so the three read as one workspace.
   `.card` itself was not reused: it caps at 30rem, carries an 18px radius and 18/18/22 padding,
   and every one of those is the wrong answer inside a 228px column.
   ------------------------------------------------------------------------------------------- */
.cwpanel{min-width:0;border-radius:var(--r-lg);background:var(--card);
  border:1px solid var(--rule);box-shadow:var(--e-1);padding:var(--sp-4)}
.cwpanel + .cwpanel{margin-top:var(--sp-3)}
/* A PANEL HEADING, AND THE ONE CONTROL THAT MAY SIT AT THE END OF IT. `.ssh`'s shape at one step
   down in size, because a heading inside a 228px column is not a page heading. */
.cwhd{display:flex;align-items:center;flex-wrap:wrap;gap:var(--sp-2);
  margin:0 0 var(--sp-3);font-family:var(--ui);font-size:var(--t-title);
  font-weight:var(--w-bold);letter-spacing:var(--ls-title);color:var(--ink)}
.cwhd > h3{margin:0;font:inherit;letter-spacing:inherit;color:inherit;flex:0 0 auto}
.cwhd > .cwn{font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-semi);
  color:var(--ink3);flex:0 0 auto}
.cwhd > .cwact{margin-left:auto;flex:0 0 auto}

/* ==============================================================================================
   THE LINE SELECTOR, AND NOTHING ELSE ON IT.
   ==============================================================================================
   EVERY CARD IS A REAL SUBMIT BUTTON on a page somebody may write to, and that is the whole of
   how one line's editor replaces another without a script and without losing a keystroke. A link
   would have been a page load, and a page load in the middle of typing forty boxes is forty boxes
   gone. A press posts the form, the server redraws it with everything that was typed still in it
   and the asked-for line open, and `views/rows.js` intercepts the same press to do it instantly.
   A reader who may look and not write gets an `<a>` instead, because there is nothing to lose.

   ==============================================================================================
   THESE RULES ARE STILL THE LEFT COLUMN'S, AND THEY BELONG TO `/cost-review/<key>` NOW.

   The COSTING page's selector became a horizontal strip on 2026-09-26 (§1 of his redesign) and
   the strip's own rules are the block after this one, scoped under `.cwsel`. Cost Review draws
   the identical frame - `.cwork` / `.cwgrid` / `.cwl` / `.cwc` / `.cwr`, a card per line down the
   left - and is not in that redesign, so every declaration here is load-bearing on that page and
   nothing was deleted for the costing page's benefit.
   ============================================================================================== */
.cwlist{display:grid;gap:var(--sp-2);grid-template-columns:minmax(0,1fr)}
.cwitem{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:2px var(--sp-3);
  width:100%;min-width:0;min-height:44px;text-align:left;text-decoration:none;
  padding:var(--sp-3);border-radius:var(--r-md);border:1px solid var(--rule);
  background:var(--card);color:var(--ink);font:inherit;font-family:var(--ui);cursor:pointer;
  transition:border-color .14s ease,background-color .14s ease}
.cwitem:hover{border-color:var(--priRule)}
/* THE SELECTED CARD CARRIES THE PICTURE'S BLUE OUTLINE, in this app's own blue. `--ship` is the
   measured pair already used by the Labour badge and by every `t-ship` mark in the app; a second
   blue for one border would be two blues nobody can tell apart, which is the finding the purple
   block at the head of this file records. The ring is a `box-shadow` and not a second border, so
   the card does not move by a pixel when it is chosen. */
.cwitem.cwon{border-color:var(--ship);background:var(--shipBg);
  box-shadow:0 0 0 1px var(--ship)}
/* THE NUMBER, IN ITS OWN ROUND. Two rows tall so it sits against the name and the line under it. */
.cwno{grid-area:1 / 1 / 3 / 2;align-self:start;display:inline-flex;align-items:center;
  justify-content:center;width:26px;height:26px;border-radius:var(--r-pill);
  background:var(--paper2);color:var(--ink2);
  font-size:var(--t-xs);font-weight:var(--w-bold);font-variant-numeric:tabular-nums}
.cwitem.cwon .cwno{background:var(--card);color:var(--ship)}
/* THE NAME WRAPS AND DOES NOT ELLIPSISE, WHICH WAS MEASURED RATHER THAN CHOSEN. At 1280 this
   column is 223px and at a 1024 iPad it is 183; `Freestanding floor display` on one clipped line
   reads `Fr…`, which identifies nothing. Two lines of a real name is what the picture shows and
   is what a selector is for. `-webkit-line-clamp` holds it to two so a paragraph in an item name
   cannot make one card the height of the list. */
.cwnm{grid-area:1 / 2 / 2 / 4;min-width:0;font-size:var(--t-sm);font-weight:var(--w-bold);
  color:var(--ink);line-height:1.35;overflow:hidden;overflow-wrap:anywhere;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}
/* ==============================================================================================
   HOW MANY THE RFQ ASKED FOR SITS BESIDE THE DESCRIPTION, AND THAT IS THE SECOND ANSWER TO IT.

   The picture puts `50 pcs` at the top right of the card, and at its 288px that leaves the NAME
   190. This column is 223 at a 1280 desktop and 183 on a 1024 iPad, where the same arrangement
   left the name 71px - `Floor Displ…`, which is the selector failing at the one thing it does.
   So the name still takes its whole line.

   BUT IT MAY NOT SHARE A ROW WITH THE FIGURE EITHER, WHICH IS A FAULT THIS PAGE SHIPPED WITH AND
   2026-09-23 SHOT. The quantity was row 3 column 3 and `.cwamt` was row 3 columns 1-2; the money
   is nowrap tabular by design, so once the card was narrow enough the figure simply RAN OUT of
   its two tracks and painted over the quantity - a 1024 iPad read `USD 466.000 pcs`, which is a
   figure that is not the figure. A money string overprinting anything is the one defect on a
   costing page that costs real money, so the arrangement is now one that cannot produce it: the
   FIGURE has the whole width of the card to itself, and the quantity moved up beside the
   DESCRIPTION - the line that already clamps and already ellipsises, and can therefore give up
   48px without losing an identity. It is also where the drawing puts it.
   ============================================================================================== */
.cwpcs{grid-area:2 / 3;justify-self:end;align-self:start;font-size:var(--t-xs);color:var(--ink3);
  white-space:nowrap;font-variant-numeric:tabular-nums}
.cwsub{grid-area:2 / 2 / 3 / 3;min-width:0;font-size:var(--t-xs);color:var(--ink3);line-height:1.4;
  overflow:hidden;overflow-wrap:anywhere;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}
/* THE FIGURE TAKES ITS OWN LINE AND NOT THE END OF THE NAME'S. `USD 1,026.00` is 96px of
   tabular digits; beside a name in a 183px column it leaves 60px for the name, which is the
   layout that produced `Fl…`. The picture prints `$466` - three characters - and this app prints
   the currency on every figure by the owner's own ruling, which is a fact about this app and not
   a thing to design around by hiding it. So the name gets the width and the money gets a line. */
/* AND IT IS RIGHT-ALIGNED, WHICH IS THE DRAWING'S OWN ARRANGEMENT. The picture stacks `$466` over
   `$9.32/unit` against the card's right edge, so the two figures share an edge and read as one
   column of money; `.cwper` on the line below is already `margin-left:auto`, so this is the half
   of that pair that was not keeping it. */
.cwamt{grid-area:3 / 1 / 4 / 4;justify-self:end;align-self:end;margin-top:2px;
  font-size:var(--t-body);font-weight:var(--w-heavy);
  color:var(--ink);white-space:nowrap;font-variant-numeric:tabular-nums}
.cwfoot2{grid-area:4 / 1 / 5 / 4;display:flex;align-items:center;flex-wrap:wrap;gap:2px var(--sp-2);
  margin-top:var(--sp-1)}
.cwper{margin-left:auto;font-size:var(--t-micro);color:var(--ink3);white-space:nowrap;
  font-variant-numeric:tabular-nums}
/* ==============================================================================================
   THE COSTING STATUS OF ONE LINE, AND WHERE THE THREE WORDS COME FROM.

   There is no per-line flag on this record and this slice deliberately does not add one: a column
   nobody can set is a column that goes stale. The three states are read off the rows themselves,
   and each is a state `costs.js` already keeps on purpose:

     Not priced    the line has no cost rows at all.
     In progress   it has rows, and they come to nothing. `readCostRow` keeps a name with no
                   figures on it BECAUSE that says something a missing row does not - "this was
                   considered and is not being charged for" - so a line in that state is a line
                   somebody has started and not finished.
     Complete      the line comes to a figure.

   THE PICTURE SHOWS `Not priced` ON A LINE READING $466, which is the mockup's placeholder text
   rather than a rule; a line carrying four hundred and sixty-six dollars is priced. The words are
   his; which line gets which is the data's.
   ============================================================================================== */
.cwstate{display:inline-flex;align-items:center;gap:6px;font-family:var(--ui);
  font-size:var(--t-micro);font-weight:var(--w-semi);color:var(--ink3)}
.cwstate::before{content:"";flex:0 0 auto;width:7px;height:7px;border-radius:50%;
  background:var(--ink3)}
.cwstate.cwdone{color:var(--ok)}
.cwstate.cwdone::before{background:var(--ok)}
.cwstate.cwpart{color:var(--ship)}
.cwstate.cwpart::before{background:var(--ship)}

/* ==============================================================================================
   THE LINE ITEMS STRIP — §1 OF HIS REDESIGN, 2026-09-26. `/pricing/<key>` ONLY.
   ==============================================================================================
   «Remove the left-side Line Items panel. Place a compact, full-width Line Items strip below the
   project heading and above the pricing workspace. Show items in one horizontal row, without
   wrapping. Each item displays its number, name, quantity, and pricing status. Highlight the
   selected item with a pale blue background and blue border. When items overflow, provide
   left/right navigation arrows. Disable the relevant arrow at either end; hide arrows when
   everything fits. Support touch swiping on iPad and horizontal trackpad scrolling. Keep the
   selected item visible within the strip.»

   ----------------------------------------------------------------------------------------------
   WHAT THE STRIP GIVES BACK, MEASURED, BECAUSE THAT IS THE WHOLE POINT OF §3. The four content
   widths this app actually gets are 362 / 594 / 732 / 1012 (the note above `.cwork` derives all
   four from the rail and `.wrap`). With the left column gone the grid is two tracks:

     container   device                  centre column      cost breakdown      was
     362         390 phone               362  (one column)   stacked cells       362, stacked
     594         834 iPad portrait       594  (one column)   table               594, table
     732         1024 iPad LANDSCAPE     526  (732−190−16)   TABLE               ~423, stacked
     1012        1280 desktop            759  (1012−233−20)  table               ~518, table

   THE 1024 IPAD IS THE CELL THAT CHANGES CHARACTER AND IT IS THE ONE THE OWNER WORKS ON. The
   note above `.cwork` recorded the old answer honestly - «what he cannot have at 732 is the
   table with three columns» - and §1 is what buys it: two tracks instead of three puts 526px in
   the middle, the Cost Breakdown's own floor is 480 of `.cwc`, and it is a table again. Nothing
   in `@container cwc` moved to achieve that; the column simply got wide enough to pass it.

   ----------------------------------------------------------------------------------------------
   A ROW THAT SCROLLS AND NEVER WRAPS, AND THE ARROWS ARE THE ENHANCEMENT ON TOP OF IT. The box
   is `overflow-x:auto` with `overscroll-behavior-x:contain`, which is a finger, a trackpad, a
   shift-wheel and the keyboard all working with no script - the same answer the owner gave for
   the bottom bar («بدي الشريط ينسحب يمين و شمال»). `scroll-snap-type:x proximity` so a swipe
   lands on a card edge rather than halfway through one. `views/cost.js` draws the two arrows
   `hidden disabled`, because whether everything fits is a measurement a server cannot take; the
   script reveals them where there is something to scroll. See `stripArrow`.
   ============================================================================================== */
.cwsel{margin:0 0 var(--sp-4)}
.cwstrip2{display:flex;align-items:stretch;gap:var(--sp-2);min-width:0}
/* THE STRIP CARDS, IN ONE ROW, EACH KEEPING ITS OWN WIDTH. `grid-auto-flow:column` with a fixed
   track rather than flex, so four cards read as four at every width and the fifth is the one you
   scroll to - the same shape the narrow left-column layout already used, made the only shape. */
.cwsel > .cwstrip2 > .cwlist{flex:1 1 auto;display:grid;gap:var(--sp-2);
  grid-auto-flow:column;grid-auto-columns:minmax(12.5rem,1fr);grid-template-columns:none;
  overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;
  overscroll-behavior-x:contain;scroll-snap-type:x proximity;
  /* ============================================================================================
     THE FOCUS RING AND THE SELECTED CARD'S OWN RING ARE 1px OUTSIDE THE CARD, and a scroller
     clips at its padding box - so without this the ring on the first and last card is shaved.
     Two pixels of padding and the same two given back as a negative margin: the strip measures
     exactly what it did and nothing is cut.

     AND `scroll-padding-inline` IS THE OTHER HALF OF THAT, MEASURED RATHER THAN GUESSED. A card
     is `scroll-snap-align:start`, so its snap position is its own start edge against the
     scrollport's - and the padding puts the first card's edge 2px into the scrollable content,
     which made Chrome snap a freshly-loaded strip to `scrollLeft: 2`. Two pixels is invisible
     and the consequence was not: `syncStrip` reads «am I at the left-hand end» off that number,
     so the back arrow arrived ENABLED on a strip nobody had scrolled. The inset tells the snap
     algorithm about the padding, the first card snaps to 0, and the arrow is right again.
     ========================================================================================== */
  padding:2px;margin:-2px;scroll-padding-inline:2px}
/* ==============================================================================================
   ONE CARD: THE NUMBER, THE NAME, HOW MANY, AND WHERE IT STANDS — four facts, two lines.

   200px IS THE TRACK (12.5rem) AND IT WAS MEASURED, not taken off the picture. The drawing's
   cards are 298px in 1584 of window; this app's content width at 1280 is 1012, so the same
   proportion is 190 - and at 200 a real item name («Freestanding floor display») clamps to two
   lines with the quantity and the status under it, which is four facts in 88px of height. Five
   of them fit inside 1012 without scrolling, which is what an RFQ of this shop's usual size is.

   NO MONEY ON THIS CARD, which is `selector`'s own note in `views/cost.js` and is §1 read
   literally: his four facts, and the two figures the card used to carry are the pair he
   photographed disagreeing with the Cost Summary beside them.
   ============================================================================================== */
.cwsel .cwitem{grid-template-columns:auto minmax(0,1fr);gap:2px var(--sp-2);
  align-content:start;padding:var(--sp-2) var(--sp-3);scroll-snap-align:start}
.cwsel .cwno{grid-area:1 / 1 / 2 / 2;align-self:center;width:auto;min-width:22px;height:20px;
  padding:0 5px;border-radius:var(--r-xs);font-size:var(--t-micro)}
.cwsel .cwnm{grid-area:1 / 2 / 2 / 3;-webkit-line-clamp:1}
.cwsel .cwpcs{grid-area:2 / 1 / 3 / 2;justify-self:start;align-self:center}
.cwsel .cwfoot2{grid-area:2 / 2 / 3 / 3;margin:0;justify-content:flex-end}
/* ==============================================================================================
   THE TWO ARROWS. 44px SQUARE, WHICH IS THE SHELL'S FLOOR FOR A THING A FINGER PRESSES.

   `flex:0 0 auto` and `[hidden]{display:none}` is the whole of "hide arrows when everything
   fits": the pair is drawn hidden, the script un-hides it, and the strip beside it simply takes
   the room back. A `disabled` arrow keeps its place and goes quiet rather than disappearing at
   the end of the scroll - an arrow that vanished at one end would move the strip sideways under
   the finger that was scrolling it, which is the fault this app already fixed once on the stage
   chip row.
   ============================================================================================== */
.cwarr{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:44px;min-height:44px;align-self:center;padding:0;border-radius:var(--r-md);
  border:1px solid var(--rule);background:var(--card);color:var(--ink2);
  font:inherit;cursor:pointer}
.cwarr[hidden]{display:none}
.cwarr:hover:not([disabled]){border-color:var(--priRule);color:var(--priInk)}
.cwarr[disabled]{color:var(--ink3);opacity:.45;cursor:default}
.cwarr svg{width:18px;height:18px}

/* ==============================================================================================
   CENTRE — ONE LINE'S COSTING, AND NEVER TWO.
   ==============================================================================================
   EVERY LINE'S EDITOR IS IN THE DOCUMENT AND EXACTLY ONE IS ON THE SCREEN, which is not the same
   claim and is the only shape that is safe here. This form POSTS THE WHOLE COSTING - `saveCost`
   replaces every live row with what arrived - so a page that rendered only the chosen line would
   silently delete every other line's rows the first time anybody pressed Save. The boxes have to
   be in the body; what the owner asked for is that only one of them is ever in front of you.
   `display:none` takes the others off the screen, out of the tab order and out of the accessible
   tree, and leaves them in the submission, which is exactly the division wanted.
   ============================================================================================== */
.cwline{display:none}
.cwline.cwon{display:block}
/* THE LINE'S OWN HEAD: what it is on the left, how many and the way to the brief on the right. */
.cwtop{display:flex;align-items:flex-start;flex-wrap:wrap;gap:var(--sp-3);
  margin:0 0 var(--sp-4)}
/* THE NAME, THE QUANTITY AND THE WAY TO THE BRIEF SHARE ONE LINE where there is room for three.
   A 220px basis plus a 60px quantity plus a 200px control is 504 in a 511px column, which is
   inside the rounding - so the control wrapped to a line of its own and the header cost 44px it
   did not need. The basis is what gives way, because the name and the description below it are
   the two things allowed to wrap. */
.cwtt{flex:1 1 150px;min-width:0}
.cwtt > h3{margin:0;font-family:var(--ui);font-size:var(--t-title);font-weight:var(--w-heavy);
  letter-spacing:var(--ls-title);color:var(--ink);overflow-wrap:anywhere}
.cwtt > h3 > .cwhash{font-size:var(--t-sm);font-weight:var(--w-bold);color:var(--ink3);
  margin-right:6px;font-variant-numeric:tabular-nums}
.cwtt > p{margin:2px 0 0;font-size:var(--t-xs);color:var(--ink3);overflow-wrap:anywhere}
.cwqty{flex:0 0 auto;text-align:right}
.cwqty > b{display:block;font-family:var(--ui);font-size:var(--t-micro);
  font-weight:var(--w-bold);letter-spacing:var(--ls-caps);text-transform:uppercase;
  color:var(--ink3)}
.cwqty > span{display:block;font-family:var(--ui);font-size:var(--t-body);
  font-weight:var(--w-heavy);color:var(--ink);white-space:nowrap;
  font-variant-numeric:tabular-nums}
/* THE WAY INTO THE BRIEF, AND IT IS A LINK TO A FRAGMENT OF THIS PAGE. A same-document fragment
   is the one navigation that does not reload, so opening the references cannot cost a keystroke -
   which is the whole requirement. See `.cwrefs`. */
.cwtop > .cwbtn{flex:0 0 auto}
.cwbtn{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);
  min-height:40px;padding:0 var(--sp-3);border-radius:var(--r-md);
  border:1px solid var(--rule);background:var(--card);color:var(--ink);
  font:inherit;font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-semi);
  text-decoration:none;cursor:pointer;white-space:nowrap}
.cwbtn:hover{border-color:var(--priRule);color:var(--priInk)}
.cwbtn svg{width:16px;height:16px;flex:0 0 auto}
/* THE ONE PRIMARY ON THE PAGE, FLAT NAVY. `.card button` fills with `--priGrad`, which is this
   app's own button; the picture's is flat and the owner's note for this round says no gradients,
   so the two controls that are his drawing say so in one token. */
.cwpri{background:var(--pri);border-color:var(--pri);color:var(--priOn)}
.cwpri:hover{background:var(--pri2);border-color:var(--pri2);color:var(--priOn)}

/* ----------------------------------------------------------------------------------------------
   THE COST BREAKDOWN — ONE TABLE, THREE BODIES.
   ----------------------------------------------------------------------------------------------
   The picture draws one table with a type badge on every row; this record keeps three sections
   under every line, because that is the shape the owner asked for by name («اي ضيفن كاقسام») and
   the shape `rfq_cost_rows.section` stores. Those are the same thing written two ways: one
   `<table>` with one `<tbody>` per section renders as the picture's single grid, keeps the
   sections in the markup where the reader and the script both already look for them, and needs
   not one byte of new machinery. `views/rows.js` finds a section by `[data-sec]` and a row by
   `[data-row]` and cares about neither element's tag name.
   ------------------------------------------------------------------------------------------- */
.cwtab{overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;
  overscroll-behavior-x:contain;border-radius:var(--r-md);border:1px solid var(--rule)}
/* ==============================================================================================
   `table-layout:fixed` AND SEVEN PERCENTAGES, WHICH IS THE WHOLE OF WHY ALL SEVEN COLUMNS FIT.

   Left to itself a table measures its content, and the content here is a `<select>` holding
   seventy-six material names - so the Description column took whatever it liked and the Total and
   the remove control were pushed off the end of the scroller, which is where the first cut of
   this page left them. Fixed layout takes every width from the first row and does no measuring at
   all, so a long material name shortens nothing but itself.

   ==============================================================================================
   RE-SPLIT ON 2026-09-23, AND BOTH REASONS ARE THINGS THE PAGE WAS GETTING WRONG.

   1. THE DESCRIPTION WAS CUTTING WORDS IN HALF. Measured at a 1280 desktop, that box had 79px of
      interior and `Powder Coating` is 96px of ink at `--t-sm`: the owner's own screenshot shows
      `Powder Coa` and `Acrylic 3mn`. A pricer who cannot read back what they typed cannot check
      a costing, and this is the one column that holds words somebody wrote rather than a figure
      a box scrolls. It was 19% and is 24%.

   2. THE TYPE CELL NOW HAS TO HOLD A CONTROL AND NOT ONLY A PILL. `+ Add Cost → Other` adds a row
      that ASKS which of the three it is, in the cell where the badge sits (see `otherRow` in
      `views/cost.js`), and a `<select>` saying `Outsource` wants its text plus an arrow plus its
      own padding. It was 15% - in which the 66px `Outsource` PILL did not fit either, which is
      why that badge touched both edges of its cell in the same screenshot - and is 19%.

   The seven are sized from the narrowest column this page ever gets - 525px at a 1280 desktop,
   less the panel's own 32 and the scroller's border - which is 481px of table:

     Type  19%  91px   `Outsource` is 66px of pill, and the `Other` row's control is 80px
     Desc  24% 115px   a material name, or the picker. 107 of box, 97 of interior, which is
                       `Powder Coating` with a pixel to spare - the measurement this re-split is
     Unit  11%  53px   `sheet` is 34px of ink and a box plus a cell now spends 16
     Qty   10%  48px   `12.5` is 25
     Cost  11%  53px   `30.50` is 31; `1234.56` is 50 and scrolls its own box, which a
                       right-aligned figure being typed into may do and a NAME may not
     Total 13%  63px   `USD 264.00` breaks at its own space to `USD` over `264.00`, which costs
                       no height at all - the row is already as tall as the 40px boxes beside it
     Acts  12%  58px   one 34px glyph, and the word ACTIONS on one line above it

   THE BOXES SPEND FOUR PIXELS A SIDE AND THE CELLS FOUR, which was five and five until this
   re-split. Fourteen pixels came off the chrome and went into the two columns above; a cell of
   this table is the tightest control in the app and the padding is what it can afford to give
   up. The 40px height, which is what a finger presses, is not.

   EVERY ONE OF THOSE SEVEN WAS MEASURED IN A BROWSER AND NOT ESTIMATED, at 834 and at 1280,
   which are the two widths this page is a table at. Two of them were wrong on the first cut by
   three and six pixels - `ACTIONS` and the `Outsource` pill - and three or six pixels of
   overflow on a fixed table is the last column sliding under the next one.

   `min-width:430px` IS THE FLOOR UNDER ALL OF THAT and the box around it scrolls, so the band
   between 430 and 480 of middle column - which no device this app is measured at actually lands
   in - degrades to a scroller rather than to overlap. Under 480 it is not a table at all.
   ============================================================================================== */
.cwt{width:100%;min-width:430px;table-layout:fixed;border-collapse:collapse;
  font-family:var(--ui);font-size:var(--t-sm)}
.cwt th{font-family:var(--ui);font-size:var(--t-micro);font-weight:var(--w-bold);
  letter-spacing:var(--ls-caps);text-transform:uppercase;color:var(--ink3);
  text-align:left;line-height:1.25;padding:6px 4px;
  background:var(--paper2);border-bottom:1px solid var(--rule);vertical-align:bottom}
.cwt th.cwsum2,.cwt th.cwqt,.cwt th.cwuc{text-align:right}
.cwt th.cwdo{text-align:right}
/* ==============================================================================================
   RE-SPLIT AGAIN ON 2026-09-26 — §3: «Give Description the largest share of table width. Keep
   Type and Delete compact, while allowing comfortable quantity and cost entry.»

   MOST OF §3 IS PAID BY THE TABLE AND NOT BY THE PERCENTAGES, which is worth saying first
   because it is where the room actually came from. §1 took the left column out of the grid, and
   these are measured in Chrome, not derived (`tools/costing-shots.mjs`):

                        1024 iPad landscape      1280 desktop      834 iPad portrait
     centre column      530px (was ~423)         763px (was ~525)  594px (unchanged)
     table width        494px (was: NOT a        719px (was 481)   558px (unchanged)
                        table at all)

   THE 1024 IPAD IS THE CELL THAT CHANGES CHARACTER, and it is the machine the owner works on.
   The Cost Breakdown needs 480px of `.cwc` before it can be a table; it had ~423 and has 530, so
   at an iPad in landscape this page is a table for the first time instead of the stacked
   labelled cells `@container cwc (max-width:479px)` draws. Nothing in that block moved.

   ----------------------------------------------------------------------------------------------
   AND THEN TWO POINTS MOVE ON TOP OF THAT, WHICH IS AS FAR AS THE MEASUREMENTS ALLOW.

     Desc  24 → 25   123px / 180   the largest share, which is what he asked for — and 180px at
                                   a 1280 desktop against the 115 the last re-split fought for.
     Type  19 → 18    89px / 129   «keep Type compact». The `Other` row's `<select>` is the floor
                                   here and it measures 80: 18% leaves 81px of interior at the
                                   narrowest table this page now has, and 17% leaves 74.

   THE OTHER FIVE DID NOT MOVE, AND ONE OF THEM TRIED TO. Every width below was read off a real
   `<th>` at 1024 and at 1280, `scrollWidth` against `getBoundingClientRect().width`, which is
   what says whether the ink fits the box:

     Unit    11      54px /  79   `sheet` is 34 of ink and a box plus a cell spends 16.
     Qty     10      49px /  72   `12.5` is 25.
     Cost    11      54px /  79   `30.50` is 31; `1234.56` is 50 and scrolls its own box, which
                                  a right-aligned figure being typed into may do.
     Total   13      64px /  94   `USD 1,026.00` breaks at its own SPACE to `USD` over
                                  `1,026.00`, and `1,026.00` is 55 of tabular digits that may
                                  never break. `.cwt td` is `overflow-wrap:anywhere`, so a Total
                                  column under 63 does not wrap this figure — it breaks it in
                                  half, which is a money string this page would be lying about.
     Acts    12      59px /  86   ONE POINT CAME OFF THIS COLUMN AND WENT BACK ON. «Keep Delete
                                  compact» is his, and at 11% the cell measured 54px at a 1024
                                  iPad against 58 of `ACTIONS` — a `<th>` does not wrap a single
                                  word, so the heading was painting over the Total beside it.
                                  Found by measuring, not by reading; the point came off Type
                                  instead, which had 5px of slack and this column had none.

   `min-width:430px` BELOW AND THE 480 THRESHOLD IN `@container cwc` ARE BOTH UNCHANGED — this
   moved how 100% is divided and no breakpoint.
   ============================================================================================== */
.cwt th.cwty{width:18%}
.cwt th.cwde{width:25%}
.cwt th.cwun{width:11%}
.cwt th.cwqt{width:10%}
.cwt th.cwuc{width:11%}
.cwt th.cwsum2{width:13%}
.cwt th.cwdo{width:12%}
.cwt td{padding:4px;border-top:1px solid var(--rule2);vertical-align:middle;
  color:var(--ink2);overflow-wrap:anywhere}
.cwt tbody:first-of-type tr:first-child td{border-top:0}
.cwt td.cwnum{text-align:right;white-space:nowrap;font-variant-numeric:tabular-nums}
/* THE TOTAL WRAPS AT ITS OWN SPACE AND NEVER INSIDE THE DIGITS. `USD 264.00` is one line in 88px
   and `USD 1,026.00` is two - `USD` over `1,026.00` - which costs no height at all, because the
   row is already as tall as the 40px boxes beside it. Held to `nowrap` this column would instead
   have slid under the one after it, which is what the first cut of this table did. */
.cwt td.cwsum2{text-align:right;font-weight:var(--w-bold);color:var(--ink);line-height:1.3;
  font-variant-numeric:tabular-nums}
.cwt td.cwdo{text-align:right;white-space:nowrap}
/* EVERY BOX IN A CELL IS THE APP'S OWN CONTROL, ONE STEP DOWN. 46px is the shell's floor for a
   control a finger presses on a form somebody FILLS IN; a cell of a dense table is the shape the
   density pass of 2026-09-21 already answered at 40, which is the bottom of the range his own
   spec for that page names. */
/* EVERY BOX IN A CELL IS 40px, WHICH IS THE BOTTOM OF THE RANGE THE OWNER'S OWN SPEC NAMES and
   the figure the density pass of 2026-09-21 already settled for a dense form ("40-46px
   controls"). 46 is the shell's floor for a form somebody fills in down a single column; a cell
   of a table is the other case, and this is the number that case was given. */
.cwt input,.cwt select{width:100%;min-width:0;font:inherit;font-family:var(--ui);
  font-size:var(--t-sm);min-height:40px;padding:4px;border-radius:var(--r-xs);
  border:1px solid var(--rule);background:var(--paper);color:var(--ink)}
.cwt td.cwnum input{text-align:right}
/* ==============================================================================================
   THE CONTROL AN `Other` ROW ASKS ITS QUESTION WITH, IN THE CELL THE BADGE WILL FILL.

   IT IS SIZED AS THE BADGE IT IS ABOUT TO BECOME and that is a measurement before it is a
   reading: `Outsource` at `--t-sm` is 64px of ink and a native select spends about 16 more on its
   own arrow, which does not fit in the 83px of interior a 19% Type column has at a 1280 desktop.
   At `--t-micro` bold - which is the badge's own size and weight - it is 54, the control measures
   80, and the cell holds it. The cell therefore reads the same before and after the question is
   answered, which is the whole argument for asking it here rather than anywhere else.

   NO TRACKING, for `.cwbadge`'s own recorded reason: every other `--t-micro` label in this app is
   `--ls-caps` because it is uppercase and needs the air; these are words in their own case.
   ============================================================================================== */
.cwt select.cwtype{font-size:var(--t-micro);font-weight:var(--w-bold);letter-spacing:0;
  padding:4px 2px}
/* THE TYPE BADGE. Three measured pairs this file already carries and no new colour: green for
   what was bought, blue for what was worked, warm orange for what was bought out - which is the
   picture's own reading and, by luck rather than design, the three tones `--ok`, `--ship` and
   `--risk` were cut for. */
/* THE PILL CARRIES NO EXTRA TRACKING, which is three pixels and is the reason the Type column is
   16% and not 17%. Every other `--t-micro` label in this app is `--ls-caps` because it is
   UPPERCASE and needs the air; this one is a word in its own case inside a coloured box. */
.cwbadge{display:inline-block;padding:3px 6px;border-radius:var(--r-xs);
  font-family:var(--ui);font-size:var(--t-micro);font-weight:var(--w-bold);
  letter-spacing:0;white-space:nowrap;background:var(--waitBg);color:var(--ink2)}
.cwbadge.cwmat{background:var(--okBg);color:var(--ok)}
.cwbadge.cwlab{background:var(--shipBg);color:var(--ship)}
.cwbadge.cwout{background:var(--riskBg);color:var(--risk)}

/* ==============================================================================================
   THE MATERIAL PICKER — §4 AND §5 OF HIS REDESIGN, 2026-09-26. A FLOATING POPOVER.
   ==============================================================================================
   «Use a floating searchable popover approximately 520–560px wide, constrained to the available
   viewport. Its width must be independent of the Description input width. Render it above the
   table without stretching rows or changing page height. Avoid clipping by table/card overflow
   containers. Anchor it to the active material field, reposition on scroll/resize, and open
   upward when insufficient space exists below. Keep search visible at the top and scroll only
   the results, with roughly 4–5 compact results visible. Each result displays a clear material
   name, smaller specifications/code, and a separate price-and-unit column.»

   ----------------------------------------------------------------------------------------------
   IT IS AN ENHANCEMENT AND THE PAGE PRICES A JOB WITHOUT IT. Not one declaration in this block
   is reachable with `script-src` blocked: there is no `.cwpop` in the markup at all. The control
   the server draws is the `<select name="c1m1_pick">` this app has always drawn, which is what
   `views/cost.js`' header block is about and what the datalist ruling of 2026-09-13 protects.

   THERE IS ONE POPOVER FOR THE WHOLE PAGE AND IT LIVES ON `<body>`, which settles three of his
   requirements at once and one of this page's own:

     «avoid clipping by table/card overflow containers»   `.cwtab` is `overflow-x:auto` and
       `.cwork` is a query container - and a container is a containing block for its
       `position:fixed` descendants, so a popover drawn inside the row would be positioned
       against the workspace and clipped by the table's scroller. On `<body>` it is neither.
     «without stretching rows or changing page height»     `position:fixed` is out of flow. The
       table is the width and height it was with the picker shut.
     «its width must be independent of the Description input width»   it is: 520–560, set here,
       against a cell that is 120px at a 1024 iPad.
     AND THE PAGE WEIGHT, which is this page's own measured constraint (`onePicker` in
       `views/cost.js`: repeating the catalogue per row was 1,368 copies of 76 facts, 62KB). One
       popover, built in the browser, is nought bytes on the wire and one element in the document
       however many rows the costing has.

   520–560 AND NOT A FIXED NUMBER, because he wrote a range and the range is doing work. The
   script asks for 560 and takes what the viewport can give, down to a phone: `min(560, vw − 16)`
   is 560 at 1280 and at 1024, 560 at an 834 iPad, and 374 on a 390 phone. `max-width` here is
   the ceiling stated in CSS as well so a future positioning bug cannot paint one wider than he
   asked for; `width` and `left` and `top` are the script's, because only it has measured the
   field. See `positionPop` in `views/rows.js`.

   4–5 RESULTS VISIBLE, AS A `max-height` ON THE SCROLLER AND NOT ON THE POPOVER. A compact
   result row is 44px - the shell's floor for a thing a finger presses, and his «controls
   comfortable for touch» - so five of them is 220 and that is what the list is capped at. The
   count line above it does not scroll, which is «keep search visible at the top and scroll only
   the results» read against the one box there is: the search itself is the Description field the
   popover is anchored to, one row above.
   ============================================================================================== */
.cwpop{position:fixed;z-index:60;box-sizing:border-box;max-width:560px;
  border-radius:var(--r-md);border:1px solid var(--rule);background:var(--card);
  box-shadow:var(--e-3);overflow:hidden;font-family:var(--ui)}
.cwpop[hidden]{display:none}
/* THE COUNT LINE — the kind on the left, how many matched on the right. It is the picture's own
   header row, and it is the popover's only fixed furniture. */
.cwpoph{display:flex;align-items:baseline;gap:var(--sp-3);margin:0;
  padding:var(--sp-2) var(--sp-3);border-bottom:1px solid var(--rule2);
  background:var(--paper2);font-size:var(--t-micro);color:var(--ink3)}
.cwpoph[hidden]{display:none}
.cwpoph > b{min-width:0;font-weight:var(--w-semi);color:var(--ink2);overflow-wrap:anywhere}
.cwpoph > span{margin-left:auto;flex:0 0 auto;white-space:nowrap}
/* THE RESULTS, AND THEY ARE THE ONLY THING THAT SCROLLS. */
.cwpopl{max-height:220px;overflow-y:auto;-webkit-overflow-scrolling:touch;
  overscroll-behavior-y:contain}
/* ==============================================================================================
   ONE RESULT: THE NAME, AND THE PRICE AND UNIT IN A COLUMN OF THEIR OWN.

   `dir="auto"` IS ON THE NAME AND ON NOTHING ELSE, which is §5 («handle Arabic text correctly
   and isolate dimensions, codes and currency values so mixed RTL/LTR content remains readable»).
   A material on the owner's list reads «لوح خشب ابيض 18ملم -قياس - 183 * 366شمسان تركي» - Arabic
   with Latin digits, an asterisk and a hyphen inside it - and the bidi algorithm gets that right
   on its own once the paragraph direction is the text's own. The PRICE is in its own element in
   its own track, so `USD 43.50` is never reordered by the name beside it: the isolation his
   requirement asks for is a separate box, not a control character.

   THE PRICE COLUMN IS `flex:0 0 auto` AND FIRST IN THE FLEX ORDER, which is what puts it on the
   reading-start edge for an Arabic name and on the left for a Latin one without two rules.
   ============================================================================================== */
.cwpopr{display:flex;align-items:center;gap:var(--sp-3);width:100%;min-height:44px;
  padding:var(--sp-2) var(--sp-3);border:0;border-top:1px solid var(--rule2);
  background:transparent;color:var(--ink);font:inherit;font-family:var(--ui);
  font-size:var(--t-sm);text-align:start;cursor:pointer}
.cwpopl > .cwpopr:first-child,.cwpopl > .cwpopg:first-child + .cwpopr{border-top:0}
.cwpopr:hover,.cwpopr:focus{background:var(--shipBg);outline:0}
.cwpopr:focus-visible{box-shadow:inset 0 0 0 2px var(--ship)}
.cwpopr > .cwpopn{min-width:0;flex:1 1 auto;font-weight:var(--w-semi);line-height:1.35;
  overflow-wrap:anywhere}
/* ==============================================================================================
   AND THERE IS NO SECOND LINE UNDER THE NAME, WHICH IS A FACT ABOUT THIS APP'S DATA.

   §4 asks each result for «a clear material name, smaller specifications/code, and a separate
   price-and-unit column», and his drawing shows `8 ملم · 1.22 × 2.44 · ستار` under a name. The
   `materials` table is four columns — kind, name, unit, price. There is no specifications column
   and no code column, on any row, and his own instruction two lines later is «Do not copy mockup
   values or invent missing specifications». On the real list those dimensions and codes are
   INSIDE the name: «لوح خشب لامايكا -ابيض -بيليبان تركي 5 -ملم -قياس - 280 * 210 / WDST / 500-7»
   is one string the shop typed, and splitting it on a guess would be inventing the split.

   WHAT IS DRAWN INSTEAD IS THE KIND, as a heading over each run of results (`.cwpopg` below) —
   which is the other thing his drawing puts there, is the same window the native `<optgroup>`
   already draws, and is a fact this app really holds. The day `materials` grows a spec column,
   the second line is one element in `popRow` and one rule here.
   ============================================================================================== */
.cwpopr > .cwpopp{flex:0 0 auto;text-align:start;
  font-weight:var(--w-heavy);white-space:nowrap;font-variant-numeric:tabular-nums}
.cwpopr > .cwpopp > em{display:block;font-style:normal;font-size:var(--t-micro);
  font-weight:var(--w-reg);color:var(--ink3);white-space:normal;overflow-wrap:anywhere}
/* A GROUP HEADING INSIDE THE LIST — one per kind, the same windows the native `<optgroup>` draws
   and the same order, so the two paths present the owner's list identically. */
.cwpopg{padding:var(--sp-2) var(--sp-3) 4px;background:var(--paper2);
  font-size:var(--t-micro);font-weight:var(--w-bold);letter-spacing:var(--ls-caps);
  text-transform:uppercase;color:var(--ink3);overflow-wrap:anywhere}
/* NOTHING MATCHED. §5 asks for «clear loading and empty-result states» and this is the one of
   the two that exists: the options are already in the document, the server wrote them, nothing
   is fetched and nothing is awaited. A spinner here would be a control that can never be seen,
   which is the one thing this codebase refuses to draw. See `renderPop` in `views/rows.js`. */
.cwpope{padding:var(--sp-4) var(--sp-3);font-size:var(--t-sm);color:var(--ink3);text-align:center}

/* THE ROW'S OWN REMOVE, WHICH STILL ASKS ONCE. It is the same `<details>` and the same two taps
   the form has had since the owner asked for a `-`; what changed is that it lives in an ACTIONS
   cell instead of under the row, so the cell is what is narrow rather than the control. */
.cwt details{display:inline-block;position:relative}
.cwt summary{display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:40px;border-radius:var(--r-xs);color:var(--ink3);cursor:pointer;
  list-style:none}
.cwt summary::-webkit-details-marker{display:none}
.cwt summary:hover{background:var(--lateBg);color:var(--late)}
.cwt summary svg{width:15px;height:15px;display:block}
.cwt details[open] > button{position:absolute;z-index:5;right:0;top:calc(100% + 4px);
  min-width:11rem;padding:var(--sp-2) var(--sp-3);border-radius:var(--r-sm);
  border:1px solid var(--rule);background:var(--card);box-shadow:var(--e-3);
  font:inherit;font-family:var(--ui);font-size:var(--t-xs);font-weight:var(--w-semi);
  color:var(--late);text-align:left;cursor:pointer;white-space:normal}
/* ==============================================================================================
   AND THE WORKING LINE UNDER EACH ROW IS GONE, WHICH IS THE TABLE EARNING ITS KEEP.

   The stacked form printed `12.5 m2 x USD 3.33 = USD 41.63` under every row, and the argument for
   it was exact: somebody checking a costing with a calculator needs to see WHICH two numbers were
   multiplied, and in a stack of four separately-labelled boxes that was genuinely hard to see.

   In a table the quantity, the unit and the unit cost are three adjacent cells on the same line as
   the total, under headings that name them. The working line would be restating three cells a
   reader is already looking at - the same fact on two surfaces, which is the rule this whole
   redesign is held to. So it is not drawn. `views/rows.js` removes a `p.costnote` from a row it
   changes and finds none, which its own null check has always allowed for.
   ============================================================================================== */

/* ONE `+ Add Cost`, AND THE THREE THINGS IT MAY ADD.
   ----------------------------------------------------------------------------------------------
   The owner asked for one button where there were three. The three are still what the RECORD has,
   so the control opens and offers them - a native `<details>`, which is the fold this codebase
   reaches for when something must be opened before it can be used, and which needs no script, no
   dialog and no round trip to arm. Each entry inside is the same `name="more"` submit button that
   has always added a row to that section, carrying the same `1m` / `1l` / `1o` slot; the server
   is told exactly what it was told before and `views/rows.js` intercepts exactly what it always
   intercepted. One press, one row, in the open. */
.cwadd{position:relative;display:inline-block}
/* 44 AND NOT 40 SINCE 2026-09-24. The ENTRIES inside this menu were raised to the owner's touch
   floor on the 23rd and the thing you press to REACH them was left at 40 - so all three
   `.cwadd` summaries in this app (`+ Add Cost`, `Request action`, and the `⋯`) measured 40px
   tall in a real browser at every width, the `⋯` at 45x40. `min-width` is the other half of the
   same floor: a 44px-tall target 20px wide is not one, and `⋯` is two characters. */
.cwadd > summary{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);
  min-height:44px;min-width:44px;padding:0 var(--sp-4);border-radius:var(--r-md);
  background:var(--pri);color:var(--priOn);font-family:var(--ui);font-size:var(--t-sm);
  font-weight:var(--w-bold);cursor:pointer;list-style:none;white-space:nowrap}
.cwadd > summary::-webkit-details-marker{display:none}
.cwadd > summary:hover{background:var(--pri2)}
/* 45 AND NOT 5 SINCE 2026-09-24 - the SECOND thing that made an open menu unpressable, found on
   the costing workspace while the first was being measured. `.nav` - the phone's bottom bar - is
   `position:fixed; z-index:40`, so a menu long enough to reach the foot of the screen was painted
   UNDER it: at 390 the `Other` entry of `+ Add Cost` was 190x44, inside the viewport, and
   `elementFromPoint` at its centre returned the bottom bar. 45 is over the bar and over the rail
   (both 40) and under the flash bar (60), which is the order those three already keep. */
.cwmenu{position:absolute;z-index:45;right:0;top:calc(100% + 6px);min-width:13rem;
  padding:var(--sp-2);border-radius:var(--r-md);border:1px solid var(--rule);
  background:var(--card);box-shadow:var(--e-3)}
.cwmenu button,.cwmenu a{display:flex;align-items:center;gap:var(--sp-2);width:100%;
  min-height:44px;padding:0 var(--sp-2);border:0;border-radius:var(--r-xs);background:none;
  color:var(--ink);font:inherit;font-family:var(--ui);font-size:var(--t-sm);
  font-weight:var(--w-semi);text-align:left;text-decoration:none;cursor:pointer}
.cwmenu button:hover,.cwmenu a:hover{background:var(--paper2)}
.cwmenu .cwdot{flex:0 0 auto;width:9px;height:9px;border-radius:50%;background:var(--ink3)}
.cwmenu .cwdot.cwmat{background:var(--ok)}
.cwmenu .cwdot.cwlab{background:var(--ship)}
.cwmenu .cwdot.cwout{background:var(--risk)}
/* ==============================================================================================
   THE DESTRUCTIVE ENTRY AT THE FOOT OF A MENU — 2026-09-25, and it is two declarations of rule
   and ink for a control that has now lived in three places in two days.

   «بدي اعمل ال delete request من ضمن قائمة request action». `Delete Request` came out of the red
   outlined button §1 asked for and went into `Request action`, at the bottom, «kept visually apart
   from the workflow moves above it». That separation is what this rule IS: a hairline and the
   late-red ink, so the one irreversible entry in a menu of workflow moves does not read as the
   fifth of five.

   `> a.gone` AND NOT A NEW CLASS, WHICH IS THE WHOLE REASON IT IS TWO RULES AND NOT SEVEN.
   `gone` is already this app's word for a destructive link - `.acts > a.gone` and `.cmenu > a.gone`
   have both dressed it that way - and `deleteOpener` has emitted exactly that class since it was
   written. So the BENCH's `Delete this request`, which has sat unmarked at the foot of the `⋯`
   since the owner moved it there on the 24th, gets the same separation from the same bytes. One
   idiom, one act, two cards.

   IT IS NOT `.cwdel`, WHICH IS GONE. The red outlined pill below was seven declarations buying a
   44px filled-outline button; an entry in a menu is already 44px tall by `.cwmenu a` above and
   already has its hit target. What was needed was the SEPARATION and the colour, which is this.

   THE TOUCH FLOOR SURVIVES THE PADDING. `.cwmenu a` is `min-height:44px` and this file is
   `box-sizing:border-box` throughout, so the 8px of `padding-top` and the 1px border come out of
   the content box and the TARGET stays 44 - measured with `elementFromPoint` at 390/834/1024/1280
   on both cards rather than reasoned about, which is the standing rule for anything in a
   `.cwmenu` since the two faults of 2026-09-24.

   AND THE RULE IS INSET BY `.cwmenu`'s OWN 8px OF PADDING, which is what makes it read as a menu
   divider rather than as a box drawn round the last item.
   -------------------------------------------------------------------------------------------- */
.cwmenu > a.gone{margin-top:var(--sp-2);padding-top:var(--sp-2);
  border-top:1px solid var(--rule2);border-radius:0;color:var(--late)}
.cwmenu > a.gone:hover{background:var(--lateBg);color:var(--late)}

/* ==============================================================================================
   THE SPLIT BUTTON AND THE DELETE, AT THE FOOT OF THE CREATIVE REQUEST'S OVERVIEW - 2026-09-25.

   THIS IS THE ONE BLOCK THE WHOLE SLICE ADDS, AND IT IS ADDED RATHER THAN AVOIDED BECAUSE THE
   TWO CONTROLS IT DRAWS CANNOT BE DRAWN OUT OF WHAT WAS HERE. Everything else in §1 - the status
   bar, the latest delivery, the three removals - reuses vocabulary this file already had, which
   is why they cost it nothing. These two do not:

     THE MAIN HALF has to be a PILL that reads as the primary act - `.cwadd > summary` is exactly
     that pill and exactly that navy, and it cannot be used, because a `<summary>` opens a
     `<details>` and this half must NAVIGATE. There is no other 44px filled control in this file
     that is an anchor.

     `Delete Request` has to be a RED OUTLINED BUTTON, by name, because the owner asked for one
     after living with the alternative. The nearest thing here is `.acts > a.gone` - a 24px grey
     text link that turns red on hover - and re-shipping that is re-shipping the complaint: he
     moved Delete into the `⋯` on the 24th, could not find it, and has now asked for it back as
     something that looks like a button. `.pchip.iss` is red and is a SUBMIT inside a panel; this
     is the LINK that opens that panel.

   SO: two class names, eight declarations of colour and box, and 24px of height bought for both
   controls. `app.css` was af9049dc35828fd7f04ab4db38ec85ad779753fb62cb92dd5d3836a18bb339a7 before
   this block and the eleven hand-pinned asserts have been re-pinned by hand with the reason above
   each one. Nothing that existed was changed: every selector here is new, so no other page in the
   app renders one byte differently.

   THE TOUCH FLOOR IS THE POINT OF HALF OF IT. `.acts > a` is `min-height:24px`, which is what
   `+ Attach`, `Send to pricing` and `Send to quotation` have measured in a real browser since the
   action row shipped - under the owner's own 44px floor, on the row that carries the two most
   consequential presses on the page. Both controls here are 44 and are measured at 390, 834, 1024
   and 1280 with `elementFromPoint` at their centres.
   -------------------------------------------------------------------------------------------- */

/* THE TWO HALVES ARE ONE OBJECT AND `margin-left:auto` IS ON THE WRAPPER. That is where
   `.acts > a.gone` used to carry it and where `.acts > details.cwadd` carries it on the bench: it
   absorbs the free space in the wrapping flex row, so the split button and the delete that follows
   it sit at the right-hand end at 1280 and fall onto their own line together at 390. `stretch` is
   what makes the arrow exactly as tall as the half beside it without either being told a height. */
.cwsplit{display:inline-flex;align-items:stretch;margin-left:auto}
/* THE MAIN HALF. `.cwadd > summary`'s pill, as an anchor, with the right-hand corners squared off
   so the arrow completes it. It is NOT a shared rule with that selector: a `<summary>` carries
   `list-style:none` and a `::-webkit-details-marker` reset it needs and this does not, and one
   rule serving both would be a control whose shape depends on which element it happens to be. */
.cwsplit > a.cwgo{display:inline-flex;align-items:center;gap:var(--sp-2);min-height:44px;
  padding:0 var(--sp-4);border-radius:var(--r-md) 0 0 var(--r-md);background:var(--pri);
  color:var(--priOn);font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-bold);
  text-decoration:none;white-space:nowrap}
.cwsplit > a.cwgo:hover{background:var(--pri2)}
/* THE WHOLE PILL WHERE THERE IS NOTHING TO CHOOSE BETWEEN - a reader who holds one of the two
   capabilities gets one button and no arrow, and it must not be drawn with a flat right edge and
   nothing against it. `:only-child` says exactly that and needs no second class from the server. */
.cwsplit > a.cwgo:only-child{border-radius:var(--r-md)}
/* THE ARROW. The same pill, narrower, with the LEFT corners squared and a hairline to divide the
   two halves - which is what makes it read as one object with two targets rather than as two
   buttons that happen to touch. `--priRule` is the same translucent ink the primary surfaces
   already divide with. */
.cwsplit > details.cwadd > summary{padding:0 var(--sp-3);
  border-radius:0 var(--r-md) var(--r-md) 0;box-shadow:inset 1px 0 0 var(--priRule)}
/* AND THIS ONE OPENS UPWARDS, WHICH IS THE MOCK-UP'S OWN DRAWING AND IS ALSO A BUG FIX.

   `.cwmenu` is `top:calc(100% + 6px)` for the two menus that hang off a heading - `+ Add Cost` and
   `Request action` - and there is always page below those. This menu hangs off the LAST row of the
   Overview tab: the reader is standing at the foot of the document when they press it, an open
   `.cwmenu` is absolutely positioned and therefore adds NO height for them to scroll into, and at
   834 the second entry opened below the fold with nowhere left to go. Driven at 390 / 834 / 1024 /
   1280: `details.open` was `true`, `Make a quotation` had an honest 190x44 box, and it was outside
   the viewport - which is the September fault in its third form, found the same way.

   THE OWNER'S OWN PICTURE ALREADY HAD THE ANSWER: §2's mock-up draws the two options ABOVE the
   split button, not below it. `bottom` instead of `top` is the whole change, and there is always
   content above this row - it is the last thing on the tab, under the facts, the delivery and the
   brief - so the opposite failure cannot happen here. */
.cwsplit > details.cwadd > .cwmenu{top:auto;bottom:calc(100% + 6px)}

/* THE RED OUTLINED `Delete Request` PILL STOOD HERE AND CAME OUT ON 2026-09-25, one day after it
   went in. «بدي اعمل ال delete request من ضمن قائمة request action» - the control is an entry in
   `Request action` now, dressed by `.cwmenu > a.gone` up in the menu family where it belongs, and
   `.acts > a.cwdel` had exactly one emitter which no longer exists. A rule left behind for markup
   nothing writes is a rule the next reader has to disprove.

   `.cwdel` ITSELF IS STILL A LIVE CLASS AND IS NOT THIS ONE. `views/cost.js` draws
   `<section class="cwdel">` for the costing page's own delete, and `.cwdel{margin-top:…}` further
   down is that section's. Only the `.acts > a` form went. */
/* AND THE SECOND THING THAT MENU HOLDS — COPY COSTS FROM ANOTHER LINE.
   A COPY AND NEVER A LINK, and the copy happens in the FORM and not in the database: the press
   is a redraw, the rows land in the boxes, and nothing is written until somebody presses Save.
   That is what makes it undoable by not saving. `Add` is the primary and `Replace` is the second
   control, because `Add` cannot destroy anything and the safer of two defaults is the default. */
.cwcopy{display:grid;gap:var(--sp-2);padding:var(--sp-2) 0 0;margin-top:var(--sp-2);
  border-top:1px solid var(--rule2)}
.cwcopy > label{margin:0;font-family:var(--ui);font-size:var(--t-micro);
  font-weight:var(--w-bold);letter-spacing:var(--ls-caps);text-transform:uppercase;
  color:var(--ink3)}
/* 44 ON BOTH, 2026-09-24 - these two are INSIDE `.cwmenu` and were the last controls in it still
   under the floor: the picker measured 40 tall and `Add` / `Replace` 91x40. Same rule, same day,
   same reason as the entries above them. */
.cwcopy select{width:100%;font:inherit;font-family:var(--ui);font-size:var(--t-sm);
  min-height:44px;padding:0 var(--sp-2);border-radius:var(--r-xs);border:1px solid var(--rule);
  background:var(--paper);color:var(--ink)}
.cwcopy > div{display:flex;gap:var(--sp-2)}
.cwcopy > div > button{flex:1 1 0;justify-content:center;min-height:44px;padding:0 var(--sp-2);
  border:1px solid var(--rule);border-radius:var(--r-xs);background:var(--card);color:var(--ink);
  font:inherit;font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-semi);
  cursor:pointer}
.cwcopy > div > button.cwpri{border-color:var(--pri)}

/* THE FOUR CARDS UNDER THE TABLE. Three of them are the section the row above was badged with,
   in the same tone, so a reader who has learnt the green pill meets the green card. */
/* ==============================================================================================
   FOUR ACROSS WHERE FOUR FIT AND TWO WHERE THEY DO NOT, decided by the content rather than by a
   breakpoint - `auto-fit` over a 104px minimum lands on four at a 1280 desktop and two on a 1024
   iPad's middle column, which is the same answer a pair of media queries would have reached with
   two more numbers to keep in step.

   AND THE THREE TINTED CARDS CARRY NO MARK, WHICH IS THE ONE THING ON THIS PAGE THE PICTURE HAS
   AND THIS DOES NOT. It is the currency doing it, measured rather than argued:

     the picture's card    140px wide, `$336.00` on one line beside a 30px icon
     this page's card      120px wide, `USD 336.00` - which is what every figure in this app
                           says, by the owner's own ruling that a costing is in dollars

   With a mark a card has 74px of text and `USD 336.00` is 76 at the size the picture draws it, so
   the figure broke over two lines and the card grew TALLER than the drawing's. Without it the
   figure has 104px, sits on one line, and is `--t-body` - one step under the picture's 17px
   rather than three under it. The tint is what says which section a card is, exactly as the pill
   on the rows above says it; the glyph was saying it a second time.

   THE FOURTH CARD NEVER HAD ONE. `Line Total` is white and bordered in the drawing too, which is
   why all four now read as one set rather than as three and a stranger.
   ============================================================================================== */
.cwfigs{display:grid;gap:var(--sp-2);grid-template-columns:repeat(auto-fit,minmax(104px,1fr));
  margin-top:var(--sp-3)}
/* THE SIDE PADDING IS 8 AND NOT 12, AND IT IS THE SAME MEASUREMENT AS THE BLOCK ABOVE taken again
   on 2026-09-23 after the columns were re-split to the owner's stated band. Four across in a 483px
   table column a card is 115px; at 12 a side that leaves 91px of text and `USD 336.00` is 97, so
   three of the four figures broke over two lines and the row of cards grew to 75px against the
   drawing's 70. At 8 the text has 99 and every figure is on one line. The vertical padding does
   not move - it is what sets the card's height against the picture. */
.cwfig{min-width:0;display:flex;align-items:center;gap:var(--sp-2);padding:10px var(--sp-2);
  border-radius:var(--r-md);background:var(--paper2);border:1px solid transparent}
.cwfig.cwmat{background:var(--okBg)}
.cwfig.cwlab{background:var(--shipBg)}
.cwfig.cwout{background:var(--riskBg)}
.cwfig.cwtot2{background:var(--card);border-color:var(--rule);box-shadow:var(--e-1)}
.cwfig > div{min-width:0}
/* THE CARD LABELS ARE IN THEIR OWN CASE AND NOT IN CAPS, WHICH IS A MEASUREMENT BEFORE IT IS A
   READING OF THE PICTURE. `OUTSOURCING` at `--t-micro` with `--ls-caps` is 88px of ink; a card
   four-across in a 491px column has 71px of text. The same word in its own case with no extra
   tracking is 58, which fits - and is what the owner's drawing shows anyway. */
.cwfig b{display:block;font-family:var(--ui);font-size:var(--t-micro);font-weight:var(--w-semi);
  color:var(--ink3);line-height:1.35;overflow-wrap:anywhere}
/* THE FIGURE WRAPS AT ITS OWN SPACE AND NEVER INSIDE A NUMBER. Ordinary wrapping breaks at the
   space between the currency and the digits, so a four-figure total becomes `USD` over
   `1,026.00` - two lines, both readable. `overflow-wrap:anywhere` is the last resort under that
   and exists for the case a digit group alone is wider than the card, which is where `USD
   336.0` / `0` came from on the first cut of this block: it was the whole string that had to
   fit, and it does not have to. */
.cwfig span{display:block;font-family:var(--ui);font-size:var(--t-body);
  font-weight:var(--w-heavy);color:var(--ink);overflow-wrap:anywhere;line-height:1.3;
  font-variant-numeric:tabular-nums}

/* THE COST / UNIT STRIP, AND THE ONE PLACE THIS PAGE IS ALLOWED TO SAY `Saved`. */
.cwstrip{display:flex;align-items:center;flex-wrap:wrap;gap:var(--sp-3);
  margin-top:var(--sp-3);padding:var(--sp-3);border-radius:var(--r-md);background:var(--paper2)}
.cwstrip > .tico{width:34px;height:34px}
.cwstrip > div{min-width:0}
.cwstrip b{display:block;font-family:var(--ui);font-size:var(--t-micro);
  font-weight:var(--w-bold);letter-spacing:var(--ls-caps);text-transform:uppercase;
  color:var(--ink3)}
.cwstrip .cwbig{display:block;font-family:var(--ui);font-size:var(--t-title);
  font-weight:var(--w-heavy);color:var(--ink);font-variant-numeric:tabular-nums}
/* ==============================================================================================
   `Saved automatically` IS EMPTY UNTIL A SERVER HAS SAID SO, AND THAT IS THE WHOLE RULE.

   There is no state here that means "saving"; the element is written by `views/rows.js` only when
   a POST has come back accepted, and the same element carries the refusal when one has not. With
   the script blocked it is never written at all, which is correct: without a script there is
   nothing to debounce and the way to save is the button that says Save Draft.
   ============================================================================================== */
.cwsave{margin-left:auto;display:flex;align-items:center;gap:var(--sp-2);
  font-family:var(--ui);font-size:var(--t-xs);color:var(--ink3);text-align:right}
.cwsave.cwok{color:var(--ok)}
.cwsave.cwbad{color:var(--late)}
.cwsave em{display:block;font-style:normal;color:var(--ink3);font-size:var(--t-micro)}

/* ==============================================================================================
   A FIGURE THE SCRIPT WORKED OUT IS NOT A FIGURE A SERVER CONFIRMED, AND THE PAGE SAYS SO.

   `views/rows.js` recomputes every hooked figure on this page from the boxes as they stand, so
   after one keystroke not one of them is the number the server sent. That is what the owner
   asked for — the total on the line as he types — and it is also a page full of money nobody has
   filed yet, on a record somebody will be asked to justify. So the form wears `cwlive` from the
   first keystroke until a server has accepted those exact boxes, and every figure inside it is
   drawn in ONE flat `--ink2` — a step back from the `--ink` a confirmed figure wears, and one
   value rather than a step per surface, so that "the script's" has exactly one look. The figure
   that is NOT a step back is the per-unit, which sits at `--ink3` when it is the server's; it is
   marked the same as the rest, because a mark that means two things is not a mark.
   The WORD is the auto-save strip's — `Not saved`, then `Saved
   automatically` when a POST has come back accepted — which is the discipline this whole feature
   is held to and the reason there is no new sentence anywhere on the page.

   `body:not(.nomoney)` IS LOAD-BEARING AND NOT TIDINESS. `body.nomoney .mny` and a two-class
   descendant rule have the same specificity, so without this guard the later of the two — this
   one — would win and repaint the owner's covered figures in readable ink. The cover is the one
   thing on this page that may not be lost to a style, so it is excluded by the selector rather
   than by counting on source order.

   ONE INK STEP AND NOT A SECOND COLOUR, NO WEIGHT CHANGE AND NO UNDERLINE. These figures are
   `font-variant-numeric:tabular-nums`; a weight change would re-measure them on every keystroke
   and jitter the column, and an underline is what `.fset` already means in this app.
   ============================================================================================== */
body:not(.nomoney) .cwork.cwlive .mny{color:var(--ink2)}

/* THE TWO BUTTONS AT THE FOOT — Save Draft left, Submit Costing right. */
.cwfoot{display:flex;align-items:center;flex-wrap:wrap;gap:var(--sp-3);
  margin-top:var(--sp-4);padding-top:var(--sp-4);border-top:1px solid var(--rule2)}
.cwfoot > .cwbtn{min-height:44px;padding:0 var(--sp-5)}
.cwfoot > .cwbtn.cwpri{margin-left:auto}

/* ==============================================================================================
   RIGHT — THE TOTALS, AND HIS OWN NOTES. NOTHING ON IT IS TYPED ANYWHERE ELSE.
   ============================================================================================== */
/* ONE LINE OF THE SUMMARY, AND THE NAME TAKES THE WHOLE WIDTH RATHER THAN SHARING IT.
   The picture's summary column is 300px and this app's is 223; with the figure beside it the name
   had 73px, in which `#2 Window Vinyl` came back as `#2` and an ellipsis. The money has its own
   line under the quantity instead, which is the same answer the line card on the left reached for
   the same reason - and both were measured with a real client's line names in them. */
.cwsrow{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:0 var(--sp-2);
  padding:var(--sp-2) 0;align-items:center}
.cwsrow + .cwsrow{border-top:1px solid var(--rule2)}
.cwmark{grid-area:1 / 1 / 3 / 2;align-self:start;margin-top:2px;display:inline-flex;align-items:center;
  justify-content:center;width:22px;height:22px;border-radius:50%;
  background:var(--paper2);color:var(--ink3)}
.cwmark svg{width:13px;height:13px;display:block}
.cwmark.cwdone{background:var(--okBg);color:var(--ok)}
.cwsrow > .cwnm{grid-area:1 / 2 / 2 / 4}
.cwsrow > .cwsub{grid-area:2 / 2}
.cwsrow > .cwamt{grid-area:2 / 3;margin:0;justify-self:end;font-size:var(--t-sm)}
.cwsrow > .cwper{grid-area:3 / 2 / 4 / 4;margin:0;justify-self:end}
/* THE THREE SECTION TOTALS. `.row`'s rhythm without `.rows`' card, because they are inside one. */
/* THE THREE SECTION TOTALS, AND THE FIGURE NEVER WRAPS. `flex-wrap` on the line means the LABEL
   drops to its own line when the column is too narrow for both, which is the right one of the two
   to break: a figure split across two lines is a figure somebody has to reassemble. */
/* THE LABEL IS A STEP SMALLER THAN THE FIGURE, WHICH BUYS THE LINE. `Materials total` at 13px
   and `USD 1,026.00` beside it is 192px in a 191px column, so the pair wrapped on every row of
   the summary; at 12px the label is 80 and the two sit on one line with room. The FIGURE does not
   move - it is what the column is for. */
.cwtl{display:flex;align-items:baseline;flex-wrap:wrap;gap:2px var(--sp-2);padding:6px 0;
  font-family:var(--ui);font-size:var(--t-xs);color:var(--ink2)}
.cwtl > span{margin-left:auto;font-size:var(--t-sm);font-weight:var(--w-bold);color:var(--ink);
  white-space:nowrap;font-variant-numeric:tabular-nums}
.cwrule{margin:var(--sp-3) 0;border:0;border-top:1px solid var(--rule2)}
/* THE FIGURE THE WHOLE PAGE IS FOR. */
.cwgrand{margin-top:var(--sp-3);padding:var(--sp-3);border-radius:var(--r-md);
  background:var(--priBg)}
.cwgrand > .cwtl{padding:0;font-size:var(--t-body);font-weight:var(--w-bold);color:var(--ink)}
.cwgrand > .cwtl > span{font-size:var(--t-title);font-weight:var(--w-heavy);letter-spacing:-.02em}
/* THE COMPLETION BAR IS A REAL `<progress>`, AND THAT IS A CONSTRAINT SPEAKING RATHER THAN TASTE.
   A bar drawn as a filled child needs its width written into the element, and an inline `style`
   attribute is the one thing every page sweep in this suite refuses - the policy is `style-src
   'self'` with no `'unsafe-inline'`. `<progress value max>` carries the same number in an
   attribute the browser draws from, tells a screen reader what it is without a `role`, and lets
   `views/rows.js` move it by assigning `.value` rather than by touching a style. */
.cwbar{appearance:none;-webkit-appearance:none;display:block;width:100%;height:6px;
  margin:var(--sp-2) 0 6px;border:0;border-radius:var(--r-pill);
  background:var(--card);color:var(--ok);overflow:hidden}
.cwbar::-webkit-progress-bar{background:var(--card)}
.cwbar::-webkit-progress-value{background:var(--ok);border-radius:var(--r-pill)}
.cwbar::-moz-progress-bar{background:var(--ok);border-radius:var(--r-pill)}
.cwdone2{display:flex;align-items:baseline;gap:var(--sp-2);font-family:var(--ui);
  font-size:var(--t-micro);color:var(--ink2)}
.cwdone2 > span{margin-left:auto;font-weight:var(--w-bold);font-variant-numeric:tabular-nums}
/* THE NOTE. NO SENTENCE UNDER IT saying who can see it: the owner has struck explanatory text off
   these pages more than eight times, and the answer to "who reads this" is the permission, which
   is `cost.read` - the admin and the pricer - and is not a thing to write on a box. */
.cwnotes{margin-top:var(--sp-4)}
.cwnotes > .cwhd{gap:var(--sp-2);font-size:var(--t-sm);margin-bottom:var(--sp-2)}
.cwnotes > .cwhd > .tico{width:26px;height:26px;border-radius:var(--r-xs)}
.cwnotes > .cwhd > .tico svg{width:14px;height:14px}
.cwnotes textarea{width:100%;font:inherit;font-size:var(--t-sm);line-height:1.5;
  min-height:112px;resize:vertical;padding:var(--sp-2) var(--sp-3);border-radius:var(--r-md);
  border:1px solid var(--rule);background:var(--paper);color:var(--ink)}
.cwnotes p{margin:0;font-size:var(--t-sm);color:var(--ink2);line-height:1.55;
  white-space:pre-wrap;overflow-wrap:anywhere}
.cwnotes .cwbtn{margin-top:var(--sp-2);min-height:40px}

/* ==============================================================================================
   THE REFERENCES PANEL — the brief, the line's own brief, the delivered design, the production
   files, the reference images, the documents and the links, without leaving the workspace.
   ==============================================================================================
   IT IS `:target` AND IT IS FIXED, and both halves are load-bearing.

   `:target` because a same-document fragment is the ONLY navigation a browser makes that does not
   reload the page - so opening this and closing it cannot cost a single character of what has
   been typed into the form, which is the requirement. It is also the confirm mechanism this app
   already uses everywhere something must be opened before it is used, it needs no script, and it
   keeps working with scripting blocked. The cost, stated because it is real: a document has one
   `:target`, so opening this closes the delete panel at the foot of the page and vice versa.

   `position:fixed` because a panel that is part of the flow is a panel that makes the document
   taller and, at 300px wide beside a 1012px page, wider. A fixed element contributes nothing to
   `document.documentElement.scrollWidth`, which is the number `test/figures.test.js` measures.
   `z-index:50` puts it over the rail's 40 and under the flash bar's 60.
   ============================================================================================== */
/* THE DELETE SITS UNDER THE WHOLE WORKSPACE and takes `section`'s own gap from it rather than
   touching the last panel. One rule, scoped to the two things that can follow the workspace. */
/* THE DELETE'S OWN LINE IS `.hint.nofoot`, WHICH IS A CARD'S FOOTER RULE, and under the workspace
   it is the only thing in its `.rows` box - so its 20px top margin and its own top border landed
   ON that box's border and read as a line struck through the link. Scoped by a class on the
   section rather than by a sibling combinator: the references panel sits between the workspace
   and this, so `+` matched nothing. The identical control on Cost Review is inside a list row and
   is untouched. */
.cwdel{margin-top:var(--sp-6)}
.cwdel > .rows > .hint.nofoot{margin:0;padding:var(--sp-3) var(--sp-4);border-top:0}

.cwrefs{display:none}
.cwrefs:target{display:flex;flex-direction:column;position:fixed;z-index:50;
  top:0;right:0;bottom:0;width:min(30rem,100%);
  background:var(--card);border-left:1px solid var(--rule);box-shadow:var(--e-3)}
.cwrhd{flex:0 0 auto;display:flex;align-items:center;gap:var(--sp-3);
  padding:var(--sp-3) var(--sp-4);border-bottom:1px solid var(--rule);background:var(--card)}
.cwrhd > h2{margin:0;flex:1 1 auto;min-width:0;font-family:var(--ui);font-size:var(--t-title);
  font-weight:var(--w-bold);letter-spacing:var(--ls-title);color:var(--ink)}
.cwrbd{flex:1 1 auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;
  padding:var(--sp-4);padding-bottom:calc(88px + env(safe-area-inset-bottom))}
.cwrbd > section{margin-bottom:var(--sp-5)}
.cwrbd > section:last-child{margin-bottom:0}

/* ==============================================================================================
   THREE COLUMNS FROM 700 UP — the 1024 iPad in landscape and the 1280 desktop.
   ==============================================================================================
   TWO STEPS AND NOT ONE. 732px of column and 1012px are not the same room, and the two bands want
   OPPOSITE things from the same percentages.

   AT 1012 (a 1280 window) the middle is what is short: seven columns of live controls need 481 of
   table, so the sides are held to 22 and 23 and the middle takes what is left. That band is
   below, with its measurements.

   AT 732 (a 1024 iPad in LANDSCAPE) the middle is short WHATEVER IS DONE, and that is the honest
   reading rather than a shrug. `.wrap` caps at 760 until 1200 — the app shell, not this page —
   so the whole workspace is 732; the Cost Breakdown needs 480 of middle column before it can be a
   table at all, and 480 of middle plus two side columns a line card can be read in does not exist
   inside 732. The table therefore becomes the stacked labelled rows below, which is `.atab`'s own
   answer at a narrow width and this app's established one.

   SO AT THIS BAND THE SIDES ARE GIVEN MORE AND NOT LESS — 25 and 26, which is 183 and 190. Every
   pixel handed to the middle instead would buy a stacked row nothing, and would be taken off a
   line card that is already the tightest thing on the page. The owner asked for the same three
   columns on the iPad in landscape and that is what this band draws; what he cannot have at 732
   is the table with them, and the number that decides it is the shell's.
   ============================================================================================== */
@container cwork (min-width:700px){
  .cwgrid{gap:var(--sp-3);grid-template-columns:minmax(0,25%) minmax(0,1fr) minmax(0,26%)}
  /* ============================================================================================
     AND THE COSTING PAGE'S OWN GRID IS TWO TRACKS, SINCE 2026-09-26 — §1 took the left one away.

     THE RIGHT-HAND TRACK KEEPS ITS 26%, WHICH IS NOT LAZINESS. `Materials total` beside
     `USD 1,056.00` is 192px of ink and 26% of 732 is 190 — the measurement the three-column
     block below this one records, and the Cost Summary is the same column with the same rows on
     it. What changed is only who gets the 25% the selector used to hold, and §3 says who: «Use
     the space recovered from the old left panel to expand Cost Breakdown. Keep Cost Summary as a
     compact right-hand column.» So the middle takes all of it — 526px at a 1024 iPad, which is
     the first time on this page that the Cost Breakdown clears its own 480px table floor there.
     ========================================================================================== */
  .cwgrid.cwtwo{grid-template-columns:minmax(0,1fr) minmax(0,26%)}
  /* THE SELECTOR AND THE SUMMARY EACH STICK TO THE TOP OF THEIR OWN COLUMN once there is a column
     to stick in. A costing of twelve lines is taller than a screen, and the point of the middle
     column is that the two beside it are still there while you work in it. */
  .cwl,.cwr{position:sticky;top:var(--sp-3);max-height:calc(100vh - var(--sp-6));
    overflow-y:auto;-webkit-overflow-scrolling:touch}
  /* THE SUMMARY IS NOT A FOLD AT THIS WIDTH. It is a `<details open>` in the markup so that the
     narrow layout can close it; here the handle is simply not a control. */
  .cwr > summary{display:none}
}
/* ==============================================================================================
   THE DESKTOP SPLIT IS 22 / 52 / 23 AND THE PICTURE'S IS 24 / 49 / 25, AND THE DIFFERENCE IS 200
   PIXELS THIS PAGE HAS NOT GOT.

   The drawing is 1448px wide with a 208px rail, so its workspace is 1220 and its three columns
   measure 296 / 599 / 304 - 24.3 / 49.1 / 24.9 of it. This app's `.wrap` caps at 1280 and its
   rail is 216, so at a 1280 window the workspace is 1012, and the picture's own percentages of
   that are 246 / 497 / 252. The middle at 497 is where this block started and is where it failed:
   seven columns of live controls did not fit, and `sheet` came back as `sl` - the page lying
   about what is in a box.

   SO THE SIDES GIVE UP TWO POINTS EACH AND THE MIDDLE TAKES THEM: 22 / 1fr / 23, which measures
   223 / 525 / 233. The table is then 481 and every one of the seven columns holds what it says it
   holds - `sheet`, `m2` and `job` all read whole, checked in a browser at this width.

   AND 22/23 RATHER THAN THE 21/22 THIS SHIPPED WITH, which was a point under both of the owner's
   own floors (his band is 22-24 / 52-55 / 23-25). The two points cost the middle nothing that
   could be measured and bought the summary its single-line rows: `Materials total` beside
   `USD 1,056.00` is 192px of ink, which did not fit in 223 and does in 233. A line card at 223
   holds its name on two lines, its figure on a line of its own and its per-unit under that, all
   measured with real client names in them.
   ============================================================================================== */
@container cwork (min-width:940px){
  .cwgrid{gap:var(--sp-4);grid-template-columns:minmax(0,22%) minmax(0,1fr) minmax(0,23%)}
  /* TWO TRACKS AT 1012: 759 of centre and 233 of summary. The 23% is the measured floor under
     `Materials total  USD 1,056.00` on one line and is unchanged; the middle takes the 22 the
     line selector used to hold, which is §3 paid out in full — the Cost Breakdown goes from a
     481px table to a 715px one and Description from 115px to 179. */
  .cwgrid.cwtwo{grid-template-columns:minmax(0,1fr) minmax(0,23%)}
  /* AND THE PANELS BREATHE, which is the one place the picture is more generous than this page
     was and there is room to agree with it. 16 -> 20 inside a panel and 16 -> 20 between the
     title and the workspace; below this width every pixel is spoken for and they stay at 16. */
  .cwpanel{padding:var(--sp-5)}
  .cwmeta{margin-bottom:var(--sp-6)}
}

/* ==============================================================================================
   UNDER 700 — the iPad in portrait and the phone. One column, the selector on top, the summary
   folded away, and the order of the three is the order the owner named: pick a line, price it,
   then check the totals.
   ============================================================================================== */
@container cwork (max-width:699px){
  /* THE LINE SELECTOR BECOMES A COMPACT UPPER SELECTOR - a row that slides, which is the same
     answer the owner gave for the bottom bar («بدي الشريط ينسحب يمين و شمال») rather than folding
     lines away behind a More. The card keeps its own width so four of them read as four. */
  .cwlist{grid-auto-flow:column;grid-auto-columns:minmax(13.5rem,1fr);
    grid-template-columns:none;overflow-x:auto;overflow-y:hidden;
    -webkit-overflow-scrolling:touch;overscroll-behavior-x:contain;
    padding-bottom:var(--sp-2);scroll-snap-type:x proximity}
  .cwitem{scroll-snap-align:start}
  /* THE SUMMARY IS COLLAPSIBLE AND ARRIVES OPEN. `<details open>` in the markup means the totals
     are there when the page loads and can be got out of the way on a phone - which is the one
     thing a fold is for. */
  .cwr > summary{display:flex;align-items:center;gap:var(--sp-2);min-height:44px;
    margin:calc(var(--sp-4) * -1) calc(var(--sp-4) * -1) 0;padding:0 var(--sp-4);
    font-family:var(--ui);font-size:var(--t-title);font-weight:var(--w-bold);
    letter-spacing:var(--ls-title);color:var(--ink);cursor:pointer;list-style:none}
  .cwr > summary::-webkit-details-marker{display:none}
  .cwr > summary::after{content:"";flex:0 0 auto;margin-left:auto;width:8px;height:8px;
    border-right:2px solid var(--ink3);border-bottom:2px solid var(--ink3);
    transform:rotate(45deg);margin-bottom:3px}
  .cwr[open] > summary::after{transform:rotate(-135deg);margin-bottom:-3px}
  .cwr[open] > summary{margin-bottom:var(--sp-3);
    padding-bottom:var(--sp-3);border-bottom:1px solid var(--rule2)}
  .cwr > .cwhd:first-of-type{display:none}
}

/* ==============================================================================================
   UNDER 480 OF MIDDLE COLUMN — THE COST BREAKDOWN STOPS BEING A TABLE.
   ==============================================================================================
   `.atab`'s own answer on a phone, for `.atab`'s own reason and with one difference that matters
   here: these cells hold CONTROLS, and a control with no visible column heading above it has no
   name at all. So each cell carries its own label in this layout - `data-l` on the `<td>`, drawn
   by `::before` - and the row reads down as What it is / Description / Unit / Qty / Unit cost /
   Total, with the remove in the corner. The `<th>`s go, because a header for a column that no
   longer exists is a heading a screen reader announces about nothing.
   ============================================================================================== */
@container cwc (max-width:479px){
  .cwtab{overflow-x:visible}
  .cwt{min-width:0;display:block}
  .cwt thead{display:none}
  .cwt tbody{display:block}
  /* ============================================================================================
     ONE COST ROW AS FOUR LINES AND NOT AS SIX, WHICH IS A MEASUREMENT.

     Given a line each, a cost row was 250px tall on a 1024 iPad and four of them were a screen
     and a half - the first cut of this layout, shot and counted. A unit, a quantity and a price
     are three short fields that read as one statement, so they share a line; the type badge and
     the remove share the line above it with the description under them, and the total closes the
     row with the label that names it.

     THE CELLS ARE NOT MOVED IN THE MARKUP AND COULD NOT BE. They are still seven `<td>`s of seven
     columns in document order, because at every wider width they still ARE seven columns; what
     changes here is only where the grid puts them. That is why this whole layout is thirteen
     rules and not a second renderer.
     ============================================================================================ */
  .cwt tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--sp-2);
    padding:var(--sp-3);border-top:1px solid var(--rule2)}
  .cwt tbody:first-of-type tr:first-child{border-top:0}
  .cwt td{display:block;padding:0;border:0;min-width:0}
  .cwt td.cwty{grid-area:1 / 1 / 2 / 3;align-self:center}
  .cwt td.cwdo{grid-area:1 / 3;justify-self:end}
  .cwt td.cwde{grid-area:2 / 1 / 3 / 4}
  .cwt td.cwun{grid-area:3 / 1}
  .cwt td.cwqt{grid-area:3 / 2}
  .cwt td.cwuc{grid-area:3 / 3}
  .cwt td.cwsum2{grid-area:4 / 1 / 5 / 4;text-align:left}
  .cwt td.cwnum input{text-align:left}
  /* THE CELL'S OWN NAME, from the attribute rather than from a heading it no longer has. */
  .cwt td[data-l]::before{content:attr(data-l);display:block;margin-bottom:3px;
    font-family:var(--ui);font-size:var(--t-micro);font-weight:var(--w-bold);
    letter-spacing:var(--ls-caps);text-transform:uppercase;color:var(--ink3)}
  /* ============================================================================================
     AND THE TYPE CELL LAYS ITS HEADING BESIDE ITS CONTENT, WHICH IS WHAT MAKES EVERY ROW THE SAME
     HEIGHT - the owner's own instruction, «بدي تكون الأسطر كلها متناسقة».

     It is the one cell here that may: every other one holds a BOX, which wants the width, and
     this one holds a pill or a picker, both of which are short. Stacked with the heading above
     it, a badge cell was 35px and an `Other` row's `<select>` made it 55 - so the row that asks
     which of the three it is stood 17px taller than the rows around it, which is the same fault
     the `Other` fold had in miniature. Beside it, both are inside the 40px this grid row already
     spends on the remove control, and the two rows measure the same. It also takes 20px off
     EVERY row, which on a phone is a screenful across a costing.

     THE PICKER IS `--t-xs` HERE AND `--t-micro` IN THE TABLE, and that is not an inconsistency:
     in the table it is squeezed into a 19% column beside six others and sized to the badge it
     replaces; here it has a cell two tracks wide and can be read at the size of the boxes below
     it. `width:auto` because a full-width picker in a 230px cell is a control that looks like a
     mistake next to a 74px pill.
     ============================================================================================ */
  .cwt td.cwty{display:flex;align-items:center;gap:var(--sp-2);min-width:0}
  .cwt td.cwty::before{margin-bottom:0;flex:0 0 auto}
  .cwt td.cwty > select.cwtype{width:auto;min-width:8.5rem;max-width:100%;
    font-size:var(--t-xs);padding:4px var(--sp-2)}
  /* AND THE FOUR CARDS GO TWO BY TWO, WHICH IS A MEASUREMENT AND NOT A PREFERENCE. `auto-fit` at
     a 104px floor put THREE across a 328px phone column and left the fourth alone on a row of its
     own - and at 104 a card has 88px of text, in which `USD 336.00` breaks. Two across is 160
     each, every figure on one line, and the set reads as a set. The drawing is four across
     because the drawing is a desktop. */
  .cwfigs{grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* DARK IS THE SELECTED PALETTE AND NOT AN INVERSION, and every rule above resolves through tokens
   that already flip - so there is not one colour in this block that needs restating here. The two
   that would have needed it are the selected line's ring and the grand total's fill, and both are
   `--ship` and `--priBg`, which are declared in all three theme states at the head of this file
   with their own measurements. This comment exists so the next reader does not go looking. */

/* ==============================================================================================
   THE NEW-ORDER FORM'S PANELS ARE PANELS ONLY WHERE BEING ONE PAYS FOR ITSELF - 2026-09-24.

   The page was rearranged to the owner's mock-up: six numbered panels, the project image beside
   preparation and stations, the project requirements as a real table. A panel costs about 56px of
   chrome - a 44px tinted strip, 16 of margin, 30 of padding, less the `<label>` it replaces - and
   there are six, so the layout carries ~336px that only a wide band can pay for.

   ABOVE 900 OF CONTAINER IT IS PAID FOR TWICE OVER: `.qlt` becomes seven real columns and the
   image sits beside the stations, and the page is 83px SHORTER than the flat form it replaced
   while carrying two sections the flat form did not have.

   BELOW 900 THERE IS NO TABLE AND NO PAIRING - one column whatever the chrome - so the chrome is
   bought and nothing is sold. Measured, at 390/834/1024/1280: without this block the page is
   2541/1891/1688/1459; with it 2105/1616/1477/1459; the flat form it replaced was
   2195/1723/1542/1542. It is the three narrow widths this exists for, which are the widths the
   owner actually works at.

   THE BAND IS 900 BECAUSE THAT IS `.qlt`'S OWN THRESHOLD for becoming a table. One number, one
   reason - a second threshold here would be a second thing to remember and to get wrong.

   `@container` AND NOT `@media`, for the costing workspace's reason written above `.cwork`: at a
   1024 window this form is 732px wide, and a page that asked the WINDOW would lay itself out for
   a desktop inside a column that has not got one.

   SCOPED TO `.oform`, WHICH ONLY THE NEW-ORDER FORM CARRIES - and that class was added for this,
   because `/orders/new` and `/creative-request/new` and `/clients/new` are all `class="qform
   cform"` to the byte, so a rule on those two would have reached two other pages. `/quotations/new`
   is `qform cform qnew` and `/rfqs/new` is `qform` alone; none of the four can be reached by any
   selector below. Verified by rendering them, not by reading the selectors.
   ============================================================================================== */
@container rfqform (max-width:899px){
  /* `border:0` IS IN HERE AND IT IS NOT TIDYING. Taking the background and the shadow off left a
     faint rounded outline round the stations block at 390 - a card edge with no card behind it.
     It costs no height; it is the difference between "a plain labelled block" and "a panel
     somebody switched the fill off in". */
  .oform .qsec.csec{padding:0;margin-bottom:var(--sp-4);border:0;border-radius:0;box-shadow:none;
    background:none}
  .oform .qsec.csec > .qsh{margin:0 0 var(--sp-2);padding:0;background:none;border:0;
    font-size:var(--t-sm);font-weight:var(--w-bold);letter-spacing:var(--ls-caps);
    text-transform:uppercase;color:var(--ink3)}
  /* THE STEP NUMBER GOES WITH THE PANEL. `1` in a circle beside a heading is a panel's ordinal;
     above a plain labelled block it is a decoration that has lost the thing it was counting. */
  .oform .qsec.csec > .qsh > .qsn{display:none}
}

/* ==============================================================================================
   THE SALES CREATIVE CARD, COMPACT - 2026-09-25. Three blocks: the action forms above the tabs,
   the latest delivery, and the brief summary.
   ==============================================================================================
   EVERY SELECTOR IN THIS BLOCK NAMES `.cforms`, `.cdel` OR `.cbrf`, and all three classes are
   written by `views/creative-record.js` and by nothing else in this app. That is deliberate and it
   is the same rule the drop-zone block one screen up states: this stylesheet is served
   content-addressed and its sha256 is asserted in eleven test files, so a change here that reached
   a page nobody asked about would be found by a test that cannot say which page it was. Nothing
   below can match on the RFQ card, the quotation record, the designers' bench or the costing
   workspace - verified by rendering them, not by reading the selectors.

   `@container` AND NOT `@media` THROUGHOUT, which is this file's standing bias and is load-bearing
   here rather than decorative. At a 1024 window `.wrap` gives this card 732px, not 1024; the four
   real content widths are 362 / 594 / 732 / 1012 at 390 / 834 / 1024 / 1280. The mock-up these
   three blocks come from is 1456 wide, so every width in it is a width this card has never had. A
   rule that asked the WINDOW would lay three option cards out for a desktop inside a column that
   has not got one - which is the costing workspace's own recorded argument above `.cwork`.
   ============================================================================================== */

/* ----------------------------------------------------------------------------------------------
   1. THE ACTION FORMS, DIRECTLY UNDER THE STATUS BAR AND ABOVE THE TABS.

   HIS COMPLAINT, VERBATIM: «وقت اكبس ابورف الخيارات ال ٣ لسعتها بآخر الصفحة أنا تفقت معك تكون فوق
   التبويبات بطريقة مرتبة» - when I press Approve the three options are at the end of the page; we
   agreed they would be above the tabs, tidily. And «اي كلن فوق» when he was asked whether the
   other eight forms followed: all of them.

   THE MOVE ITSELF IS NOT IN THIS FILE AND COULD NOT BE. A `:target` drawer is visible where it is
   WRITTEN; `:target` has never decided position. `recordCard` grew a slot between the bar and the
   strip and `actionForms` fills it - see both for why that is a document-order problem and not a
   CSS one, and why no script was added to a page that may not require one.

   WHAT IS HERE IS THE «بطريقة مرتبة» - the tidy arrangement: the panel as the white card the
   mock-up draws, its full heading with the Close control, the three deliverables as small radio
   cards in one row, and the navy button on the same row at the right.
   ------------------------------------------------------------------------------------------- */

/* THE QUERY CONTAINER, AND IT IS THE WRAPPER RATHER THAN THE PANEL because the panel is
   `display:none` until it is targeted and a container query on a box with no layout is a query
   with no answer. The wrapper is in flow always and is worth NO HEIGHT while every panel inside it
   is closed: `.pick` has no padding, no border and no margin of its own, and both of its children
   are out of flow (`.pick > .prows{display:none}`). So the five tabs that carry a closed form pay
   nothing for it, which is the same measurement the panels themselves have always been worth. */
/* ==============================================================================================
   AND THE OPEN MENU MUST NOT LAND ON TOP OF THE FORM IT JUST OPENED. THAT BUG IS FIXED IN THE
   HREF NOW AND NOT HERE — 2026-09-25, and the three attempts are kept because the next person to
   move this menu will meet all three again.

   WHAT THE BUG WAS. `Request action` is a `<details>`; its `.cwmenu` is `position:absolute;
   z-index:45` and opens DOWNWARDS out of the status bar. Choosing an entry was an `href="#id"` at
   a panel on the page the reader was already on, which is a SAME-DOCUMENT navigation - so nothing
   reloaded and the `<details>` stayed open. That cost nothing while the panels were at the FOOT
   of the tab: the fragment jump scrolled the bar and its open menu off the top of the screen. Once
   the panel opened directly under the bar, at 390 the seven-entry menu (190x308) was painted over
   the whole top of the panel and `document.elementFromPoint` at the centre of `Production Files`
   returned the menu's own anchor. Drawn, and not pressable - the exact fault `.rows.noclip` was
   written for in September, arriving by a second route.

   ATTEMPT 1, AND IT IS WRONG FOR A MEASURED REASON RATHER THAN A TIDY ONE. A z-index:
   `.cforms{position:relative;z-index:46}` does put the panel over the menu's 45 - and over
   `.masthead`'s 30, which is a STICKY bar. Scrolled 120px at 390, `elementFromPoint` at the
   middle of the masthead returned `LABEL.pchip prow`: the radio card was painted across the
   search box. Trading the form for the search bar is not a trade; the menu is 45 because it must
   clear the phone's fixed bottom bar at 40 and the masthead is 30, so there is no number that is
   over the one and under the other.

   ATTEMPT 2, AND IT WEDGED A BROWSER. `#main:has(.cforms > .pick:target) … .cwmenu{display:none}`
   - a `:has()` whose SUBJECT is the whole page. `test/creative-card.test.js`'s delete walk failed
   the same way three runs running: Chrome went silent for 45 seconds and never fired `load`,
   which is `test/browser.js`'s wedge guard catching a renderer that has stopped answering. A
   `:has()` on `#main` makes every `:target` change on the page a restyle of everything under it.
   IF A `:has()` IS EVER NEEDED IN THIS BLOCK AGAIN, SCOPE IT TO SIBLINGS - the sibling form
   `.rows.noclip:has(+ .cforms > .pick:target)` is two elements and was green.

   ATTEMPT 3 SHIPPED AND THE OWNER REPORTED WHAT IT COST. The sibling `:has()` above hid `.cwmenu`
   whenever any panel was targeted, which stopped the overlap and stopped the MENU: `Request
   action` showed nothing at all while a form was open, so re-opening it needed the form closed
   first. A control that is drawn and does nothing when pressed is the fault this card has now
   been fixed for three times, and hiding a menu to protect a panel is one more of them.

   ==============================================================================================
   SO THE MENU CLOSES ON SELECTION INSTEAD, WHICH IS WHAT A MENU IS SUPPOSED TO DO, AND NO RULE
   IN THIS FILE CAN DO IT. `<details open>` is an attribute; CSS cannot unset one, and a script
   that closed it would make the one menu every reader on this card needs require JavaScript on a
   page that may not. What closes a `<details>` with no script is a NEW DOCUMENT - so every entry
   now carries a query word as well as its tab, the press is a real navigation, and the menu comes
   back closed with the panel open under it. See `pickHref` in `views/creative.js`: that is where
   this bug is fixed, and it is why there is no rule here hiding anything.

   WHAT IS LEFT IN THIS FILE IS THE CONTAINER AND THE SCROLL MARGIN, and both are layout.
   ============================================================================================== */
.cforms{container-type:inline-size;container-name:cforms}
/* AND THE JUMP LANDS BELOW THE MASTHEAD AND BELOW THE BAR THAT WAS PRESSED. `.pick`'s own
   `scroll-margin:96px` was measured for a panel hanging under a station bar in the middle of a
   long order card; these sit near the TOP of the page, under a sticky masthead (~60px) and a
   status bar (~72px), so 96 put the panel's heading behind the masthead at 390 and hid the bar
   the reader had just used. 150 keeps both on screen. Measured at 390/834/1024/1280. */
.cforms > .pick{scroll-margin-top:150px}

/* THE OPEN PANEL IS THE CARD. Margin, padding, ink and shadow arrive with `:target` and leave with
   it, exactly as `.job > .pick:target`'s own rule works: a closed panel that carried a margin would
   put a gap between the bar and the tabs on every tab, on a card where nothing is open. */
.cforms > .pick:target{margin:var(--sp-3) 0 var(--sp-4);padding:var(--sp-4) var(--sp-5) var(--sp-5);
  background:var(--card);border:1px solid var(--rule);border-radius:var(--r-lg);
  box-shadow:var(--shadow)}

/* THE HEADING, WHOLE - §2's «Show the full "Approve Design" heading without truncation and retain
   the Close control». `.pickl` is `flex:0 0 84px` with `text-overflow:ellipsis` on it, which is
   right for a picker hanging under a station bar on an order card and is what printed `Approve
   De…` here. It is the section heading's own size and weight now, because that is what it IS on
   this card: `Approve Design` reads at the same size as `Project information` two rows under it.

   THE CLOSE CONTROL IS UNTOUCHED AND IS ALREADY THE MOCK-UP'S. `.pickx > em` is `CLOSE` in uppercase
   micro-caps with `margin-left:auto`, and `.pickx::after` is the `×` beside it; the whole row is the
   anchor, so the target is the full width of the card and 44px tall. */
.cforms > .pick:target > .pickx{min-height:44px;gap:var(--sp-3);padding:0}
.cforms > .pick > .pickx > .pickl{flex:1 1 auto;min-width:0;font-family:var(--ui);
  font-size:var(--t-title);font-weight:var(--w-bold);letter-spacing:var(--ls-title);
  white-space:normal;overflow:visible;text-overflow:clip}

/* THE FORM IS A ROW AND NOT A STACK. `.prows` is a grid of full-width rows everywhere else in this
   app, which is what put the three deliverables one under another and the button under those. Here
   it is a wrapping flex line: the options take the room they need, the button is the last item and
   carries `margin-left:auto`, and everything that is NOT one of those two - a label, a reason box,
   a file input, a sentence of confirmation - claims a whole line of its own below. The panel's own
   tint, border and ring come off, because the panel is the card now. */
.cforms > .pick:target > .prows{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-3);
  margin:var(--sp-3) 0 0;padding:0;border:0;border-radius:0;background:none;box-shadow:none}
.cforms > .pick:target > .prows > .hint,
.cforms > .pick:target > .prows > .ptxt,
.cforms > .pick:target > .prows > .pfile,
.cforms > .pick:target > .prows > p{flex:1 1 100%;min-width:0}
/* A LABEL INSIDE THIS CARD IS NOT A CARD FOOTER. `.hint.nofoot` carries a 20px top margin and a
   rule, which is a footer's separator on a form card and is a stray line across a panel here. */
.cforms > .pick:target > .prows > .hint.nofoot{margin:var(--sp-1) 0 0;padding:0;border-top:0}
.cforms > .pick:target > .prows > .hint.nofoot + .ptxt{margin-top:calc(var(--sp-2) * -1 + 2px)}

/* §2's «Display these existing options in one compact row … small radio selection cards with
   subtle borders and a light tint for the selected option».

   THE `<fieldset>` HAS TO BE RESET BEFORE IT CAN BE A ROW. It is the browser's own box - a 2px
   groove border, `margin-inline:2px` and about 10px of padding - and nothing in this stylesheet had
   ever styled `.pset`, so the three options sat inside a visible engraved rectangle. That rectangle
   is the grey box in the owner's screenshot.

   THE BASIS IS 220px AND IT IS MEASURED RATHER THAN CHOSEN. `Design Handover Package` is the
   longest of the three and sets the floor: at 14px/650 it measures ~161px, plus an 18px radio, a
   10px gap, 24px of padding and 2px of border = ~215px. So three of them fit on one line wherever
   there are ~676px of room and drop to two, then one, below that - which at this card's real widths
   is 3 across at 1012 and 732, 2 at 594 and 1 at 362. Nothing is ever clipped: the label wraps
   rather than ellipsing, which is the whole of §5's «wrap the approval options neatly without
   clipping or horizontal scrolling». */
.cforms .pset{flex:1 1 320px;min-width:0;display:flex;flex-wrap:wrap;gap:var(--sp-2);
  margin:0;padding:0;border:0}
.cforms .pset > .pchip.prow{flex:1 1 220px;width:auto;min-width:0;min-height:52px;
  gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);border-radius:var(--r-md);
  background:var(--card);border:1px solid var(--rule);color:var(--ink);
  font-family:var(--ui);font-size:14px;font-weight:var(--w-semi)}
/* THE LABEL WRAPS. `.prow > span` is `white-space:nowrap` with `text-overflow:ellipsis`, which is
   right in a 150px picker row on an order card and is CLIPPING here - the one thing §5 names. */
.cforms .pset > .pchip.prow > span{flex:1 1 auto;min-width:0;white-space:normal;overflow:visible;
  text-overflow:clip;overflow-wrap:anywhere}
.cforms .pset > .pchip.prow > input[type=radio]{flex:0 0 auto;width:18px;height:18px;margin:0;
  accent-color:var(--pri);cursor:pointer}
.cforms .pset > .pchip.prow:hover{border-color:var(--priRule)}
/* THE KEYBOARD GETS THE SAME RING THE MOUSE NEVER SEES. The radio is the focusable thing and it is
   18px; the ring belongs on the card around it, which is the target a finger and an eye both use. */
.cforms .pset > .pchip.prow:focus-within{outline:2px solid var(--priInk);outline-offset:2px}
/* «A LIGHT TINT FOR THE SELECTED OPTION», AND `:has()` IS SAFE HERE FOR THE ONE REASON IT WAS NOT
   SAFE OVER THE `⋯` MENU: it fails the RIGHT way round. An engine that does not know `:has()` draws
   an untinted card with a CHECKED NATIVE RADIO in it, which still says which option is selected -
   nothing becomes unreachable and nothing becomes invisible. The menu case hid a control
   completely, which is why that one is a class the server writes. `.jshow:has(.pick:target)`
   already uses it, on the same terms. */
.cforms .pset > .pchip.prow:has(> input[type=radio]:checked){background:var(--priBg);
  border-color:var(--priInk);box-shadow:inset 0 0 0 1px var(--priInk)}

/* §2's «Place the navy "Approve Design" button on the same row, aligned right». `margin-left:auto`
   is what aligns it, and it is the same property `.cwsplit` and `.acts > details.cwadd` use for the
   same job on this card; `--pri`/`--priOn` is the navy `Request action` is already painted in, one
   row above, so the two primary controls on this card are one colour rather than two.

   `:not(.pchip)` IS NOT A TIDY-UP. The delete confirm and the reopen both submit through a
   `button.pchip.prow` - red and green chips with their own meaning - and painting those navy would
   make `Delete it anyway` look like the safe press on the card. They keep their tone and take a
   line of their own. */
.cforms > .pick:target > .prows > button[type=submit]:not(.pchip){flex:0 0 auto;
  margin:0 0 0 auto;min-height:48px;padding:0 var(--sp-5);border-radius:var(--r-md);
  border:1px solid var(--pri);background:var(--pri);color:var(--priOn);
  font-family:var(--ui);font-size:var(--t-sm);font-weight:var(--w-bold);
  letter-spacing:.01em;cursor:pointer;white-space:nowrap}
.cforms > .pick:target > .prows > button[type=submit]:not(.pchip):hover{background:var(--pri2);
  border-color:var(--pri2)}
.cforms > .pick:target > .prows > .pchip.prow{flex:1 1 100%;min-height:44px}

/* §5's «Keep controls compact and touch-friendly», applied to the one control on these forms that
   was under the floor: §5's attachment field. `.pfile` has never had a rule in this stylesheet, so
   a bare `<input type="file">` measured 21px tall at every width - half the floor, on the only
   form on this card that takes a file. The box is ours and the BUTTON INSIDE IT IS THE BROWSER'S:
   on an iPad the native control is what opens Files, iCloud and the camera, which is exactly the
   note `.card input[type=file]` carries, and these are that rule's own declarations. */
.cforms > .pick:target > .prows > .pfile{min-height:44px;padding:9px 12px;line-height:1.7;
  border:1px solid var(--rule);border-radius:var(--r-md);background:var(--paper2);
  font:inherit;font-size:var(--t-sm);color:var(--ink2)}
.cforms > .pick:target > .prows > .pfile::file-selector-button{font:inherit;font-size:12.5px;
  font-weight:640;margin:0 11px 0 0;padding:7px 12px;border-radius:10px;
  border:1px solid var(--rule);background:var(--card);color:var(--ink2);cursor:pointer}

/* THE BUTTON KEEPS THE OPTIONS' ROW ONLY WHILE THERE IS ROOM FOR BOTH. Below 900 of container the
   fieldset takes the whole line and the button drops under it, still at the right - which is the
   difference between three options of 220px beside a 165px button (impossible at 732) and three
   options of 220px on a line of their own (comfortable at 732). It is the ONE breakpoint in this
   block, and it is a container width: at a 1024 window this card is 732px wide. */
@container cforms (max-width:899px){
  .cforms .pset{flex:1 1 100%}
}
/* AND AT THE PHONE THE CARD GIVES ITS PADDING BACK TO THE OPTIONS. 20px each side of a 362px
   column is 11% of the width spent on air; 16 is the shell's own gutter. */
@container cforms (max-width:439px){
  .cforms > .pick:target{padding:var(--sp-3) var(--sp-4) var(--sp-4)}
  .cforms > .pick > .pickx > .pickl{font-size:17px}
}

/* ----------------------------------------------------------------------------------------------
   2. `Latest design delivery` - §3: «Combine version information and the attachment into one clean
   white card. Use a small document thumbnail, with the filename and file type beside it. Preserve
   file opening, preview, and any existing attachment interactions. Remove the excessive empty
   space.»

   WHAT THE EMPTY SPACE WAS, MEASURED. The section is a `.rows` box holding the version line and a
   `.bgrid` photo wall holding the files, and the two are separate cards with a gap, two borders and
   two shadows between them. The wall is `repeat(auto-fill,minmax(150px,1fr))`, so ONE delivered PDF
   is a 150px tile with a dashed rectangle in it and ~850px of nothing beside it at 1012.

   NOT ONE BYTE OF `deliveredDesignSection` MOVED, and that is the constraint this block is written
   under: that renderer draws the delivered design on FIVE surfaces and §5 preserves the other four.
   `.cdel` is a wrapper this card alone writes.
   ------------------------------------------------------------------------------------------- */
.cdel{margin:0 0 var(--sp-4)}
/* ONE CARD. The section is the box now; its parts give theirs up. `overflow:hidden` is what clips
   the first row and the last file to the radius, exactly as `.rows` does it for every list. */
.cdel > section{background:var(--card);border:1px solid var(--rule);border-radius:var(--r-lg);
  box-shadow:var(--shadow);overflow:hidden}
.cdel > section > .rows{margin:0;border:0;border-radius:0;box-shadow:none;background:none;
  overflow:visible}
/* THE SECOND IDENTITY ROW - `PRINT Production files` - IS A ROW OF THE SAME CARD and takes the
   hairline every `.row` after the first one takes. */
.cdel > section > .rows + .rows{border-top:1px solid var(--rule2)}
.cdel > section > .costnote{margin:0;padding:0 var(--sp-4) var(--sp-3)}
/* THE WALL BECOMES A LIST. One file per line whatever the width, because what is being drawn is an
   attachment and not a photograph - the owner's own instruction about file rows on the other card:
   «بدي الملفات تظهر كاسطر و معلومات عنها ... ومابدي تكون الصورة كبيرة». */
.cdel > section > .bgrid{display:grid;grid-template-columns:1fr;gap:var(--sp-2);margin:0;
  padding:var(--sp-1) var(--sp-4) var(--sp-4)}
.cdel .bcell{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-2);
  border-radius:var(--r-md);background:var(--paper)}
/* §3's «small document thumbnail». 56px, which is over the 44px touch floor the whole cell's link
   has to clear and is the size `.frow`'s own file tile settled on for the same job one card over. */
.cdel .bcell .shot{flex:0 0 auto;width:56px;height:56px;min-height:0;aspect-ratio:auto;
  border:1px solid var(--rule);border-bottom:1px solid var(--rule);border-radius:var(--r-sm);
  overflow:hidden;background:var(--card)}
.cdel .bcell .shot.none .dwcap{position:absolute;inset:0;flex-direction:row;justify-content:center;
  align-items:center;gap:0;padding:0}
/* THE WORDS STAY IN THE ACCESSIBLE NAME AND LEAVE THE PICTURE. `Design · Open it ↗` will not fit in
   56px and it is the only text in this anchor - hiding it with `display:none` would leave a link
   with no name at all. These are `.vh`'s own five declarations. */
.cdel .bcell .shot.none .dwcap > span{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.cdel .bcell .shot.none:before,.cdel .bcell .shot.none:after{display:none}
.cdel .bcell .shot.draw img{width:100%;height:100%;object-fit:cover}
/* THE FILENAME AND THE FILE TYPE, BESIDE IT - §3's own words, and they are already `.bcap` and its
   `<em>`. What changes is that they are a column beside a tile instead of a caption under one. */
.cdel .bcap{flex:1 1 auto;min-width:0;padding:0;font-size:var(--t-sm);font-weight:var(--w-med);
  color:var(--ink)}
.cdel .bcap em{margin-top:2px;font-size:11px;color:var(--ink3)}

/* ----------------------------------------------------------------------------------------------
   3. `Brief summary` - §4: «Put the heading, "View full brief" link, and brief text inside one
   white rounded card. Heading at the top left; link at the top right. Use readable dark body text,
   approximately 16-18px, with comfortable line spacing and 20-24px padding.»

   IT IS THE ONE SECTION ON THIS CARD WITH ITS HEADING INSIDE THE BOX. `Project information` and
   `Latest design delivery` keep theirs on the paper above, which is the mock-up's own arrangement
   and this app's: a heading over a grid of facts is a caption, and a heading inside a card with
   prose under it is a document. `.sact` and `.vall` are untouched - the link has carried
   `margin-left:auto` and a 44px floor since September, so `top left` and `top right` needed no
   rule at all.
   ------------------------------------------------------------------------------------------- */
.cbrf{margin:0 0 var(--sp-4);padding:var(--sp-5) var(--sp-5) var(--sp-6);background:var(--card);
  border:1px solid var(--rule);border-radius:var(--r-lg);box-shadow:var(--shadow)}
.cbrf > .sact{margin:0 0 var(--sp-4)}
/* 17px/1.7 AND FULL-STRENGTH INK - §4's «approximately 16-18px, with comfortable line spacing».
   `.empty.brf` is 13px in `--ink2` because `.empty` is this app's "there is nothing here" line and
   the brief has been borrowing it; a paragraph somebody wrote for this reader should not be set in
   the type of an absence. `white-space:pre-wrap` is already on it and is now worth something: see
   `excerpt`'s `lines` option for what stopped collapsing the newlines before they arrived. */
.cbrf > .brf{margin:0;padding:0;font-size:17px;line-height:1.7;color:var(--ink)}

/* ==============================================================================================
   ==============================================================================================
   THE PRODUCTION BOARD, SECOND PASS - his approved mock-up of 2026-09-26.

   `design-target/PRODUCTION-BOARD-V2.md` is the brief, recorded verbatim, and
   `design-target/production-board-v2-mockup.png` (1448 x 1086) is the drawing. This block is
   everything the redesign needed that the first pass's rules above do not already say. It is at
   the FOOT of the file, which is deliberate: every rule in here is a deliberate override of a
   rule further up, they are all about one surface, and keeping them together is how the next
   reader can see the whole of the second pass in one place instead of hunting eleven diffs.

   THE FIVE THINGS IT DOES:

     1.  §2's COMPACT CARD - tighter identity block, the reference/person/location lines with
         their marks, the stations without their sentence, and a footer of progress + View
         details. Everything §2 removed (the deadline pair, the delivery chip, Preparation and
         the three figures) simply is not rendered any more, so their rules above are now only
         used by the DETAILS PANEL, which still draws them.
     2.  §1's HEIGHT - the one-line attention summary, and the measured grid.
     3.  §3's COLLAPSIBLE RAIL.
     4.  §4's FOURTH TAB - four tabs in the width three used to have.
     5.  THE REFERENCE VIEWPORT'S CONTAINER THRESHOLD, which is arithmetic and is the single
         most load-bearing number in the whole pass. It is argued where it is written.
   ============================================================================================ */

/* ----------------------------------------------------------------------------------------------
   1. THE CARD

   EVERY NUMBER IN HERE IS A HEIGHT THE CARD GAVE UP, and they were all measured against the
   1024 x 768 reference viewport rather than against the 1448-wide drawing. The card is the same
   card - same classes, same container queries, same `.jobs` grid - with §2's four groups gone and
   the padding the mock-up actually draws.
   ------------------------------------------------------------------------------------------- */
.jident{padding:11px 12px 9px;gap:7px 11px}
.job:not([open]) > .jident{padding-bottom:11px}
@container job (min-width:540px){ .jident{gap:8px 12px} }

/* THE THUMBNAIL IS THE MOCK-UP'S PROPORTION - «Preserve image proportions and keep the complete
   design visible». It is wider relative to the card than the first pass drew it (the drawing gives
   it about 40% of the card's width) and it keeps `object-fit:cover` inside a fixed 4:3 frame, so a
   phone photograph, a Drive thumbnail of a PDF and a 16:10 render are all the same box and the
   cards in a row are the same height whatever the pictures are. */
.jthumb{width:86px;border-radius:11px}
@container job (min-width:430px){ .jthumb{width:96px;border-radius:12px} }
@container job (min-width:540px){ .jthumb{width:112px;border-radius:13px} }

/* THE ORDER NUMBER AND THE BADGE, SIDE BY SIDE AT THE LEFT - the drawing's own header row. They
   were a number on the left and a chip pushed right; in the image they sit together and the rest
   of the line is white space, which is what makes the two read as one label for the card. */
.jidhead{gap:6px;margin:0 0 3px}
.jidhead .po{font-size:11.5px;padding:4px 8px}
@container job (min-width:540px){ .jidhead .po{font-size:12.5px;padding:4px 10px} }
.okmark,.stat{font-size:9px;padding:4px 8px}
@container job (min-width:540px){ .okmark,.stat{font-size:9.5px;padding:4px 9px} }

/* THE NAME. Two lines are still RESERVED so the stations strip starts at the same y on every card
   in a row - that is §2's «keep all cards equal in width and height» and it is the half data
   cannot break. What is new is the size: 16.5px against 18.5, which is the type the drawing sets
   at this card width and is still comfortably above the 13px this app calls body text. */
.jidtx .jtitle{font-size:16.5px;line-height:1.14;margin:0 0 2px;min-height:calc(2 * 1.14em)}
@container job (min-width:430px){ .jidtx .jtitle{font-size:18px} }

/* THE THREE METADATA LINES AND THEIR MARKS - §2's «PO/reference, person/client information, and
   location», drawn as the image draws them: a glyph, then the value, with the person and the place
   sharing a line. `min-width:0` and the ellipsis on each so a long governorate or a long
   salesperson's name gives up characters instead of pushing the card wider. */
.jmeta{gap:2px;font-size:11.5px}
.jcode{display:inline-flex;align-items:center;gap:5px;font-size:11px}
.jcode svg{flex:0 0 auto;opacity:.7}
.jcode > span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.jwho2{display:flex;align-items:center;flex-wrap:wrap;gap:3px 12px;min-width:0}
.jsub.jwhoname{display:inline-flex;align-items:center;gap:5px;min-width:0;font-size:11.5px}
.jsub.jwhoname svg{flex:0 0 auto;opacity:.7}
.jsub.jwhoname > span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.jsub.jloc{font-size:11.5px}

/* THE STATIONS. `.nonote` is the CARD's copy - the rail sentence has moved to the footer, so the
   wrapper holds one child and the 11px grid gap between strip and sentence is height nobody is
   using. The PANEL's copy is untouched and keeps the sentence under the strip, which is where the
   drawing puts it there. */
.jprod{margin:0 12px 9px;padding:9px 11px 8px;border-radius:14px}
.jprod .jph{font-size:11.5px;gap:8px}
.jprod .jpwrap{margin-top:7px}
.jprod.nonote .jpwrap{gap:0}
.jprod .strip{gap:2px;row-gap:8px}
.jprod .st i{height:12px}
.jprod .st span{font-size:8px;margin-top:5px}
/* THE TAP TARGET DOES NOT SHRINK WITH THE BAR. `a.st` keeps the padding that takes each segment
   past 40px tall - the bar is 2px shorter, the thing a thumb is aimed at is not. */
.jprod a.st{padding:9px 2px 7px}

/* THE FOOTER - «Progress summary at bottom left and View details at bottom right».
   `.jfp` is text and `.jfv` is the link, so there is exactly one thing on this line to aim at,
   and it keeps the 44px floor the suite measures at all four widths. */
.jfoot{padding:3px 8px 3px 12px}
.jfp{margin:0;min-width:0;font-size:11.5px;line-height:1.35;color:var(--ink2);
  overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;
  -webkit-box-orient:vertical}
.jfp b{font-weight:700;color:var(--ink)}
.jfv{font-size:12px;min-height:44px}

/* ----------------------------------------------------------------------------------------------
   2. §1's HEIGHT: THE ONE-LINE ATTENTION SUMMARY, AND THE MEASURED GRID
   ------------------------------------------------------------------------------------------- */

/* THE MOCK-UP'S OWN LINE: `Needs attention 0   Nothing overdue.` It replaces a heading, a count
   chip and a sentence on three lines' worth of `shead` furniture, and when anything IS late the
   same line is the summary of a `<details>` with the old rows inside it. */
.attline{display:flex;align-items:center;gap:9px;margin:0 0 10px;padding:3px 2px;
  min-height:26px;font-size:12px;color:var(--ink3);list-style:none;min-width:0}
.attline::-webkit-details-marker{display:none}
.attk{font-family:var(--ui);font-size:12px;font-weight:750;color:var(--ink);white-space:nowrap}
.attline .n{font-family:var(--ui);font-size:11px;font-weight:700;color:var(--ink2);
  display:inline-flex;min-width:22px;height:22px;padding:0 6px;align-items:center;
  justify-content:center;border-radius:999px;background:var(--paper2);flex:0 0 auto}
.attline em{font-style:normal;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.attfold{margin:0 0 10px}
.attfold > .attline{margin:0;cursor:pointer;min-height:36px}
.attfold[open] > .attline{margin-bottom:8px}
.attch{display:inline-flex;align-items:center;color:var(--ink3);margin-left:auto;flex:0 0 auto;
  transition:transform .14s ease}
.attfold[open] .attch{transform:rotate(90deg)}
.attfold > .attline:hover{color:var(--ink2)}
/* A LATE PROJECT IS STILL COLOURED WHEN THE FOLD IS SHUT. The count wears the late tint rather
   than the neutral one the zero state wears, because the whole value of this line at a glance is
   whether the number is nought. */
.attfold > .attline .n{background:var(--lateBg);color:var(--late)}

/* THE PAGE HEADER, AT THE SPACING THE IMAGE DRAWS. `.phead` carried `--sp-5` below it for a
   header that also had a sentence under it; the sentence is gone from the card tabs, so the
   gutter that was separating two things is now separating one thing from the board. */
/* `main:has(.pboard)` IS THE BOARD AND NOTHING ELSE IN THIS APP, and `:has()` is acceptable HERE
   for the reason it was refused for the panel's column count: this is a MARGIN. A browser too old
   to match it draws the first pass's gutter, which is 8px more white space - not a lost card
   column. `.jdetails .jwho:has(> details[open])` above is the same bargain. */
main:has(.pboard) .phead{margin-bottom:var(--sp-3)}
.tabsbar{padding-bottom:5px}
.tabs{margin-bottom:6px}

/* ============================================================================================
   THE MEASURED GRID - §1, «fit SIX complete cards in TWO columns x THREE rows ... without
   scrolling at normal browser zoom», and it is the one thing on this page that genuinely needs
   `views/board.js`.

   WHY CSS ALONE CANNOT DO IT. The height available to the board is the viewport minus the
   masthead, minus the stage chips, minus the page header, minus the attention line - four
   elements whose heights depend on their own content (a long stage name wraps, a two-digit count
   is wider than a one-digit one) and on the reader's own text size. There is no container query
   for height and no CSS expression for "what is left below this element", so the number is
   measured or it is guessed. It was guessed for one afternoon and the guess was 400px wrong.

   WHAT `board.js` SETS. One custom property on `.jobs`, `--jobh`, which is
   `floor((available - 2 * gap) / 3)` - the height that makes exactly three rows end at the bottom
   of the viewport. It sets it ONLY when that number clears `--jobh-min`, the height below which
   this card stops being readable; under that floor it sets nothing at all and the board keeps its
   natural heights and scrolls, which is the honest answer and is what every reader gets today.

   WITHOUT THE SCRIPT NOTHING IN HERE APPLIES. `grid-auto-rows` falls back to `auto`, which is
   `.jobs`' own behaviour and the behaviour on every other tab. That is the §5 bargain kept: the
   script makes six fit, it is not what makes the board work.
   ========================================================================================== */
:root{--jobh-min:168px}
.jobs{grid-auto-rows:var(--jobh,auto)}
/* WHEN THE ROW HEIGHT IS FIXED, A LONG NAME ELLIPSES INSTEAD OF BEING CLIPPED. `.job` is
   `overflow:hidden`, so a third line of title in a row whose height the viewport decided would
   simply be cut off mid-letter - and a name cut off by a box is indistinguishable from a name
   that ends there. Two lines and an ellipsis is a visible statement that there is more, and the
   whole name is in the panel one tap away, in the panel's own `<h2>`, untruncated.
   IT IS SCOPED TO `[style*="--jobh"]` so a board with no script - or one whose available height
   could not fit three readable rows - keeps the first pass's rule, where a long name simply wraps
   and the row grows with it. */
.jobs[style*="--jobh"] .jidtx .jtitle{display:-webkit-box;-webkit-line-clamp:2;
  -webkit-box-orient:vertical;overflow:hidden}

/* ----------------------------------------------------------------------------------------------
   3. §3: THE COLLAPSIBLE LEFT RAIL

   THE TOGGLE IS A LINK TO `/prefs` AND THE STATE IS A COOKIE - see `sidebar` in
   `views/layout.js` for why it is not script, and `readPrefs` in `app.js` for why it is a cookie
   and not `localStorage`. These rules are the two states.
   ------------------------------------------------------------------------------------------- */
:root{--side-w-min:68px}

/* ============================================================================================
   THE BOARD IS NOT PROSE, SO IT IS NOT CAPPED AT A LINE LENGTH - 2026-09-26, and this is the
   single change that makes §1 and §3 possible at all.

   WHAT WAS WRONG, MEASURED. `.wrap` is `max-width:760px` and steps to 1280 only at a 1200px
   window. At the reference viewport - a 1024px iPad in landscape - that caps the content at 760
   whatever the rail is doing, and two consequences follow that no amount of card tuning could fix:

     THE BOARD DREW ONE CARD COLUMN. `.jobs` is `repeat(auto-fill,minmax(360px,1fr))` with a 16px
       gap, and two tracks need 736px against 732 of content. Four pixels short, since September.
     AND COLLAPSING THE RAIL BOUGHT THE GRID NOTHING. His §3 says «The card grid must resize
       automatically when navigation changes»; with the cap in place the 124px the rail gave back
       became wider margins and the cards did not move at all. Measured at 1024: card 732px with
       the rail open, card 732px with it collapsed.

   SO THE CAP COMES OFF THE BOARD AND STAYS ON EVERYTHING ELSE. `body.board` is set by `page()`
   only on a render that drew a grid of cards (`ctx.cards` in `views/pages.js`) - the same
   condition the board's two scripts are linked under, because it is the same fact. The other
   thirty-eight pages in this app are columns of text, rows and forms, 760px is the measure they
   were designed to, and not one of them moves a pixel.

   IT IS ON THE WHOLE SHELL AND NOT ONLY ON `<main>`, deliberately: the masthead, the breadcrumb
   and the stage chips all ride on `.wrap` too, and a board that spanned the window under a chip
   row that stopped at 760 would have the first card and the first chip beginning at different
   x - which is exactly what the mock-up does not do.

   FROM 768 UP AND NOT ON A PHONE. Below 768 the cap is already wider than the screen and this
   rule would mean nothing; above it, the width that is actually available is `100vw` minus
   whatever `body` is padding for the rail, which is what `none` resolves to.
   ========================================================================================== */
@media(min-width:768px){ body.board .wrap{max-width:none} }
.railtog{display:none}
@media(min-width:768px){
  /* THE CONTROL, in the top-left corner where the drawing puts it. It is above the wordmark and
     inside the rail, so it moves with the rail and cannot end up floating over the page. */
  .railtog{display:inline-flex;align-items:center;justify-content:center;
    width:38px;height:38px;min-width:38px;min-height:38px;margin:0 0 var(--sp-3);
    border-radius:var(--r-md);color:var(--side-ink2);text-decoration:none;
    background:var(--side-hover);border:1px solid transparent;
    transition:background .14s ease,color .14s ease}
  .railtog:hover{background:var(--side-on-bg);color:var(--side-ink)}
  .railtog:focus-visible{outline:2px solid #fff;outline-offset:-2px}
  .railtogi{display:inline-flex;align-items:center;color:currentColor}
  /* THE TWO CHEVRONS OVERLAP INTO THE DRAWING'S DOUBLE ARROW rather than being a third glyph
     nobody else in this app uses. `I_CHEV` twice, the second pulled back over the first. */
  .railtogi svg:last-child{margin-left:-7px}
  /* AND IT POINTS THE WAY IT WILL MOVE. One drawing, rotated - see `sidebar` in
     `views/layout.js` for why the markup does not branch. */
  .railmin .railtogi{transform:rotate(180deg)}

  /* THE COLLAPSED RAIL. Same links in the same order with the same gold active door - only the
     words and the two pieces of chrome that need words are out of the layout. */
  .railmin{width:var(--side-w-min);padding-left:var(--sp-2);padding-right:var(--sp-2);
    align-items:center}
  .railmin .rlabel{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
    clip-path:inset(50%);white-space:nowrap;border:0}
  .railmin .bsub,.railmin .railfoot{display:none}
  /* THE WORDMARK BECOMES ITS INITIALS, which is the drawing's own collapsed brand: `EMPIRE MENA`
     at 68px would wrap to four lines. It is `text-indent` and an `::after` rather than a second
     element, so the accessible name of the link is untouched - a screen reader still hears the
     company's name, in both states. */
  .railmin .brand{padding:0 0 var(--sp-4);text-align:center}
  .railmin .bname{font-size:0;letter-spacing:0}
  .railmin .bname::after{content:"EM";font-size:14px;letter-spacing:.06em;
    font-weight:var(--w-heavy);color:var(--side-ink)}
  .railmin .raillinks{align-items:center;width:100%}
  .railmin .rb{justify-content:center;gap:0;padding:0;width:44px;min-width:44px;
    align-self:center}
  .railmin .railtog{margin-left:auto;margin-right:auto}
  .railmin::after{display:none}

  /* AND THE PAGE TAKES THE WIDTH THE RAIL GAVE UP. This is the half §3 names by its effect -
     «The card grid must resize automatically when navigation changes» - and it needs no second
     mechanism: `.pboard` is a container, `body`'s padding decides how wide it is, and every
     `@container pboard` rule below re-evaluates the moment this number changes, whether it
     changed because a page was rendered with the cookie set or because `board.js` moved the class
     with no navigation at all. */
  body.navmin{padding-left:calc(var(--side-w-min) + var(--sp-4))}
  body.navmin .skip:focus{left:calc(var(--side-w-min) + var(--sp-4))}
}
@media(min-width:1200px){
  body.navmin{padding-left:calc(var(--side-w-min) + var(--sp-5))}
}

/* ----------------------------------------------------------------------------------------------
   4. §4: FOUR TABS IN THE WIDTH THREE HAD

   `Overview | Dates | Files | Costs & team` in a 360-400px panel. `Costs & team` is the long one
   and it is the one that decides the size; the strip scrolls rather than wraps, because a tab
   strip that wraps to two lines takes a line of the panel's height on every project.
   ------------------------------------------------------------------------------------------- */
.dptabs{gap:2px}
/* ============================================================================================
   `min-width:44px` AND IT IS NOT DECORATION - measured, 2026-09-26, and this rule put it there.

   The base `.dpt` is `padding:0 12px; font-size:13px`, under which the three-tab strip cleared the
   44px floor on its shortest label with room to spare. Four tabs in a 300px panel needed the
   padding down to 7px, and `Files` - the shortest word of the four - came out **42px wide** at
   390, 834, 1024 AND 1280. The height was never the problem (the base rule's `min-height` holds it
   at 44); the WIDTH fell through, on one tab, at every width this suite drives.

   IT IS THIS SLICE'S OWN REGRESSION AND NOT INHERITED DEBT, which is why it is fixed here rather
   than written down the way `.acct`'s 28px rows are: §4 added the fourth tab, this rule made room
   for it, and a control that was a legal target the day before is not one now.

   TWO PIXELS, AND THE STRIP STILL DOES NOT SCROLL. 44 + 49 + 70 + 94 plus three 2px gaps is 263
   inside the 298px of content the narrowest panel this layout draws has, so nothing starts
   scrolling and no label wraps. `justify-content` is untouched.
   ========================================================================================== */
.dpt{font-size:12px;padding:9px 7px;white-space:nowrap;min-width:44px;justify-content:center}
@container pboard (min-width:1100px){ .dpt{font-size:12.5px;padding:10px 9px} }

/* ----------------------------------------------------------------------------------------------
   5. THE REFERENCE VIEWPORT'S THRESHOLD - and this is the arithmetic that decides §1.

   THE OLD NUMBER WAS 900 OF CONTAINER and it was right for the rail it was measured against. With
   the full 192px rail, a 1024px window leaves the board 732px, so the panel was an overlay drawer
   there - which is correct when the reader has not collapsed anything.

   HIS §1 ASKS FOR SOMETHING ELSE: six cards «while the right-hand details panel is open and the
   left navigation is collapsed». Collapsed, the rail is 68px, so a 1024px window leaves the board
   about 940px - and an overlay drawer would COVER the cards he is counting. The panel has to be
   beside the board at the reference viewport or §1 cannot be satisfied at all.

   SO THE SUM, AT 940 OF CONTAINER:  940 - 20 gap - 320 panel = 600 for the board, which is two
   card columns of 292px each. That is under `.jobs`' own 360px floor, so the floor moves WITH the
   threshold and for the same reason - and it is not a free change: 292px is a narrower card than
   this app has ever drawn two-up, and it is why every size in section 1 above stepped down.
   Measured at 1024 x 690 with the panel open and the rail collapsed, nothing wraps, nothing
   ellipses that was not already ellipsing, and no control falls under 44px - a claim this block
   got WRONG for a few hours and `board-panel.test.js` caught: the four-tab squeeze below took the
   panel's `Files` tab to 42px wide. It has a `min-width` now. The sentence stands because the
   sweep that checks it does, not because it was written down confidently.

   THE PANEL TRACK IS 320 AND NOT 360 at this size for the same arithmetic: at 360 the board is
   560 and the cards are 272, which is where the station labels start to collide.

   `@container` AND NEVER `@media`, for this file's standing reason: the question is how much room
   the BOARD has, and at a 1024px window that is 732 or 940 depending on a cookie. A media query
   would answer "1024" to both.
   ------------------------------------------------------------------------------------------- */
@container pboard (min-width:860px){
  .pbgrid.pbopen{grid-template-columns:minmax(0,1fr) 320px}
  .pbmain{grid-column:1}
  .dpanel{grid-column:2;position:static;width:auto;align-self:stretch}
  .dpin{top:calc(var(--stick) + 10px);max-height:calc(100dvh - var(--stick) - 20px);
    box-shadow:var(--shadow)}
  /* TWO COLUMNS AT THIS WIDTH, AND THE FLOOR MOVES WITH THE THRESHOLD. `.jobs`' `auto-fill`
     minimum is 360px - the width below which the FIRST pass's card stopped being readable - and
     §2's card is a smaller thing: no figure row, no deadline pair, no preparation chips. 280 is
     where its station labels begin to collide, measured, and it is written as the floor rather
     than as a column count so that a board that really is too narrow still collapses to one. */
  .jobs{grid-template-columns:repeat(auto-fill,minmax(min(280px,100%),1fr));gap:12px}
}
@container pboard (min-width:1100px){ .pbgrid.pbopen{grid-template-columns:minmax(0,1fr) 380px} }

/* ==============================================================================================
   6. THE LAST OF §1's HEIGHT - the card clamped, and the board's own chrome at the mock-up's
   spacing.

   HIS QUESTION, when the first estimate came back saying six cards could not fit: «وليش ماتعمل
   الهيدر و التبويبات و المسافات متل الصورة ؟» - why not make the header, the tabs and the spacing
   like the image. He was right that it is the first lever, and everything in this section is that
   lever pulled, measured at 1024 x 690 with the panel open and the rail collapsed.

   WHAT IT BOUGHT, AND WHAT IT DID NOT, is written in `design-target/PRODUCTION-BOARD-V2.md` and in
   the report that went with it. The short version, because a stylesheet should not have to be read
   to know whether a requirement was met: the chrome above the first card came down from 272px to
   the number this section leaves, and the card came down with it - and six COMPLETE cards still do
   not fit 690px of usable height without taking the type and the tap targets below what he said
   not to take them below. The board scrolls for the last row, which is the honest outcome, and
   `views/board.js` refuses to fake it.
   ============================================================================================ */

/* ----------------------------------------------------------------------------------------------
   THE CARD IS CLAMPED, ALWAYS, AND NOT ONLY WHEN A HEIGHT WAS MEASURED.

   IT WAS SCOPED TO `.jobs[style*="--jobh"]` FOR AN HOUR AND THAT WAS EXACTLY BACKWARDS: the
   clamp is what lets the fit be measured, so scoping it to a measured fit meant it could never
   apply. Three lines of a 40-character project name in a 159px text column is 54px of card, on
   every card, and it is why the first measurement came back at 316px.

   NOTHING IS LOST, WHICH IS THE ONLY REASON A CLAMP IS ACCEPTABLE HERE. The panel's `<h2 class=
   "dpname">` carries the whole name, untruncated, one tap away - and `View details` is on the same
   card, in the footer, under the clamp. The first pass refused to truncate a name because the card
   was the only surface that had it; it is not any more.
   ------------------------------------------------------------------------------------------- */
.jidtx .jtitle{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;min-height:calc(2 * 1.14em)}
/* THE PERSON AND THE PLACE STAY ON ONE LINE. Each already ellipses on its own; what this stops is
   the FLEX ROW wrapping them onto two when the two together are a few pixels too wide, which on a
   280px card is most of the time and costs 16px of every card in the list. */
.jwho2{flex-wrap:nowrap;overflow:hidden}
/* AND SO DOES THE PROGRESS SENTENCE. `Now at Painting · 2 done, 4 to go, 1 not needed` is the
   longest this sentence gets; two lines of it pushed `View details` down on exactly the cards that
   have the most going on. */
.jfp{-webkit-line-clamp:1;white-space:nowrap}

/* ----------------------------------------------------------------------------------------------
   THE CHROME ABOVE THE FIRST CARD, AT THE IMAGE'S SPACING.

   Every number here was measured against the mock-up's own proportions, scaled from its 1448px
   canvas to the 1024px reference viewport (a factor of 0.707), and then held back wherever the
   scaled number would have broken the 44px tap-target floor. That floor is not traded: the
   mock-up's search box scales to 27px tall and its `View details` to 21px, and neither is a
   control this app is willing to draw at that size on a touch screen.

   IT IS SCOPED TO `body.board`, so the other thirty-eight pages keep the spacing they were
   designed with. The board is the only page in this app with a fixed number of things that have
   to fit a fixed height.
   ------------------------------------------------------------------------------------------- */
@media(min-width:768px){
  /* THE MASTHEAD DOES NOT MOVE, AND IT WAS THE FIRST THING TRIED. It is 63px at every width in
     this app, `--stick:63px` is declared off that number so the details panel sticks BELOW it
     rather than under it, and `board-panel.test.js` presses the panel's close button with
     `elementFromPoint` to prove it. Seven pixels of header is not worth a tap that opens the
     account menu instead of closing the panel. */
  body.board .backrow{margin-top:7px;margin-bottom:0}
  body.board .tabs{margin-bottom:2px}
  body.board .tabsbar{padding-top:0;padding-bottom:4px}
  body.board main#main > .phead{margin-bottom:8px}
  body.board .ptile{width:34px;height:34px;border-radius:10px}
  body.board .ptile svg{width:19px;height:19px}
  body.board .phead .shead h2{font-size:23px}
  body.board .attline{margin-bottom:6px;min-height:24px}
  /* THE BREADCRUMB AND THE CHIP ROW, at the image's spacing. The mock-up draws the `PRODUCTION`
     crumb and the stage chips tight against each other and against the heading; this app had a
     12px gutter under the masthead, another under the crumb and 8px under the chips, which is
     most of a card row between the masthead and the heading. Nothing is removed - both rows are
     still there, still the same controls, still above the 44px floor. */
  body.board .backrow{margin-top:6px;margin-bottom:0}
  body.board .backrow .crumb{min-height:30px;padding-top:0;padding-bottom:0}
  body.board .attfold > .attline{min-height:30px}
  /* THE PANEL AT THE REFERENCE VIEWPORT IS 300 AND NOT 320, which is the mock-up's own proportion
     (414px of a 1448px canvas is 293 at this scale) and gives the board back 20px - one pixel more
     than the gap between the two card columns. */
  .pbgrid.pbopen{gap:14px}
}
@container pboard (min-width:860px){ .pbgrid.pbopen{grid-template-columns:minmax(0,1fr) 300px} }

/* ----------------------------------------------------------------------------------------------
   THE FOLD CHEVRON COSTS THE CARD NO WIDTH - measured, 2026-09-26, and it was worth 43 pixels.

   `.jident` is a three-column grid, `"no tx gear"`: the thumbnail, the text, and `.jacts` holding
   the chevron. On a 293px card - which is what two columns beside an open panel come to at the
   reference viewport - that third track and its gutter took 43px of 267px of content, leaving the
   text column 127px. At 127px the ORDER NUMBER AND THE STATUS BADGE WRAP ONTO TWO LINES, which is
   16px of every card in the list, and the badge stops being beside the number, which is the one
   thing his §2 says about that row.

   SO THE CHEVRON COMES OUT OF THE FLOW AND SITS IN THE CORNER. The grid is two columns again, the
   text gets its 170px - which is about the proportion the approved image draws - and the number
   and the badge are on one line at the reference viewport again.

   IT IS STILL A 32px TARGET AND IT IS STILL THE `<summary>`'s OWN MARKER IN SPIRIT, so it is still
   not a button: a button inside a summary is a second activation target on top of the one the
   summary already is. `padding-right` on `.jidhead` is what keeps the status badge out from under
   it - the badge is the only thing on that row that can reach the corner.

   AND THE APPROVED IMAGE HAS NO CHEVRON AT ALL. It is kept because the control it belongs to is
   HIS - «بدي زر بالـ Production / Orders اذا كبستو كل الكروت يفتحو و اذا رجع كبستو يتكسرو»,
   2026-09-20 - and a fold with no affordance is a fold nobody finds. What the image decides here
   is that it may not cost the card a column.
   ------------------------------------------------------------------------------------------- */
.jident{grid-template-columns:auto minmax(0,1fr);grid-template-areas:"no tx"}
.jacts{position:absolute;top:7px;right:7px;z-index:3}
.jidhead{padding-right:30px}
.jfold{padding:5px;min-width:30px;min-height:30px}

/* THE SECTION MARK ON THE CARD'S STATIONS BOX IS THE CARD'S SIZE, NOT THE PANEL'S. `.tico` is a
   30px tile with an 18px glyph, which is right at the head of a section in a details panel and is
   a third of the height of a station block on a 250px card. The PANEL's copy is untouched. */
.jprod.nonote .jph{gap:7px;font-size:11.5px}
.jprod.nonote .jph .tico{width:22px;height:22px;border-radius:7px}
.jprod.nonote .jph .tico svg{width:14px;height:14px}
.jprod.nonote{padding:8px 10px 7px}

/* THE FOOTER IS THE TAP TARGET AND TWO PIXELS. 44px is the floor and it is not traded; what came
   off is the padding around it. */
.jfoot{padding:1px 8px 1px 12px}

/* ----------------------------------------------------------------------------------------------
   AND THE LAST FEW PIXELS, ALL OF THEM PADDING - measured at 1024 x 690, panel open, rail
   collapsed. Nothing below this line removes an element, changes a word or moves a control; it is
   the spacing of the approved image applied to a card that already draws exactly what the image
   draws. What it does NOT do is take a tap target under 44px or the body type under 11.5px, and
   the note at the head of section 6 says what that costs against his §1.
   ------------------------------------------------------------------------------------------- */
.jident{padding:9px 12px 8px;gap:6px 11px}
.jidtx .jtitle{font-size:15.5px;line-height:1.15;min-height:calc(2 * 1.15em)}
@container job (min-width:430px){ .jidtx .jtitle{font-size:17px} }
.jidhead{margin:0 0 2px}
.jmeta{gap:1px}
.jprod.nonote{margin:0 12px 8px;padding:7px 10px 6px}
.jprod.nonote .jpwrap{margin-top:6px}
.jprod.nonote .jph{font-size:11px}
.jprod.nonote .st span{font-size:7.5px;margin-top:4px}
.jprod.nonote a.st{padding:8px 2px 6px}

/* THE CARD'S OWN BOTTOM MARGIN INSIDE THE GRID WAS 16px OF DOUBLE SPACING, and it is a real bug
   rather than a tuning: `.job` carries `margin:0 0 16px` for the places a card stands on its own,
   and `.jobs` is a GRID with its own `gap`. Measured at the reference viewport the row pitch came
   back 282px on a 253px card - 29px of gutter where the grid asked for 12. It has been there since
   `.jobs` became a grid; it never showed because nobody had measured a row pitch before §1. */
.jobs > .job{margin-bottom:0}

/* THE LAST OF THE CHROME, and every one of these is a padding. The chip row, the page heading and
   the attention line keep every control they had and none of them goes under the size this app
   already drew it at - `.tab` is still its own 34px min-height, which is the size the stage chips
   have been since September. */
@media(min-width:768px){
  body.board .tabsrow{padding:0 0 2px}
  body.board .tabsbar{padding-bottom:2px}
  body.board main#main > .phead{margin-bottom:6px}
  body.board .phead .shead{margin-bottom:0}
  body.board .attline{min-height:22px;margin-bottom:5px}
  body.board .attfold > .attline{min-height:28px}
}

/* `View details` IS ONE LINE, AND THE PROGRESS SENTENCE IS WHAT GIVES WAY - measured at the
   reference viewport, where the two of them share a 269px footer.

   Without this the link wrapped to `View` / `details` on exactly the cards whose progress sentence
   is longest (`Now at Painting · 2 done, 4 to go, 1 not needed`), which put 18px back on the card
   and broke the one thing his §2 says about this row - «Progress summary at bottom left and View
   details at bottom right». The link is the DESTINATION and the sentence is a STATE, so the
   sentence is the one that ellipses; the whole of it is in the panel's own stations block, which
   is where that link goes. */
.jfv{white-space:nowrap;flex:0 0 auto}
.jfp{flex:1 1 auto;min-width:0}

/* THE PANEL'S OWN HEAD AT THE REFERENCE VIEWPORT. At 300px the 96px thumbnail and the close
   button left the project's code, its salesperson and its location about 150px, and all three
   ellipsed to three characters. The frame steps down with the panel, exactly as the card's does
   with the card - `.jthumb`'s own `aspect-ratio` keeps the proportion his §2 asks for. */
@container pboard (max-width:1099px){
  .dphead{padding:12px 12px 10px;gap:9px}
  .dphead > .jthumb{width:68px}
}

/* ==============================================================================================
   THE `Delivery status` LABEL WAS NEVER BEING STYLED, AND THE SELECTOR SAYS WHY - found 2026-09-26
   on the panel's new `Dates` tab, PRE-EXISTING, and fixed here rather than written down.

   `.jdd > .dline.deliv .dlx > i` above sets that label: 8.5px, uppercase, tracked, in the shipping
   tint, `nowrap` with an ellipsis. It has never matched anything. The markup `delivChip` emits is
   `<span class="dlx"><span class="dlh">…<i>Delivery status</i></span>…</span>` - the `<i>` is
   inside `.dlh`, so it is `.dlx > .dlh > i` and not `.dlx > i`. The rule was written for a shape
   this element had before `.dlh` was introduced between them, and it has been drawing the browser's
   default ITALIC SERIF ever since.

   IT IS FIXED AND NOT RECORDED, WHICH IS THE OPPOSITE OF WHAT THIS FILE USUALLY DOES WITH
   PRE-EXISTING DEBT - the `.acct` and `.moneytog` heights are written down and left alone. Two
   things make this one different. It is on the surface §4 just created, in the tab §2's removals
   were moved to, so it is the first thing he will look at that he has not seen before; and it is
   the same KIND of fault as §4 of the first brief («Fix the current Finance label wrapping into
   individual vertical letters»), which was also a rule not reaching the element it named, also on
   a label, and which he had photographed. The card drew this identically before this pass - the
   panel is simply narrower, so it is now unmistakable.

   THE DECLARATIONS ARE THE ONES ABOVE, UNCHANGED, AT THE SELECTOR THAT REACHES THEM. Nothing new
   is decided here; the intent was already written, twice, in this file.
   ============================================================================================ */
.jdd > .dline.deliv .dlh > i{font-style:normal;font-size:8.5px;font-weight:750;
  letter-spacing:.09em;text-transform:uppercase;color:var(--ship);white-space:nowrap;
  max-width:100%;overflow:hidden;text-overflow:ellipsis}

/* ==============================================================================================
   THE OVERLAY DRAWER ON A PHONE, WHEN THE BOARD BEHIND IT IS SHORTER THAN IT IS - measured at 390
   on 2026-09-26, and it is this pass's own regression rather than inherited debt.

   WHAT HAPPENED. `.dpanel` is `position:absolute; top:0; bottom:0` inside `.pboard`, so the drawer
   is exactly as tall as the BOARD behind it, and `.dpin` sticks inside that. On the find tab with
   one hit the board is one card: measured 281px, against 624px of panel content. The page had
   SEVEN PIXELS of scroll in a 900px window, so the pin could never rise to its sticky offset, its
   foot sat at y=898, and `.nav` - the floating bottom bar, fixed at y=825..890 with `z-index:40`
   over the drawer's 20 - covered the last 65px of it. `document.elementFromPoint` at the centre of
   `Replace the image` answered `NAV.nav`: the control was on the screen and could not be tapped,
   with no way to scroll it clear.

   IT WAS REACHABLE AT `1c967cf` ON THE SAME URL, and the reason it stopped is §2: the card was
   846px tall, so one card gave the page 561px of scroll and the pin lifted clear. **The compact
   card did not break this; it removed the accident that was hiding it.**

   THE FIX IS TWO RULES AND NEITHER IS A NUDGE.

     1. THE DRAWER GETS SOMETHING TO SCROLL IN. A sticky element can only rise if the document is
        taller than the viewport, and `.pboard`'s height is the board's. `min-height:100dvh` on the
        open grid means the page can always scroll the pin to its offset, whatever the board holds.
        It costs nothing when the panel is shut (`.pbopen` is on the grid only while something is
        in the second track) and nothing above 899 of container, where the panel is a COLUMN and
        the grid's own `align-self:stretch` already makes the aside as tall as the board.

     2. AND ITS FOOT CLEARS THE BAR. `body` already reserves `88px + env(safe-area-inset-bottom)`
        for that bar and the drawer, being absolutely positioned, was never inside that reserve.
        `.dpin`'s budget subtracts the same number, from the same place, below 768 - which is where
        the bar is, and it is a `@media` and not a `@container` deliberately: `.nav` is fixed to
        the VIEWPORT, so "is the bar on screen" is a question about the window and about nothing
        else. The value is written as the same expression `body` uses, so the two cannot drift.
   ============================================================================================ */
@container pboard (max-width:899px){
  .pbgrid.pbopen{min-height:100dvh}
}
@media(max-width:767px){
  .dpin{max-height:calc(100dvh - var(--stick) - 18px - 88px - env(safe-area-inset-bottom))}
}

/* ==============================================================================================
   THE PRODUCTION BOARD, THIRD PASS - his mock-up of 2026-09-27.

   `design-target/PRODUCTION-BOARD-V3.md` is the brief, recorded verbatim, and
   `design-target/production-board-v3-mockup.png` (1448 x 1086) is the drawing. It supersedes the
   second pass, which shipped as `eeec39a` and whose block is directly above this one: he has seen
   that board at the real viewport, accepted the measurement that six cards do not fit in 690px of
   usable height, and asked for a DIFFERENT CARD - «بدي نفس هي تمام», this one is right.

   EVERYTHING IN THE SECOND PASS'S BLOCK ABOVE STILL APPLIES except where a rule down here
   deliberately overrides it, which is the same arrangement that block has with the first pass.
   Two of its findings are load-bearing and are NOT reopened anywhere below:

     `views/board.js` STILL REFUSES TO COMPRESS A ROW below `--jobh-min` or below the height the
       cards naturally want. A bigger picture makes this card TALLER, so that refusal now fires at
       the reference viewport where before it was a near thing. It is asserted in
       `board-six.test.js`, not merely commented here.
     THE PHONE DRAWER STILL CLEARS THE BOTTOM BAR - `min-height:100dvh` on the open grid and
       `.dpin` subtracting the same reserve `body` makes for `.nav`. Both rules are at the foot of
       the block above and neither is touched.

   WHAT THIS PASS CHANGES, AND IT IS FOUR THINGS:

     1. §2  THE PICTURE. A 3:2 container across the top of the card, `object-fit:contain` on a
            neutral ground, the same height on every card, with the order number and the fold
            chevron in one pill over its top-right corner.
     2. §3  THE TEXT UNDER IT, IN HIS ORDER: status badge, title, reference code, owner and
            location - at the sizes his §3 names.
     3.     THE FOOTER LOSES ITS SUMMARY - «بدي شيل الستاتوس يلي جنب view details». See
            `cardFoot` in `views/card.js` for why that sentence overrode his own §4 and for what
            had to survive it.
     4. §1  THREE CARDS PER ROW WHERE THE AVAILABLE WIDTH ALLOWS, sized from the content area that
            is left after the rail and the panel. The threshold and the measurement that set it
            are argued at `--jobw-min` below, which is the most load-bearing number in this pass.
   ============================================================================================ */

/* ----------------------------------------------------------------------------------------------
   1. §2: THE PICTURE ACROSS THE TOP

   «Replace the tiny thumbnail beside the title with a large image area across the top of each
   card ... approximately a 3:2 image container ... object-fit: contain, centered on a subtle
   neutral background ... Give every card the same image-container height.»

   `.jident` STOPS BEING A GRID AND BECOMES A COLUMN, which is the whole shape change in one
   declaration. It was `grid-template-areas:"no tx"` - a thumbnail track and a text track side by
   side - and the drawing stacks them. `grid-template-areas:none` is written out rather than left
   to be inherited-and-ignored: a stale area name on a flex container is a rule that does nothing
   and reads as though it does something.
   ------------------------------------------------------------------------------------------- */
.jident{display:flex;flex-direction:column;align-items:stretch;gap:0;
  grid-template-columns:none;grid-template-areas:none;padding:12px 12px 10px}
.job:not([open]) > .jident{padding-bottom:12px}
@container job (min-width:540px){ .jident{gap:0} }

/* THE FRAME IS THE POSITIONING CONTEXT FOR THE BADGE, AND IT IS A REAL ELEMENT FOR ONE REASON:
   the overlay's inset has to be measured from the PICTURE and not from the card. `.jacts` was
   absolute against `.job` on the second pass, 7px from a corner that was the card's; with a
   picture inset by the card's own padding, the same two numbers would have put the badge outside
   the photograph - and would have had to be re-tuned every time `.jident`'s padding moved. */
.jframe{position:relative;width:100%;margin:0 0 10px}

/* THE 3:2 CONTAINER. The drawing's own proportion, measured off it: the first card's picture is
   299 x 198 on a 1448px canvas, which is 1.51. `aspect-ratio` rather than a height, so «the same
   image-container height» on every card in a row is a consequence of the cards being the same
   WIDTH rather than a number that has to be kept in step with the type below it.

   `contain` IS §2's WORD AND IT REPLACES `cover`. The old square frame cropped every picture to
   fill it; «so the entire stand or product remains visible without stretching or cropping» rules
   that out. The letterboxing `contain` leaves is what `--paper2` behind it is for - a subtle
   neutral ground, so a portrait photograph of a stand reads as a framed picture and not as an
   image that failed to load. */
.jthumb{grid-area:auto;width:100%;aspect-ratio:3/2;border-radius:12px;
  background:var(--paper2);border:0}
@container job (min-width:430px){ .jthumb{width:100%;border-radius:13px} }
@container job (min-width:540px){ .jthumb{width:100%;border-radius:14px} }
.jthumb img{object-fit:contain;background:var(--paper2)}
/* NOTHING TO SHOW IS STILL THE DASHED EMPTY FRAME at the new size, and the mark grows with it:
   a 20px glyph in the middle of a 190x127 box reads as a speck rather than as a placeholder. */
.jthumb.none{background:none}
.jthumb.none svg{width:34px;height:34px;opacity:.45}

/* ----------------------------------------------------------------------------------------------
   §2's «PROJECT-NUMBER BADGE AND EXISTING EXPAND/COLLAPSE CONTROL AT THE TOP-RIGHT, AS SHOWN»

   ONE PILL AND NOT TWO MARKS. The drawing puts the number and the chevron inside a single white
   rounded container with a hairline between them; drawn as two floating chips they would read as
   two unrelated controls sitting on a photograph.

   IT IS ABOVE THE PICTURE'S LINK AND THAT IS THE POINT, NOT AN ACCIDENT. Two targets share this
   corner: the pill folds the card - it is part of the `<summary>` and has no activation behaviour
   of its own - and the picture beside it opens the preview. `z-index:3` over the anchor is what
   decides which one a finger in the corner gets, and `board-panel.test.js` asserts both with
   `elementFromPoint` rather than trusting the stacking order to stay this way.
   ------------------------------------------------------------------------------------------- */
.jframe > .jacts{position:absolute;top:8px;right:8px;z-index:3;
  display:inline-flex;align-items:center;gap:0;padding:0;
  background:var(--card);border:1px solid var(--rule);border-radius:999px;
  box-shadow:var(--e-1)}
.jframe > .jacts .po{background:none;border:0;padding:4px 7px 4px 9px;font-size:12px;
  border-radius:999px 0 0 999px}
/* AND THE `#` COMES WITH IT. `.jidhead .po::before` is where that glyph has always been written,
   and the badge is not in `.jidhead` any more - so for one render of this board the pill read
   `421` where the drawing reads `# 421`. Found by holding the screenshot beside the mock-up,
   which is the check his last line asks for. The declarations are the ones above, unchanged, at
   the selector that now reaches the element. */
.jframe > .jacts .po::before{content:"#";margin-right:3px;color:var(--ink3);font-weight:750}
@container job (min-width:540px){ .jframe > .jacts .po{font-size:12.5px;padding:5px 8px 5px 10px} }
/* THE HAIRLINE IS THE CHEVRON'S OWN LEFT BORDER, so the two halves of the pill cannot come apart
   and there is no third element in the corner. It keeps the 30px box the second pass gave it -
   the fold is still the chevron's job and 30px is still what a finger needs to find it. */
.jframe > .jacts .jfold{min-width:30px;min-height:30px;padding:5px;opacity:.75;
  border-left:1px solid var(--rule);border-radius:0 999px 999px 0}
.jident:hover .jframe > .jacts .jfold{background:var(--paper2)}

/* ----------------------------------------------------------------------------------------------
   2. §3: THE TEXT UNDER THE PICTURE, IN HIS ORDER

   «Use this exact order: Small "IN ACTIVE" status badge, using the actual project status. Project
   title. Project/reference code. Owner and location, when available ... Titles should be
   approximately 15-16px, semibold, with comfortable line-height - not excessively heavy.
   Supporting information should be approximately 12-13px.»

   THE ORDER NUMBER IS NOT IN THIS LIST AND IS NOT IN THIS BLOCK - it is over the picture, which
   is §2's instruction and is what leaves the status badge first. `.jidhead` survives as the row
   the badge sits on, because `.jtop` is the element the live status picker's fragment points at
   and moving it would move an id.

   `padding-right:30px` CAME OFF IT. That was the second pass keeping the status badge out from
   under a chevron pinned to the card's top-right corner; the chevron is over the picture now and
   there is nothing in this row's right-hand end to avoid.
   ------------------------------------------------------------------------------------------- */
.jidtx{min-width:0;display:flex;flex-direction:column}
.jident > .jidtx > .jidhead{padding-right:0;margin:0 0 6px}

/* 15.5px AND WEIGHT 700 - «approximately 15-16px, semibold ... not excessively heavy». The size
   is what the second pass already measured to; the WEIGHT is the change. It was 800, which is
   this stylesheet's `--w-heavy` and is exactly the "excessively heavy" his sentence rules out,
   and the line-height goes 1.15 -> 1.3 for «comfortable line-height».

   THE TWO-LINE RESERVE MOVES WITH THE LINE-HEIGHT AND IS NOT A FIXED PIXEL NUMBER: `min-height`
   is `calc(2 * 1.3em)`, so §3's «Reserve the same two-line title space on every card so
   subsequent information aligns across the row» holds at whatever size a container step sets, and
   a one-line name and a two-line name put the code, the owner and the station strip at the same
   y. The clamp to two lines is the rule in the second pass's block and is unchanged; the whole
   name is in the panel's `<h2 class="dpname">`, untruncated, which is what makes a clamp
   acceptable at all. */
.jidtx .jtitle{font-size:15.5px;font-weight:700;line-height:1.3;letter-spacing:-.018em;
  margin:0 0 4px;min-height:calc(2 * 1.3em)}
@container job (min-width:430px){ .jidtx .jtitle{font-size:16px;min-height:calc(2 * 1.3em)} }

/* «SUPPORTING INFORMATION SHOULD BE APPROXIMATELY 12-13px» - the code, the owner and the place,
   which were 11 and 11.5 on the second pass because that card was fighting for height. This one
   is fighting for width instead, and 12px is his number. */
.jmeta{gap:3px;font-size:12px}
.jcode{font-size:12px;gap:6px}
.jsub.jwhoname{font-size:12px;gap:6px}
.jsub.jloc{font-size:12px}
.jwho2{gap:3px 10px}

/* ----------------------------------------------------------------------------------------------
   3. THE FOOTER IS ONE LINK - «بدي شيل الستاتوس يلي جنب view details»

   `.jfp` IS NOT RENDERED ANY MORE, so there is no rule for it here: `views/card.js` stopped
   emitting the element rather than this file hiding it. An element hidden by a stylesheet is
   still in the markup, still in the accessibility tree on some engines, and still a thing the
   next reader has to work out the status of.

   `margin-left:auto` IS WHAT PUTS THE LINK ON THE RIGHT with nothing on the left to push it
   there. `.jfoot` is `justify-content:space-between`, which does nothing for a single child.
   ------------------------------------------------------------------------------------------- */
/* THE FOOTER IS ITS LINK'S OWN 44px BOX AND ALMOST NOTHING ELSE. The 2px above it was there to
   separate a progress sentence from the stations box; with the sentence gone it is slack on a
   card that is already taller than the second pass's, so it comes off. The 44px floor is
   untouched - `.jfv` keeps its own `min-height` and `board-panel.test.js` measures it.

   AND THE TINT COMES OFF THE BAND - corrected 2026-09-27 by holding the screenshot beside the
   mock-up, which is the check his last line asks for. `.jfoot` has carried `background:var(
   --panel)` since the card was written and the drawing does not draw it: in the mock-up the
   footer is the card's own white with a hairline over it, and the only tinted box on that card
   is the production-stations panel. A second tinted band under it made the card read as two
   panels stacked rather than as one card with a rule across it. The HAIRLINE stays - it is in
   the drawing, and it is what keeps `View details` from floating loose under the stations. */
.jfoot{padding:0 8px 3px 12px;background:none}
.jfv{margin-left:auto;font-size:12.5px}

/* ----------------------------------------------------------------------------------------------
   THE STATIONS BOX KEEPS THE SECOND PASS'S COMPACT SIZE and gets its breathing room back at the
   top, because the text above it is now three short lines instead of a two-column block. Nothing
   about the seven, their order, their colours or their tap targets moves - his §4: «Preserve the
   seven stations and their current order: CNC, WD, PNT, MTL, LZR, PRT, FIN. Preserve the existing
   status calculations, colors, and active-station behavior.»
   ------------------------------------------------------------------------------------------- */
.jprod.nonote{margin:0 12px 6px;padding:8px 10px 7px}

/* ----------------------------------------------------------------------------------------------
   4. §6: THE PANEL'S HEAD - «A larger project image at the top. Status, clearly arranged project
   title, reference code, and owner underneath.»

   IT WAS A THREE-TRACK GRID - a 96px square, the words, the close button - and the drawing draws
   a row, then a picture spanning the panel, then the words. So the head becomes a COLUMN and the
   number and the close button share `.dphrow` above the picture.

   WHY THE CLOSE BUTTON DOES NOT GO ON TOP OF THE PICTURE the way the card's badge does: the
   card's badge sits over a photograph the reader is not being asked to aim at, and this is the
   one control that dismisses the panel. Over an arbitrary project's image its contrast is
   whatever that image happens to be in that corner. It keeps its 44px box and its own row.

   `.dphead > .jthumb{grid-column:1;grid-row:1}` ABOVE IS NOW A RULE FOR A GRID THAT NO LONGER
   EXISTS, and it is left where it is rather than deleted: `grid-column` and `grid-row` are inert
   on a flex item, and the note above it records a real measurement about named areas. This rule
   below is what places the picture now.
   ------------------------------------------------------------------------------------------- */
.dphead{display:flex;flex-direction:column;align-items:stretch;gap:0;padding:12px 12px 12px}
.dphrow{display:flex;align-items:center;justify-content:space-between;gap:8px;
  margin:0 0 9px;min-width:0}
.dphead .dpno{font-size:12px;letter-spacing:.04em;color:var(--ink);white-space:nowrap}
.dphead > .jframe{margin:0 0 11px}
.dphead > .jframe > .jthumb{border-radius:13px}
.dphead .dpx{margin:0;flex:0 0 auto}
.dphead > .dpid{min-width:0;gap:5px}
/* §3's ORDER IN THE PANEL TOO - the status badge first, then the name. `order` rather than moving
   the markup, because the markup is already in his order; this is here so the intent is stated
   once in each file. It is a no-op with the current DOM and is what keeps it a no-op. */
.dphead .dptop{order:0} .dphead .dpname{order:1} .dphead > .dpid > .jmeta{order:2}
.dpname{font-size:19px;font-weight:750;line-height:1.24;letter-spacing:-.022em}
.dphead > .dpid > .jmeta{font-size:12.5px;gap:4px}

/* ==============================================================================================
   5. §1: THREE CARDS PER ROW WHERE THE AVAILABLE WIDTH ALLOWS - and `--jobw-min` is the number
   that decides it.

   HIS §1: «With the right details panel open, show THREE equal-width project cards per row where
   the available width allows ... Measure the actual available content area after subtracting
   navigation and the details panel. Do not size the grid using the full window width.»

   THE COLUMN COUNT IS NOT WRITTEN DOWN ANYWHERE AND MUST NOT BE. `.jobs` is
   `repeat(auto-fill,minmax(min(--jobw-min,100%),1fr))`, so the number of columns is the answer to
   "how many cards of at least this width fit the room that is left" - which is exactly what his
   sentence asks, and it is asked of the CONTENT BOX after the rail and the panel have taken
   theirs, because `.jobs` is inside `.pbmain` inside the `pboard` container. A hard
   `repeat(3,1fr)` would have drawn three 120px cards on a narrow board and called it success.

   ==============================================================================================
   AND IT IS `max(--jobw-min, a third of the row)`, WHICH IS THE PART THAT MAKES IT THREE AND NOT
   AS MANY AS WILL FIT.

   `auto-fill` ANSWERS "AS MANY AS FIT" AND HIS SENTENCE SAYS "THREE". Measured on 2026-09-27 at
   a 1280 window with the rail expanded and the panel shut, a plain floor of 236 drew FOUR
   columns of 244px - a board that satisfies "at least the floor" and satisfies nothing he asked
   for. Making every track at least a THIRD of the row caps the count at three by arithmetic:
   four tracks of a third plus their gaps cannot fit a row, at any width, ever.

   SO THE TRACK MINIMUM IS THE LARGER OF TWO NUMBERS and each one does one job. The third-of-a-row
   term is what stops a fourth column; `--jobw-min` is what stops a third column on a board too
   narrow to hold three readable ones, and the board falls back to two, then to one. `min(100%,
   ...)` keeps a single-column phone from overflowing, which is `.jobs`' own long-standing guard.

   ==============================================================================================
   `--jobw-min` IS THE WIDTH BELOW WHICH THIS CARD STOPS BEING READABLE, AND IT IS 220px BECAUSE
   OF TWO MEASUREMENTS AND NOT BECAUSE IT IS A ROUND NUMBER.

     THE STATION ABBREVIATIONS START BEING CUT AT A 200px CARD. Measured in headless Chrome on
       2026-09-27 by drawing this card at every width from 320 down to 152: `scrollWidth -
       clientWidth` on the seven `<span>`s under the bars is 0 at a 204px card and 1px at 200px,
       rising to 8px at 152. Nothing else on the card clips first - not the project code, not the
       owner-and-place row, not `View details`, which is 115 x 44 at every width in that sweep.
       So 204 is the hard floor, the one below which the board would be telling the shop floor
       that a station is called `CN`.
     AND HIS OWN CARD IS 232px WIDE AT THIS VIEWPORT. The mock-up is 1448 across and draws its
       card 328 wide; scaled to the 1024px reference that is 232. That is the card he drew, at the
       size he drew it, on the screen he uses.

   220 SITS BETWEEN THEM AND NEARER HIS. It is 8% over the clipping floor rather than 2%, and 5%
   under his own card rather than 18% under it - which is the difference between "three columns"
   and «squeezing images and text», the thing his §7 says not to do. It is written as a custom
   property so that the test that measures it and the rule that uses it read the same number, and
   so that this paragraph is the only place it is argued.

   HIS §7 IS WHAT MAKES THIS THE HONEST SHAPE: «On narrower screens, reduce grid columns instead
   of squeezing images and text» and «Do not shrink the entire interface or use browser zoom to
   force six cards onto the screen». So the floor is a floor: where three of them do not fit, the
   board draws two, and where two do not fit it draws one. Nothing scales down to make a count
   come out right.

   ==============================================================================================
   WHAT THAT COMES TO AT THE REFERENCE VIEWPORT, AND IT IS THE ANSWER TO HIS §1's HARDEST CASE.
   Measured, 1024 x 690, the rail collapsed and the panel open - the state §1 names by name:

     the content area after the rail   912px
     the panel                         300px, and a 14px gutter
     THE BOARD                         598px
     a third of it                     191px  - under the 204 the labels need, and 41px under the
                                       card he drew
     SO THE BOARD DRAWS TWO            293px each, which is his card at 126% of the size he drew
                                       it rather than three at 82% of it

   THREE COLUMNS BESIDE AN OPEN PANEL ARRIVE AT A 1280 WINDOW WITH THE RAIL COLLAPSED - board
   850px, three of 275 - and at 1024 the moment the panel is closed - board 912, three of 296.
   Both of those are his three-up card at or above the size he drew it. The 1024-with-the-panel
   case is the one that cannot have it, and §7 already says what to do about that.
   ============================================================================================ */
/* ----------------------------------------------------------------------------------------------
   AND IT IS SCOPED TO `.pboard`, WHICH IS A CORRECTNESS FIX AND NOT TIDINESS.

   `.jobs` IS NOT ONLY THE BOARD. `views/record.js` uses the same class for the order record's
   two-column body (`<div class="jobs"><div>main</div><div>side</div></div>`), and a floor of 236
   there would break one page of prose into three columns of it. `.pboard` is the board's own
   query container and is emitted by `boardWithPanel` alone - every stage tab of `/orders` and the
   find results, which are exactly the surfaces that draw this card.

   `:not(.solo)` BECAUSE A CARD STANDING ON ITS OWN IS FULL WIDTH. `.jobs.solo` is an attention
   row or a single search hit; it is two class selectors, so without this the rule below would win
   on source order and put a lone card in a 236px track.
   ------------------------------------------------------------------------------------------- */
/* ONE GAP AT EVERY WIDTH, AND IT IS IN A VARIABLE BECAUSE THE TEMPLATE HAS TO SUBTRACT IT. The
   third-of-a-row term is `(100% - 2 * gap) / 3`; a gap that changed at a breakpoint while that
   expression went on subtracting the old one would draw four columns at exactly one width, which
   is the kind of bug that is found in a screenshot six weeks later. 12px is inside his §5's
   «12-16px gaps between cards» and is what the drawing measures to at this scale. */
:root{--jobw-min:220px;--jobs-gap:12px}
.pboard .jobs:not(.solo){gap:var(--jobs-gap);
  grid-template-columns:repeat(auto-fill,minmax(
    min(100%,max(var(--jobw-min),calc((100% - 2 * var(--jobs-gap)) / 3))),1fr))}

/* ==============================================================================================
   6. §2's IMAGE PREVIEW - «Tapping the image should open a larger image preview without also
   triggering the card-selection action.»

   THE MARKUP IS NOT RENDERED BY THE SERVER. This is the one thing on this page that exists only
   while a script is running: `views/board.js` builds `.jpv` on the first tap and reuses it. With
   every script blocked the picture is still an `<a href>` to the full-size bytes with
   `target="_blank"`, which is a larger view of it in a tab of its own - the behaviour that
   element has had since it was written, and the reason this enhancement is allowed to be script.

   SO THERE IS NOTHING HERE FOR A NO-SCRIPT PAGE TO MATCH, and the rules cost that reader nothing:
   no element with these classes is ever in their document.

   `position:fixed` AGAINST THE VIEWPORT AND `z-index:60`. The floating bottom bar on a phone is
   `z-index:40` and the details drawer is 20; a preview underneath either of them would be exactly
   the fault the second pass measured on the drawer, one element over. `100dvh` and not `100vh`
   for the same reason it is used there: a phone's dynamic toolbars.
   ============================================================================================ */
.jpv{position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;
  padding:24px;background:rgba(10,15,28,.78);border:0;cursor:zoom-out}
.jpv[hidden]{display:none}
.jpv img{max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;
  border-radius:12px;background:var(--card);box-shadow:0 18px 60px rgba(0,0,0,.45)}
/* THE CLOSE BUTTON IS A REAL BUTTON AT THE 44px FLOOR. The backdrop closes too - `cursor:zoom-out`
   says so - but a backdrop is not a control a keyboard or a screen reader can find. */
.jpvx{position:absolute;top:14px;right:14px;width:44px;height:44px;border-radius:12px;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--card);color:var(--ink);border:1px solid var(--rule);cursor:pointer}
.jpvx:hover{background:var(--paper2)}
.jpvx:focus-visible{outline:2px solid #fff;outline-offset:2px}
@media(prefers-reduced-motion:no-preference){
  .jpv{animation:fadein .16s ease both}
}
@keyframes fadein{from{opacity:0}to{opacity:1}}

/* ==============================================================================================
   THE CREATIVE CARD, 2026-09-27 — TWO CONTROLS AND ONE MARK, AND EVERY SELECTOR BELOW NEEDS A
   NAME THIS APP DID NOT HAVE THIS MORNING.

   ==============================================================================================
   `.wfset` / `.wfsetb` — ITEM 1: «When the designer open the creative request and assign the
   names option should view below the header bar not at the end of page».

   It is the same 44px control `.wfack .wfackb` already is and it is deliberately NOT the same
   colour. That one is `--go`, this app's filled primary, and it means «somebody is waiting on
   you»; this is the reader's own housekeeping and the two can be drawn on one record at once, so
   a second green button eleven pixels down would have made the accept stop reading as the
   urgent one. What it gets instead is the OUTLINED half of the same pair — `--rule` on the
   border, `--paper` behind it, `--ink` on the word — which is what `.pbtn`-shaped secondaries
   wear everywhere else in this file. Same height, same corners, same type.

   `margin-top:10px` MATCHES `.wfack` EXACTLY, so the gap under the bar is the same whether one
   control is drawn or both, and the two never sit on one line: each is its own block, which is
   what keeps `Accept & Start` and `Set designer` from being mistaken for a pair of equals.

   MEASURED IN CHROME AT 390, 834, 1024 AND 1280, in both themes, on a record at `Waiting
   Assignment` (control alone), at `Brief Change Requested` (accept above it) and at `On Hold`:
   the button is 44px, `elementFromPoint` at its centre answers the anchor, the tab strip is
   still above the fold at 390, and nothing scrolls sideways.

   ==============================================================================================
   `.row.wfamd` / `.stat.wfamdc` — ITEM 5: «when the changing brief showing in the latest it's
   should to be more visible two the designer notic the changing so I think we can highlighted by
   red the extra requirement or the new brief».

   NO COLOUR IS INVENTED. `--late` and `--lateBg` are the pair `.dstat.iss`, `.pchip.iss` and
   `.wfbar.wfb-iss` already wear, so both themes and both contrast ratios were paid for when the
   red was first measured. The CHIP is the reverse of that pair — `--late` behind, `--paper` on
   the word — because a tinted pill inside a tinted row is a pill you cannot see; the ROW is the
   tint plus a 3px rule down its edge, which is `.wfbar`'s own construction one screen up.

   IT REACHES THE WHOLE ROW AND NOT JUST THE WORDS, which is why the class is on `.row` rather
   than on a span inside `.k`: the point of the mark is that an eye scanning the Latest block
   stops at it, and three lines of 13.5px text going red is less visible at arm's length than one
   tinted band with a red edge.

   `.row.wfamd` NEEDS `.wfamd`, WHICH `latestBlock` IN `views/creative.js` IS THE ONLY THING IN
   THIS APP THAT EMITS, and it emits it on one row of one block. `.row`, `.row .k`, `.row .k em`,
   `.rows`, `.stat` and `.stat.dlv` are byte-for-byte what they were, so the other two rows of
   this block, the seven fact tiles, the version list, the request bar and the file table on both
   creative cards are in the same place at all four widths. The 3px edge comes off `.row .k`,
   whose floor is `min(100%,180px)`, so the wrap point moves by three pixels and the line still
   breaks where it broke.
   ============================================================================================ */
.wfset{display:flex;flex-wrap:wrap;gap:10px;margin-top:10px}
.wfset .wfsetb{display:inline-flex;align-items:center;justify-content:center;min-height:44px;
  padding:0 18px;border:1px solid var(--rule);border-radius:var(--r-md);background:var(--paper);
  color:var(--ink);font-family:var(--ui);font-size:13.5px;font-weight:700;letter-spacing:-.005em;
  text-decoration:none;cursor:pointer}
.wfset .wfsetb:hover{border-color:var(--ink3);box-shadow:0 0 0 1.5px var(--rule)}
.wfset .wfsetb:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
/* AND THE ROSTER THIS CONTROL OPENS CLEARS THE 44px FLOOR — measured at 390 and found at 42.
   `.prow` is `min-height:42px` and is shared by every picker in this app, so it is NOT edited;
   what is edited is the ONE instance item 1 put under the status bar, which is the adjacent
   sibling of `.wfset` and of nothing else. That is the identical, already-shipped shape of
   `.cforms > .pick:target > .prows > .pchip.prow{min-height:44px}` eighty lines down - the same
   two pixels, bought the same way, on the one picker whose rows a thumb now finds first on the
   card. Every other `.prow` in the app is byte-for-byte what it was. */
.wfset + .pick .prow{min-height:44px}
.row.wfamd{background:var(--lateBg);border-left:3px solid var(--late);padding-left:11px}
.row.wfamd > .k > b{color:var(--late)}
.stat.wfamdc{background:var(--late);color:var(--paper)}

/* ==============================================================================================
   THE PRODUCTION BOARD, FOURTH PASS - THE PANEL IS A COLUMN. 2026-09-27.

   `design-target/PRODUCTION-BOARD-V4.md` is the brief, recorded verbatim, and
   `design-target/production-board-v4-mockup.png` (1448 x 1086) is the drawing. It supersedes the
   third pass, which shipped as `83aa4d4` and whose block is directly above this one.

   ==============================================================================================
   WHAT THE THIRD PASS MEASURED, AND WHY HIS ANSWER TO IT IS BETTER THAN ANY OF THE ALTERNATIVES.

   Three columns beside an open panel at the reference viewport left 191px of card against a 204px
   clipping floor, so the board dropped to two columns - and the cards therefore CHANGED SIZE when
   the panel opened. His §1 of this pass says the closed board's card is the correct card and must
   not move; his §2 says how: **the panel stops being BESIDE the grid and becomes ONE OF ITS
   COLUMNS.**

       «Keep two columns of cards on the left. Reserve the entire rightmost column for the details
        panel. The panel must have exactly the same outer width as one original card ... calculate
        the card width from the full available three-column content area, then retain that width in
        both states. If available width is W and the existing gap is G, card width is (W - 2G) / 3.
        The open state uses a two-card-wide grid plus one equally wide details column. Do not
        recalculate three smaller card columns alongside a fourth panel.»

   NOTHING BELOW SHRINKS ANYTHING. There are exactly two mechanisms and they are two lines each:
   the card's track minimum is asked of the CONTAINER rather than of the board (so it is the same
   number in both states), and `.pbgrid` becomes three equal columns of which the panel takes the
   third (so the panel IS a card width, by construction rather than by a number kept in step).

   ==============================================================================================
   THE TWO FINDINGS OF THE PASSES ABOVE THAT ARE **NOT** REOPENED HERE:

     THE PHONE DRAWER STILL CLEARS THE BOTTOM BAR - `.pbgrid.pbopen{min-height:100dvh}` under
       `@container pboard (max-width:899px)` and `.dpin` subtracting the same 88px + safe area
       `body` reserves for `.nav` under `@media(max-width:767px)`. Both rules are in the second
       pass's block, neither is touched, and §5's «responsive panel treatment» is read as the
       licence to differ BELOW the threshold - which is where the drawer already lives - and not
       as permission to undo the fix that made the drawer's foot reachable at 390.
     `views/board.js` STILL REFUSES TO COMPRESS A ROW below `--jobh-min` or below the height the
       cards naturally want. Nothing here changes what it measures; the open board is two columns
       of the same card it was, so the refusal fires exactly where it fired yesterday.
   ============================================================================================ */

/* ----------------------------------------------------------------------------------------------
   1. §2's ARITHMETIC: THE CARD IS (W - 2G) / 3, COMPUTED ONCE, AND W IS THE CONTAINER.

   THE THIRD PASS ASKED THE WRONG BOX, AND ONE UNIT IS THE WHOLE CORRECTION. Its track minimum was
   `max(--jobw-min, (100% - 2 * gap) / 3)`, and `100%` inside `.jobs` is `.pbmain` - the board that
   is LEFT once the panel has taken its track. So the third-of-a-row term meant "a third of what is
   left", which is a different number in each state and is precisely the recalculation his §2 rules
   out by name: «Do not recalculate three smaller card columns alongside a fourth panel.»

   `100cqi` IS THE THREE-COLUMN CONTENT AREA AND IT IS THE SAME NUMBER IN BOTH STATES. `.pboard`
   declares `container:pboard / inline-size` and is the nearest container ancestor of `.jobs`
   (`.job` declares one too, but a card is a CHILD of this grid and not an ancestor of it), so
   `100cqi` here is the board's whole width after the rail - W, exactly the quantity his sentence
   names. Nothing measures it and nothing has to be kept in step with it: the closed grid draws
   three of these and the open grid draws two, out of one expression.

   `- 0.5px` AND IT IS NOT A FUDGE - it is what stops a rounding error costing a column. A track
   minimum of EXACTLY a third means `auto-fill` asks whether `3t + 2G <= W` with `t` a value the
   browser has just divided by three; a sub-pixel rounding UP in that division answers no, and the
   closed board silently draws two columns of 450px. Half a pixel under is far enough inside the
   error and nowhere near a fourth column: four tracks need `4t + 3G`, which exceeds `3t + 2G` by
   a whole track plus a gap at every width there has ever been.

   `--jobw-min` STILL DOES ITS JOB AND IS NOW STATE-INDEPENDENT. It is the 220px floor the third
   pass measured (the station abbreviations start being cut at a 204px card); below `860` of
   container the panel is an overlay drawer and the board keeps whatever count that floor allows,
   which is unchanged. Above it, W is at least 860 and a third of that is 278px, so the floor never
   binds and the card is his arithmetic exactly.
   ------------------------------------------------------------------------------------------- */
.pboard .jobs:not(.solo){
  grid-template-columns:repeat(auto-fill,minmax(
    min(100%,max(var(--jobw-min),calc((100cqi - 2 * var(--jobs-gap)) / 3 - .5px))),1fr))}

/* ----------------------------------------------------------------------------------------------
   2. §2's SHAPE: THREE EQUAL COLUMNS, TWO FOR THE CARDS AND ONE FOR THE PANEL.

   `repeat(3,minmax(0,1fr))` AND NOT A `calc()` PANEL TRACK, deliberately. Both would draw the same
   pixels; only one of them cannot drift. A length computed from the same `(100% - 2G)/3` as the
   rule above is two statements of one number that a later edit can separate, and this stylesheet
   has already shipped one panel track that was neither of the numbers its rule named. Three equal
   columns, the board spanning two of them, IS his §2 as a shape - «two columns of cards on the
   left ... the entire rightmost column for the details panel ... exactly the same outer width as
   one original card» - and the panel's width is a consequence of the grid rather than a claim in a
   comment.

   THE GAP IS `--jobs-gap`, WHICH IS THE CARDS' OWN GAP - §2: «Keep the same gap between the panel
   and cards as between card columns.» It was 14 here and 12 there, and with the panel outside the
   grid that was invisible; with the panel IN the grid, a 14px gutter beside a 12px one is the one
   thing on this board a straight edge would catch.

   `row-gap:0` BECAUSE THE SECOND ROW IS THE ATTENTION LINE'S DOING AND IT CARRIES ITS OWN MARGIN.
   `.pbatt` is new (see `boardWithPanel` in `views/pages.js`): the line that used to be the first
   child of `.pbmain` is a row of this grid now, so that the panel's top edge lands on the first
   CARD rather than on that line - §2's «Align its top edge with the first row of cards». A row gap
   on top of the margin `body.board .attline` already sets would have cost the board 12px of height
   it did not spend yesterday, on the one page in this app whose height is fought over.

   IT IS SCOPED WITH `>` SO THE CLOSED BOARD IS BYTE-FOR-BYTE WHAT IT WAS. `.pbgrid` without
   `.pbopen` is one column and every rule here is inside `.pbopen`; `.pbmain`'s span would
   otherwise have created an implicit second track on a board with nothing in it.
   ------------------------------------------------------------------------------------------- */
.pbgrid{row-gap:0}
.pbatt{grid-column:1 / -1;min-width:0}
@container pboard (min-width:860px){
  .pbgrid.pbopen{column-gap:var(--jobs-gap);row-gap:0;
    grid-template-columns:repeat(3,minmax(0,1fr))}
  .pbgrid.pbopen > .pbmain{grid-column:1 / 3}
  /* THE PANEL IS THE THIRD TRACK AND IS `static` AGAIN - the drawer rules below 860 set
     `position:absolute`, and a column that was still absolutely positioned would be an overlay
     the width of a card. `align-self:stretch` against the grid's own `align-items:start` is what
     makes the aside as tall as the row it is in, which is what the sticky scroller inside it
     needs; see `.dpin` at the foot of this block. */
  .pbgrid.pbopen > .dpanel{grid-column:3;position:static;width:auto;align-self:stretch}
}

/* ==============================================================================================
   3. §2's «KEEP THE PANEL VISIBLE WHILE SCROLLING» AGAINST «ALIGN ITS TOP EDGE WITH THE FIRST
   ROW», AND HOW THE TWO ARE BOTH TRUE AT ONCE.

   THEY PULL IN OPPOSITE DIRECTIONS AND HIS §3 IS WHAT RECONCILES THEM: «Keep the panel visible
   while scrolling through cards, with independent vertical scrolling when its contents exceed the
   available viewport height.» A column that starts at the first card and is as tall as its
   contents scrolls away with the board - that is not visible. A column pinned to the viewport does
   not start at the first card. The shape that is both is a STRETCHED ASIDE with a STICKY, BOUNDED
   SCROLLER inside it, which is the mechanism the second pass already built and which this pass
   only has to keep true now that the aside is a grid column:

     THE ASIDE `.dpanel` starts at the first card row (it is placed in the grid's second row, with
       `.pbatt` above it) and stretches to the bottom of the board. That is the TOP EDGE half.
     THE SCROLLER `.dpin` inside it is `position:sticky` at `--stick + 10px`, which parks it under
       the masthead and never behind it, and it is BOUNDED by `max-height:calc(100dvh - --stick -
       20px)` with `overflow-y:auto`. That is the VISIBLE half AND §3's independent scrolling, and
       the two are one mechanism rather than two: a sticky box that was merely tall would run off
       the bottom of the viewport and its own foot would be unreachable, which is exactly the fault
       the phone drawer was measured for at 390 on 2026-09-26.

   NOTHING BELOW CHANGES `.dpin`. It is written out here because the fourth brief asks the question
   in two sentences that look like a contradiction, and the answer is a property of rules that are
   already in this file rather than a new one.

   `overscroll-behavior:contain` IS THE THIRD PART AND IS ALSO ALREADY THERE: without it, reaching
   the end of the panel's own scroll hands the gesture to the board behind it, and a reader who
   scrolled the panel to its foot would find the board had moved under them.
   ============================================================================================ */

/* ----------------------------------------------------------------------------------------------
   4. §4: THE SIX STAGE CHIPS.

   «Compact rounded rectangular buttons. Consistent thin-line icons. Label followed by a small
   count badge. Active tab: deep navy background, white icon and text, subtly lighter count badge.
   Inactive tabs: white background, fine light-gray border, dark text, pale count badge. Consistent
   heights, padding, spacing, and vertically centered contents ... restrained hover, pressed,
   keyboard-focus, and selected states with smooth transitions around 150-200 ms. Make the whole tab
   clickable. Avoid layout shifts when selection changes.»

   EVERY SELECTOR HERE IS UNDER `.tabsbar`, AND THAT IS THE SCOPE RATHER THAN A PREFIX. `tabChips`
   in `views/layout.js` is the ONLY thing in this application that emits `class="wrap tabsbar"`;
   `pageTabs` - the five Sales pages' row, the same `.tab` pill - emits `.tabs > .tabsrow` with no
   `.tabsbar` between them. So the restyle reaches the six stage chips his mock-up draws and
   reaches nothing else, and /quotations, /rfqs, /clients, /won and /creative-request keep the row
   they were designed with. The unscoped `.tab` rules five thousand lines up are untouched.

   ==============================================================================================
   44px AND NOT THE DRAWING'S 36, AND IT IS THE SAME TRADE THE SECOND PASS WROTE DOWN.

   The mock-up's chip is about 36px tall on its 1448px canvas. This app's floor for anything a
   finger presses is 44 and the second pass recorded that it is not traded - «the mock-up's search
   box scales to 27px tall and its `View details` to 21px, and neither is a control this app is
   willing to draw at that size on a touch screen». The chip row has been 34px since September,
   which is UNDER that floor, and §4 is the first brief to ask for this control to be redrawn: a
   restyle that left a six-target strip at 34px would be this pass choosing to keep a known fault
   because nobody had complained about it. It costs the board ten pixels of height above the first
   card, which is written down here rather than discovered in a screenshot.

   NO LAYOUT SHIFT WHEN THE SELECTION MOVES, WHICH IS ITS OWN SENTENCE IN §4 and is a constraint on
   HOW the active state is drawn rather than on what it looks like: the border is 1px in both
   states (the active one is the same width in the navy), the weight is 700 in both, and the count
   badge keeps its padding and its `min-width`. Only colours change, so a chip that becomes current
   moves nothing beside it.
   ------------------------------------------------------------------------------------------- */
/* THE ROW IS AS NARROW AS SIX ICONS AND SIX WORDS CAN HONESTLY BE MADE, and the numbers are in
   the report: the strip is 809px wide against his own drawing's 968px at the same canvas, so
   wherever his picture's chip row fits, this one fits with 159px to spare. Below about 820px of
   content - a 1024 window with the rail EXPANDED, and an iPad in portrait - it scrolls, which is
   what `.tabsrow` above has done since the owner's own ruling of 2026-09-17 («بدي ياهن على صف
   واحد») and what `finder.js` scrolls the current chip into view for. Nothing is hidden with no
   way to reach it, and the alternative - wrapping to two lines - is the shape he ruled out. */
.tabsbar .tabsrow{gap:7px}
.tabsbar .tab{gap:6px;min-height:44px;padding:0 12px;border-radius:12px;
  font-size:11.5px;font-weight:700;color:var(--ink);background:var(--card);
  border:1px solid var(--rule);box-shadow:var(--e-1)}
/* THE GLYPH IS `--ink3` AGAINST THE LABEL'S `--ink`, so the mark supports the word rather than
   competing with it - `.jcode`'s own pairing on the card. `flex:0 0 auto` because a chip that
   scrolls into a narrow row must not squeeze its own icon. */
.tabsbar .tab svg{flex:0 0 auto;color:var(--ink3)}
.tabsbar .tab .tl{white-space:nowrap}
/* «A SMALL COUNT BADGE», WHICH IT WAS NOT - `.tab .c` was a bare tinted string. It is a pill now,
   with a `min-width` so that 4 and 524 are the same shape of thing and a row whose counts change
   overnight does not re-flow. `tabular-nums` is inherited from `.tab .c` above. */
.tabsbar .tab .c{min-width:20px;padding:2px 5px;border-radius:999px;text-align:center;
  font-size:11px;font-weight:750;background:var(--paper2);color:var(--ink2);opacity:1}
/* 150-200ms, AND ONLY ON THE FOUR PROPERTIES THAT ACTUALLY CHANGE. `transition:all` would animate
   a width or a border the moment another rule moves one, which is the layout shift §4 asks to
   avoid, drawn slowly. */
.tabsbar .tab{transition:background-color .17s ease,border-color .17s ease,color .17s ease,
  box-shadow .17s ease}
.tabsbar .tab:hover{background:var(--paper);border-color:var(--priRule);color:var(--ink)}
.tabsbar .tab:hover svg{color:var(--ink2)}
/* PRESSED IS A DARKER GROUND AND NOT A TRANSFORM. A chip that moved 1px under a finger would be a
   layout shift on every tap, and on a touch screen the finger is over the thing that moved. */
.tabsbar .tab:active{background:var(--paper2);border-color:var(--priRule)}
.tabsbar .tab:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
.tabsbar .tab[aria-current="page"]{background:var(--pri);border-color:var(--pri);
  color:var(--priOn);box-shadow:var(--e-2)}
.tabsbar .tab[aria-current="page"] svg{color:var(--priOn)}
/* «A SUBTLY LIGHTER COUNT BADGE» - white at 18%, which is the navy lifted rather than a second
   token. It is drawn on `--pri` in all three theme states and on nothing else, so one value is
   correct in every one of them; `--priOn` on top is the same white the label wears. */
.tabsbar .tab[aria-current="page"] .c{background:rgba(255,255,255,.18);color:var(--priOn);
  opacity:1}
.tabsbar .tab[aria-current="page"]:hover{background:var(--pri2);border-color:var(--pri2)}
@media(prefers-reduced-motion:reduce){ .tabsbar .tab{transition:none} }

/* ----------------------------------------------------------------------------------------------
   5. §3's PANEL HEADER - «Header: "Project details / #440" with a compact close button on the
   right.»

   THE WORDS ARE `views/card.js`'s and the only thing decided here is that the two halves read as
   one line: the name of the surface in the body ink, the separator and the order number in the
   quieter one. It is the drawing's own emphasis, and it is why the number keeps its `#`.
   ------------------------------------------------------------------------------------------- */
.dphead .dpno{color:var(--ink3);font-weight:650}
.dphead .dpno > b{color:var(--ink);font-weight:750;font-size:13.5px;letter-spacing:-.01em}

/* ----------------------------------------------------------------------------------------------
   6. §3's «ALIGNED ROWS» IN THE PANEL'S PREPARATION SECTION - corrected 2026-09-27 by holding the
   screenshot beside the mock-up, which is the check his last line asks for.

   «For Overview, match the illustrated Production stations and Preparation sections, with compact
   icons, soft gray backgrounds, ALIGNED ROWS, and readable status text.»

   The drawing puts `Design`, `Technical` and `Material Request` on THREE FULL-WIDTH ROWS, one per
   line, each with its mark at the left and its state at the right-hand end; this app drew them as
   three inline pills wrapping in a 296px column, which is the shape they have had since they were
   on the card. A row of three pills in a card-width panel wraps to two lines on most projects and
   never aligns its states, which is the thing his sentence is about.

   IT IS SCOPED TO `.dpbody`, WHICH IS THE CORRECTNESS PART RATHER THAN TIDINESS. `.pchip` and
   `.pchips` are shared names - the delivery chip, the station picker's rows, the creative card's
   state pills and both quotation forms wear them, and every re-pin block in this suite says they
   are byte-for-byte what they were. `.dpbody` is emitted by `projectPanel` in `views/card.js` and
   by nothing else, and `.jprep` inside it is the one block this sentence is about. The CARD keeps
   its pills exactly as drawn, which is also what the card's own mock-up shows.

   `margin-left:auto` ON THE STATE and not a second track: `.pchip` is already a flex row, so the
   word travels to the right-hand end without this rule touching its layout in any other place it
   is drawn. The state loses the 15% transparency it wears on a pill - «readable status text» -
   and goes up from 10.5px to 11.5px, which is the size the rest of this panel's supporting text
   is drawn at.

   THE COLOURS DO NOT MOVE. A row is tinted by its state out of `PREP_CLS` and `.pchip.wait/act/
   ok/none` exactly as the pill was; the drawing happens to show a project where all three are
   `Not set`, which is `.pchip.none` - the grey row with the DASHED ring his picture draws, and
   this app has drawn that mark for that state since it was written. Nothing here decides what a
   state looks like; it decides what shape the row is.
   ------------------------------------------------------------------------------------------- */
.dpbody .jprep .pchips{flex-direction:column;flex-wrap:nowrap;gap:6px}
.dpbody .jprep .pchip{width:100%;min-height:40px;padding:8px 11px;border-radius:10px}
.dpbody .jprep .pchip em{margin-left:auto;padding-left:10px;font-size:11.5px;opacity:1}

/* ----------------------------------------------------------------------------------------------
   7. §3's ORDER INSIDE THE PANEL'S HEAD, AND IT IS NOT THE CARD'S - corrected 2026-09-27 by
   holding the screenshot beside the mock-up.

   THE THIRD PASS PUT THE STATUS BADGE FIRST IN BOTH PLACES, and it was right about the CARD and
   wrong about the PANEL. Its §3 listed the card's contents as «Small "IN ACTIVE" status badge ...
   Project title ...» and the drawing still draws the card that way. The FOURTH brief's §3 lists
   the panel's: «Project image preview. Project title, existing status badge, reference number,
   and owner.» - the title first - and the mock-up draws it that way too: `Metal Stand Production`
   on its own line, the `IN ACTIVE` pill under it, then the code and the owner.

   IT IS THE SAME TWO DECLARATIONS THE THIRD PASS WROTE, WITH THE TWO NUMBERS SWAPPED. `order`
   rather than moving the markup, for that block's own reason: the DOM is the card's order and one
   surface re-states it, so there is one place to look and the other surface cannot drift.

   AND THE BADGE GETS ITS OWN GAP, because a pill directly under a 19px serif name with `.dpid`'s
   4px between them reads as part of the name's block rather than as a state. Three pixels, on the
   panel only.
   ------------------------------------------------------------------------------------------- */
.dphead .dpname{order:0} .dphead .dptop{order:1;margin-top:3px} .dphead > .dpid > .jmeta{order:2}
