/* ============================================================
   WHITE'S OS — design tokens

   Direction: THE COMMAND CENTRE.

   Lewis is not reading paper. At 06:40 he is deciding where four
   lorries go, and the thing in front of him should behave like an
   instrument panel: a dark ground, data that is lit, and colour
   that means something rather than colour that decorates.

   Three rules govern everything below.

   ONE. The ground is near-black and every surface above it is a
   measured step lighter. Depth comes from that step and from a lit
   top edge, never from a heavy border — a border alone on a dark
   ground reads as a hole cut in the panel rather than a panel
   sitting on it.

   TWO. Blue is what the system does. Electric blue marks the
   interactive and the active, and nothing else is allowed to use
   it. Purple appears only where a second route or crew has to be
   told apart from the first. Amber and red are states.

   THREE. Type is software, not signage. The previous direction
   set every title in condensed uppercase, which is the right voice
   for a lorry door and the wrong one for a screen somebody reads
   for eight hours. Hierarchy now comes from weight and size, and
   uppercase is reserved for the small printed label above a figure.

   Dark is the flagship. Light is the same system in daylight —
   translated rather than inverted, because half the office works
   next to a window.
   ============================================================ */
:root{
  /* --- brand, from whitesscaffolding.co.uk. Unchanged. --- */
  --navy-950:#050418; --navy-900:#07052F; --navy-800:#0B0844;
  --navy-700:#0F0C5B; --navy-600:#171370; --navy-500:#241F8E;
  --navy-300:#6E6ABF; --navy-100:#DCDBF1;
  --sky-700:#2E6F9F; --sky-600:#3A82B8; --sky-500:#4F9ED6;
  --sky-400:#77B6E1; --sky-200:#BEDCF2; --sky-050:#EAF4FC;

  /* The interactive blue. White's navy is the identity; this is
     what that identity does when it becomes a control on a dark
     ground. Every primary action in the system is this colour, and
     nothing that is not a control is allowed to borrow it. */
  --int:#168BFF; --int-hi:#3D9EFF; --int-lo:#0B6ACC;
  --int-glow:rgba(22,139,255,.30);
  --int-soft:rgba(22,139,255,.12);
  --int-line:rgba(22,139,255,.34);

  /* The second route. Purple exists so a driver can tell his run
     from the other lorry's on the same map. It is never used to
     make something look futuristic. */
  --route-2:#8B6DF6; --route-2-soft:rgba(139,109,246,.14);
  --route-2-line:rgba(139,109,246,.36);

  /* The signature accent. Amber is what this trade already wears —
     the vest, the beacon, the tape. It marks money at risk and
     work that has stopped. Never decoration. */
  --gold:#E8B04B; --gold-hi:#F6C976; --gold-dim:#9E7526;

  --brand:var(--navy-700); --accent:var(--sky-500);

  /* --- status. States, never decoration. ---
     Green and amber are read as TEXT — the figure under a lorry,
     the key on an alert panel — as well as painted on dots and
     meters. As text on white they measured 4.47:1 and 4.28:1,
     both just under the 4.5:1 AA line. A shade down each clears it
     on white and on a tinted panel, and neither is far enough to
     change what the colour says. */
  --ok:#0F7A52; --ok-bg:#E4F3EC; --ok-line:#9AD3BB;
  --warn:#9F5F05; --warn-bg:#FBF0DE; --warn-line:#E3BE7C;
  --risk:#C02B0A; --risk-bg:#FBE7E2; --risk-line:#E8A995;
  --info:#2E6F9F; --info-bg:#E7F1F9; --info-line:#A9CEE6;

  /* ------------------------------------------------------------
     SPACING  (20 Oct 2026)

     Every gap, padding and margin is a multiple of 4px. Below 8px a
     multiple of 2 is allowed (2, 4, 6) for the tight joins: an icon
     to its word, a dot to its label. The named steps are the ones
     the layout is built from; the rest of the 4px grid exists so a
     one-off can still land on it. Nothing may use 9px, 11px, 13px,
     18px or 22px: those were the wobble.

       s1 4    inside a control, icon to text
       s2 8    between controls in a row, between a label and its field
       s3 12   between rows inside a panel
       s4 16   tile padding on a phone, between tiles
       s5 24   panel padding, between panels
       s6 32   between sections, page head to content
       s7 48   between page regions
       s8 64   page bottom clearance
       s9 96   the end of a long page
     ------------------------------------------------------------ */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px;
  --s6:32px; --s7:48px; --s8:64px; --s9:96px;
  --sp-panel:24px;   /* inside a glass panel */
  --sp-tile:20px;    /* inside a tile or an attention card */
  --sp-tight:16px;   /* inside a compact card, a phone tile, a row card */
  --gap-row:8px;     /* controls side by side */
  --gap-panels:24px; /* panels and tiles next to each other */
  --gap-section:32px;/* one section to the next */

  /* ------------------------------------------------------------
     RADIUS

     Six values, and the size of the thing picks the value:
       r-xs  6   a tag, a tiny badge
       r-sm 10   a small button, a tab, a chip that is not a pill
       r-md 14   a control: button, input, select, icon button
       r-lg 20   a tile, an attention card, a phone tab bar
       r-xl 24   a panel, a sheet
       pill      a chip, an avatar, a status dot
     --r0..--r5 are the same values under their old names. */
  --r0:0; --r1:6px; --r2:10px; --r3:14px; --r4:20px; --r5:24px; --rp:999px;
  --r-xs:6px; --r-sm:10px; --r-md:14px; --r-lg:20px; --r-xl:24px; --r-pill:999px;

  /* ------------------------------------------------------------
     CONTROL HEIGHTS

     Three heights, and a row of mixed controls shares one of them:
       h-sm 36   small button, chip, segmented control
       h-md 42   button, input, select, search, icon button
       h-lg 52   the primary action on a sheet or a phone
     On a touch screen the middle step is 44px, the minimum a thumb
     can hit, and the small step is 40px. */
  --h-sm:36px; --h-md:42px; --h-lg:52px;

  /* ------------------------------------------------------------
     THE TYPE SCALE  (18 Oct 2026)

     One ratio decides every size: the golden ratio, in quarter
     steps. Every value on the lattice below is

         15px x phi^(n/4)      phi = 1.618, n = step

     rounded to the pixel. Four steps up is exactly phi, eight steps
     is phi squared: a section title (step 2) and the body (step 0)
     differ by root-phi, a page title (step 8) is phi x phi x body.
     The same quarter step happens to land within a pixel of every
     text style Apple ships (12, 13, 15, 17, 20, 22, 28, 34), which
     is why it reads as right rather than as maths.

     Before this there were 15 rendered sizes and 13 weights, and no
     two of them agreed on what a heading was. Now there are 18
     lattice points, 12 roles that pick from them, and nothing in
     the app may use a size that is not on the lattice.

     RULES
       - 12px is the floor. Nothing a person reads is smaller.
       - Inputs are body size on a desktop and lead size (17px) on a
         touch screen, because Safari zooms the page when a focused
         field is under 16px. 16 itself is not on the lattice.
       - Display roles (h1, hero, giant) step DOWN the lattice on
         smaller screens: one step on a tablet, two on a phone. The
         size changes; the system does not.
       - Leading is the same quarter step: solid for figures,
         phi^(1/4) for display, phi^(2/4) for headings, phi^(3/4)
         for interface text, phi for prose.
       - Tracking tightens 0.4% of the em per step up the lattice,
         to a cap of 4%. Uppercase labels are tracked 10%. Figures
         in Oswald are not tracked: that face is already condensed.
       - Four weights: 400, 500, 600, 700. Nothing in between and
         nothing above.
     ------------------------------------------------------------ */
  /* the lattice: 15px x phi^(n/4), rounded to the pixel, in rem so it
     follows the browser's text size and pinch-zoom (root 16px) */
  --step--2:.75rem;    /* 12   (11.79) */
  --step--1:.8125rem;  /* 13   (13.30) */
  --step-0:.9375rem;   /* 15   the base */
  --step-1:1.0625rem;  /* 17   (16.92) */
  --step-2:1.1875rem;  /* 19   (19.08)  root-phi x body */
  --step-3:1.375rem;   /* 22   (21.52) */
  --step-4:1.5rem;     /* 24   (24.27)  phi x body */
  --step-5:1.6875rem;  /* 27   (27.37) */
  --step-6:1.9375rem;  /* 31   (30.87) */
  --step-7:2.1875rem;  /* 35   (34.82) */
  --step-8:2.4375rem;  /* 39   (39.27)  phi^2 x body */
  --step-9:2.75rem;    /* 44   (44.29) */
  --step-10:3.125rem;  /* 50   (49.95) */
  --step-11:3.5rem;    /* 56   (56.34) */
  --step-12:4rem;      /* 64   (63.54)  phi^3 x body */
  --step-13:4.5rem;    /* 72   (71.66) */
  --step-14:5.0625rem; /* 81   (80.83) */
  --step-15:5.6875rem; /* 91   (91.16) */

  /* the roles: every size in the app is one of these */
  --t-micro:var(--step--2);   /* 12  a pip, a count on a tab, a tag, initials, the label above a figure */
  --t-tiny:var(--t-micro);    /*     alias kept for older screens */
  --t-small:var(--t-micro);   /*     alias kept for older screens */
  --t-meta:var(--step--1);    /* 13  table cells, secondary copy, pills, small buttons */
  --t-body:var(--step-0);     /* 15  what you read. The default. Buttons. */
  --t-lead:var(--step-1);     /* 17  a card's title, a row that matters more than its neighbours */
  --t-title:var(--step-2);    /* 19  a section */
  --t-h2:var(--step-4);       /* 24  the heading of a sheet or a card hero */
  --t-page:var(--t-h2);       /*     alias kept for older screens (it was always the sheet heading) */
  --t-h1:var(--step-8);       /* 39  the name of the screen */
  --t-figure-sm:var(--step-4);/* 24  a figure inside a card */
  --t-figure:var(--step-6);   /* 31  a number that is the point of the panel */
  --t-hero:var(--step-9);     /* 44  the day board's figures */
  --t-hero-lg:var(--step-11); /* 56  one score, one gauge */
  --t-giant:var(--step-15);   /* 91  the one figure the whole screen is about */
  --t-input:var(--step-0);    /* 15  on a desktop; 17 on a touch screen, see below */

  /* leading: Apple's, not a formula (26 October). iOS sets interface
     text at 13/18, 15/20 and 17/22, about 1.35, and Apple's own web body
     copy at 1.47. The old 1.43 and 1.62 set every block of text a tenth
     looser than Apple would, which is most of why a card read as airy
     rather than set. */
  --lh-solid:1;        /* figures */
  --lh-display:1.13;   /* h1, hero */
  --lh-heading:1.27;   /* h2, h3, card titles (iOS Title 2 is 22/28) */
  --lh-ui:1.35;        /* interface text: cards, tables, buttons (iOS 15/20) */
  --lh-prose:1.5;      /* paragraphs somebody reads (apple.com body is 1.47) */

  /* tracking: Apple's curve (26 October). SF Pro Display, which a Mac and
     an iPhone use from 20px up, is already drawn tight, so Apple tightens
     its large type only a little: about 1% of the em at a page title, not
     the 3.2% this used to apply, which pinched the letters of every title
     together. Body text is drawn in a hair, as Apple's own pages do. */
  --ls-caps:.1em;      /* uppercase labels */
  --ls-micro:0;        /* 12px */
  --ls-0:0;            /* no tracking, as a reset */
  --ls-body:-.008em;   /* 13 to 17px body text, set on body */
  --ls-1:-.01em;   --ls-2:-.006em;  --ls-3:-.005em;  --ls-4:-.005em;  --ls-5:-.006em;
  --ls-6:-.008em;  --ls-7:-.009em;  --ls-8:-.01em;   --ls-9:-.012em;  --ls-10:-.014em;
  --ls-fig:0;          /* Oswald figures, any size */

  /* Weight does the work that a fourth and fifth size used to.
     Two steps of weight against one step of size reads as more
     hierarchy than two steps of size, and stays legible small. */
  --w-body:400; --w-med:500; --w-semi:600; --w-bold:700;

  /* --- type --- */
  /* The body face: clean, modern, and -apple-system first so it is
     SF Pro on the iPhone in somebody's pocket, which is the device
     most of this system is actually read on. */
  --font:-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","DM Sans","Segoe UI",Roboto,
    "Helvetica Neue",Helvetica,Arial,sans-serif;
  /* V1 (17 Oct 2026): titles are software, figures are the
     company's voice. --font-head used to be Oswald, which put every
     page title in condensed capitals and contradicted rule THREE
     above. Titles now use the system face, set tight. Oswald is
     kept for one job, the number that is the point of a panel:
     money, counts, times, the keypad. Use --font-fig for that and
     nothing else. */
  --font-fig:"Oswald","DM Sans",-apple-system,sans-serif;
  --font-head:var(--font);
  --font-stencil:var(--font-fig);

  /* --- motion: 150–300ms. Fast enough to feel like a response,
         slow enough to be read as movement. --- */
  --dur:.15s; --dur-2:.25s; --dur-3:.35s;
  --ease:cubic-bezier(.3,.7,.4,1);
  --ease-out:cubic-bezier(.16,1,.3,1);
  /* V1: what a control animates when it changes state. Never
     `transition:all`, which also animates layout and size. */
  --tr-ui:background-color var(--dur) var(--ease),border-color var(--dur) var(--ease),
    color var(--dur) var(--ease),box-shadow var(--dur) var(--ease),
    transform var(--dur) var(--ease),opacity var(--dur) var(--ease);

  /* ------------------------------------------------------------
     SAFE AREAS

     env() cannot be overridden, which makes an inset impossible to
     test — every check of "does the status bar cover this" had to be
     done by eye on a real handset. Reading them into variables once
     fixes that: the app uses the variables everywhere, and the test
     harness can set them to a Dynamic Island iPhone's insets and
     measure what actually happens.

     The fallbacks matter. Safari on an older iOS returns nothing for
     env() rather than 0, and `calc(66px + )` with an empty operand
     invalidates the whole declaration — which is how a bottom bar
     ends up with no clearance at all.
     ------------------------------------------------------------ */
  --sat:env(safe-area-inset-top,0px);
  --sab:env(safe-area-inset-bottom,0px);
  --sal:env(safe-area-inset-left,0px);
  --sar:env(safe-area-inset-right,0px);

  --sidebar:250px; --topbar:60px;
  --bar:3px;                      /* the left rule that carries status */
  --measure:78ch;
}

/* ============================================================
   DARK — the flagship
   ============================================================ */
html[data-theme="dark"]{
  /* The ladder. Each step is small on purpose: five surfaces two
     shades apart read as one machined object with depth, where
     five surfaces ten shades apart read as five unrelated boxes
     stacked on each other. */
  --paper:#05070D;                /* the ground */
  --paper-2:#070A12;              /* the ground, one step up */
  --surface:#0D111C;              /* a panel */
  --surface-2:#121827;            /* a raised panel */
  --surface-3:#171D2A;            /* a track, an inset field, a pressed control */
  --surface-4:#1D2434;            /* a hovered control on a raised panel */
  --rule:rgba(255,255,255,.08);
  --rule-strong:rgba(255,255,255,.15);
  /* --ink-3 is the muted grey, and it carries nearly all the
     explanatory copy in this application — the line under every
     figure, the note under every card. At #5E6879 it measured
     3.35:1 against a panel and 3.15:1 against a raised one, where
     WCAG AA asks 4.5:1 for text this size. It was readable to good
     eyes on a good screen; on a phone in a yard in the sun it was
     not there at all. Lifted to clear 4.5:1 on every surface in
     the ladder, including the inset one. */
  --ink:#F7F9FC; --ink-2:#8D97A8; --ink-3:#828C9E;

  /* The rail is darker than the workspace, not lighter. The eye
     should fall into the data, not onto the navigation. */
  --chrome:#05070D; --chrome-text:#F7F9FC; --chrome-2:rgba(247,249,252,.60);
  --chrome-rule:rgba(255,255,255,.07);
  --chrome-hover:rgba(255,255,255,.05); --chrome-active:rgba(255,255,255,.09);

  /* status, restated for a dark ground */
  --ok:#31D0A0; --ok-bg:rgba(49,208,160,.10); --ok-line:rgba(49,208,160,.30);
  --warn:#F0B44E; --warn-bg:rgba(240,180,78,.10); --warn-line:rgba(240,180,78,.32);
  --risk:#FF6B57; --risk-bg:rgba(255,107,87,.10); --risk-line:rgba(255,107,87,.32);
  --info:var(--int); --info-bg:var(--int-soft); --info-line:var(--int-line);

  /* elevation. Dark surfaces need a lit top edge as much as a
     shadow — that hairline is what makes a panel sit above the
     ground rather than being a hole cut in it. */
  --lift:inset 0 1px 0 rgba(255,255,255,.05);
  --lift-2:inset 0 1px 0 rgba(255,255,255,.09);
  --sh1:0 1px 2px rgba(0,0,0,.5);
  --sh2:0 10px 30px rgba(0,0,0,.45);
  --sh3:0 30px 70px rgba(0,0,0,.68);
  --scrim:rgba(3,5,11,.72);
  /* Illumination is spent on one thing at a time: the control the
     user is about to press, or the panel carrying the live picture. */
  --glow:0 0 0 1px var(--int-line),0 8px 30px var(--int-glow);

  /* a panel's own gradient — barely there, enough to give a card
     a top and a bottom */
  --pane:linear-gradient(180deg,rgba(255,255,255,.026),rgba(255,255,255,0) 46%);
  /* the tonal gradient a primary card may carry: navy into graphite */
  --pane-hero:linear-gradient(155deg,#121A2E 0%,#0D111C 52%,#0B0F19 100%);
  --glass:rgba(7,10,18,.78);
  color-scheme:dark;
}

/* ============================================================
   LIGHT — the same system in daylight.
   Warm off-white and steel, not an inversion.
   ============================================================ */
html[data-theme="light"]{
  --paper:#EDEFF5; --paper-2:#F4F5F9;
  --surface:#FFFFFF; --surface-2:#F5F6FB; --surface-3:#E6E9F2;
  --rule:#DFE2EC; --rule-strong:#C2C8DA;
  /* Same reasoning in daylight: #7A8098 measured 3.91:1 on white
     and less on a tinted panel. */
  --ink:#0B0A20; --ink-2:#4A5070; --ink-3:#5F6579;

  --chrome:var(--navy-700); --chrome-text:#FFFFFF; --chrome-2:rgba(255,255,255,.68);
  --chrome-rule:rgba(255,255,255,.13);
  --chrome-hover:rgba(255,255,255,.08); --chrome-active:rgba(255,255,255,.16);

  --gold:#B07A12; --gold-hi:#8C5F09; --gold-dim:#E0BE7A;
  --int:#0C6FDB; --int-hi:#0A5FBC; --int-lo:#084C97;
  --int-glow:rgba(12,111,219,.18);
  --int-soft:rgba(12,111,219,.08); --int-line:rgba(12,111,219,.28);
  --route-2:#6D4FD8; --route-2-soft:rgba(109,79,216,.10);
  --route-2-line:rgba(109,79,216,.30);
  --surface-4:#DDE1EC;
  --pane-hero:linear-gradient(155deg,#FFFFFF,#F3F5FB);
  --glow:0 0 0 1px var(--int-line),0 8px 26px var(--int-glow);

  --lift:none; --lift-2:none;
  --sh1:0 1px 2px rgba(11,8,68,.07);
  --sh2:0 8px 26px rgba(11,8,68,.11);
  --sh3:0 28px 64px rgba(11,8,68,.22);
  --scrim:rgba(7,5,47,.42);
  --pane:none;
  --glass:rgba(255,255,255,.8);
  color-scheme:light;
}

/* aliases kept so every existing screen keeps working */
html[data-theme]{--bg:var(--paper);--text:var(--ink);--text-2:var(--ink-2);--text-3:var(--ink-3);
  --border:var(--rule);--border-strong:var(--rule-strong)}

/* Display roles step down the lattice with the screen: one step on a
   tablet, two on a phone. Text roles do not move; a table cell is 13px
   on every device. Inputs go to 17px wherever the pointer is a finger,
   which is the one place the HIG and Safari's zoom rule agree. */
@media (max-width:1180px){
  :root{--t-h1:var(--step-7);--t-hero:var(--step-8);--t-hero-lg:var(--step-10);--t-giant:var(--step-14)}
}
@media (max-width:600px){
  :root{--t-h1:var(--step-6);--t-hero:var(--step-7);--t-hero-lg:var(--step-9);--t-giant:var(--step-12)}
}
@media (pointer:coarse),(max-width:900px){
  :root{--t-input:var(--step-1);--h-sm:40px;--h-md:44px}
}

*,*::before,*::after{box-sizing:border-box}
html,body{height:100%}
body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--font);
  font-size:var(--t-body);line-height:var(--lh-ui);letter-spacing:var(--ls-body);-webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;overscroll-behavior-y:none}
h1,h2,h3,h4{margin:0;font-weight:var(--w-semi);letter-spacing:var(--ls-0);line-height:var(--lh-heading)}
p{margin:0}
button,input,select,textarea{font:inherit;color:inherit}
button{cursor:pointer;background:none;border:0;padding:0}
a{color:inherit;text-decoration:none}
::selection{background:var(--int);color:#fff}
:focus-visible{outline:2px solid var(--int-hi);outline-offset:2px;border-radius:var(--r2)}
html[data-theme="light"] :focus-visible{outline-color:var(--sky-600)}
.num{font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1}

/* ============================================================
   TYPE SYSTEM — four roles, each with a job.

   The condensed uppercase came back, and it came back on purpose.

   The argument against it is real: uppercase removes ascenders and
   descenders, which are what the eye uses to recognise a word by
   its shape, and that costs reading speed. It is the right call
   for a paragraph and it is why the body face below is a clean
   sans set in mixed case.

   But a title is not a paragraph. It is one or two words, read
   once, and what it does is tell you whose software you are in.
   This is the voice painted on the lorry door and stencilled on
   the scafftag — it is the company's own, and White's OS should
   sound like White's rather than like every other operations tool
   with a system font and tight tracking.

   So the split is: TITLES AND FIGURES speak in the company's
   voice. ANYTHING SOMEBODY HAS TO READ A SENTENCE OF does not.
   ============================================================ */
.dsp{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);
  font-weight:var(--w-semi);line-height:var(--lh-solid)}
.dsp-xl{font-size:var(--t-hero);letter-spacing:var(--ls-caps)}
.dsp-lg{font-size:var(--t-figure)}
.dsp-md{font-size:var(--t-title)}
.dsp-sm{font-size:var(--t-body);letter-spacing:var(--ls-caps)}

/* the printed field label, as on a docket */
.eyebrow{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);
  font-size:var(--t-micro);font-weight:var(--w-med);color:var(--ink-3)}
/* A lead reads at body size and is told apart by colour, not by
   being a size of its own. Two sizes half a pixel apart is not
   hierarchy, it is a wobble. */
.lede{color:var(--ink-2);font-size:var(--t-body);line-height:var(--lh-prose)}
.muted{color:var(--ink-3)}
.tiny{font-size:var(--t-small)}

/* ------------------------------------------------------------
   THE FIGURE

   The operational number is the hero of the whole system, so it
   gets its own optical treatment: tabular so a column of them
   lines up, and set in the condensed face, which is where that
   face is at its best — digits have no ascenders to lose.
   ------------------------------------------------------------ */
.fig{font-family:var(--font-head);font-weight:var(--w-semi);font-variant-numeric:tabular-nums;
  font-feature-settings:"tnum" 1;letter-spacing:var(--ls-fig);line-height:var(--lh-solid)}
.fig-xl{font-size:var(--step-8)}
.fig-lg{font-size:var(--t-figure)}
.fig-sm{font-size:var(--t-lead)}

/* where the body face is wanted explicitly */
.plain{font-family:var(--font);text-transform:none;letter-spacing:var(--ls-2)}
.stencil{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);
  font-weight:var(--w-semi)}

/* ---- scrollbars ---- */
*::-webkit-scrollbar{width:11px;height:11px}
*::-webkit-scrollbar-track{background:transparent}
*::-webkit-scrollbar-thumb{background:var(--rule-strong);border-radius:99px;
  border:4px solid transparent;background-clip:content-box}
*::-webkit-scrollbar-thumb:hover{background:var(--ink-3);background-clip:content-box;border:4px solid transparent}
/* ============================================================
   COMPONENTS — panels, bands, rules and rows.
   Cards are used where grouping is real. Everywhere else the
   structure comes from a rule, a band or a change of ground.
   ============================================================ */
#app{min-height:100vh;min-height:100dvh}
.shell{display:grid;grid-template-columns:var(--sidebar) 1fr;min-height:100vh;min-height:100dvh}
.shell.collapsed{--sidebar:64px}

/* ---------------- SIDEBAR ---------------- */
/* The control rail. A standard runs down its outer edge with a
   coupler at every fixing — the one literal piece of scaffold in
   the whole interface, and it earns its place by being the thing
   that separates the rail from the workspace. */
.side{background:var(--chrome);color:var(--chrome-text);display:flex;flex-direction:column;
  position:sticky;top:0;height:100vh;height:100dvh;overflow:hidden;z-index:40;
  border-right:1px solid var(--chrome-rule)}
.side::after{content:"";position:absolute;top:0;right:0;bottom:0;width:10px;pointer-events:none;
  background:
    linear-gradient(90deg,rgba(255,255,255,0) 0%,rgba(255,255,255,.10) 34%,
      rgba(255,255,255,.19) 50%,rgba(255,255,255,.06) 66%,rgba(255,255,255,0) 100%),
    radial-gradient(circle at 5px 62px,rgba(255,255,255,.3) 0 2.4px,transparent 2.6px),
    radial-gradient(circle at 5px 222px,rgba(255,255,255,.3) 0 2.4px,transparent 2.6px),
    radial-gradient(circle at 5px 382px,rgba(255,255,255,.3) 0 2.4px,transparent 2.6px),
    radial-gradient(circle at 5px 542px,rgba(255,255,255,.3) 0 2.4px,transparent 2.6px),
    radial-gradient(circle at 5px 702px,rgba(255,255,255,.3) 0 2.4px,transparent 2.6px),
    radial-gradient(circle at 5px 862px,rgba(255,255,255,.3) 0 2.4px,transparent 2.6px);
  opacity:.85}
.side-top{padding:var(--s4) var(--s4);display:flex;align-items:center;gap:12px;
  border-bottom:1px solid var(--chrome-rule);min-height:var(--topbar)}
.side-nav{flex:1;overflow-y:auto;padding:var(--s3) var(--s2) var(--s4);scrollbar-width:thin}
.side-nav::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);background-clip:content-box}
.nav-group{margin-top:var(--s4)}
.nav-group:first-child{margin-top:0}
.nav-label{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);font-size:var(--t-micro);
  color:rgba(255,255,255,.36);padding:0 12px var(--s2);font-weight:var(--w-med)}
.nav-item{display:flex;align-items:center;gap:12px;padding:8px 12px;border-radius:var(--r3);
  color:var(--chrome-2);font-size:var(--t-meta);font-weight:var(--w-med);width:100%;text-align:left;
  transition:background var(--dur) var(--ease),color var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease);position:relative;
  border:1px solid transparent}
.nav-item:hover{background:var(--chrome-hover);color:#fff}
/* the selected page is lit, not merely tinted — one item on the
   whole rail carries the interactive navy */
/* The current screen. A flat fill and the gold rule down its
     left edge — the gradient and the halo were decoration on the
     one element that is on screen every second of the day. */
.nav-item.on{color:#fff;font-weight:var(--w-semi);
  background:var(--int);
  border-color:var(--int);
  box-shadow:none}
.nav-item.on::before{content:"";position:absolute;left:-1px;top:9px;bottom:9px;width:2.5px;
  border-radius:var(--rp);background:var(--gold)}
/* on the navy rail in daylight, a translucent white read as
   barely-selected — the fill has to be a real step lighter */
html[data-theme="light"] .nav-item.on{background:var(--navy-500);
  border-color:rgba(255,255,255,.26);box-shadow:0 1px 0 rgba(255,255,255,.12) inset}
.nav-item .ic{width:17px;height:17px;flex:none;opacity:.85}
.nav-item.on .ic{opacity:1}
.nav-badge{margin-left:auto;background:var(--risk);color:#fff;font-size:var(--t-micro);font-weight:var(--w-bold);
  min-width:19px;height:18px;border-radius:var(--r1);display:inline-grid;place-items:center;padding:0 4px;
  vertical-align:middle;line-height:var(--lh-solid);font-variant-numeric:tabular-nums}
.nav-badge.amber{background:var(--warn)} .nav-badge.sky{background:var(--sky-600)}
.tabs .nav-badge{margin-left:6px}
.side-foot{padding:var(--s2) var(--s3) var(--s3);border-top:1px solid var(--chrome-rule)}
.shell.collapsed .nav-label,.shell.collapsed .nav-lbl,.shell.collapsed .wordmark,.shell.collapsed .logo svg.wm,
.shell.collapsed .side-foot .who{display:none}
.shell.collapsed .nav-item{justify-content:center;padding:8px 0;border-left-width:0}

/* the profile is a control, not a caption */
.userchip{display:flex;align-items:center;gap:12px;width:100%;padding:8px 12px;border-radius:var(--r3);
  transition:background var(--dur),border-color var(--dur);
  border:1px solid var(--chrome-rule);background:rgba(255,255,255,.035)}
.userchip:hover{background:var(--chrome-hover);border-color:rgba(255,255,255,.16)}
.avatar{width:30px;height:30px;border-radius:var(--rp);background:var(--sky-600);color:#fff;
  display:grid;place-items:center;font-weight:var(--w-bold);font-size:var(--t-tiny);flex:none;letter-spacing:var(--ls-micro)}
.avatar.lg{width:42px;height:42px;font-size:var(--t-meta)}
.avatar.xl{width:58px;height:58px;font-size:var(--t-lead)}
.avatar.sm{width:24px;height:24px;font-size:var(--t-micro)}

/* ---------------- MAIN ---------------- */
.main{min-width:0;display:flex;flex-direction:column}
.topbar{height:var(--topbar);position:sticky;top:0;z-index:30;background:var(--surface);
  border-bottom:1px solid var(--rule);display:flex;align-items:center;gap:var(--s3);padding:0 var(--s5)}
/* The bar was frosted — blur plus a saturate(140%) boost, so whatever
   scrolled under it tinted the chrome and the whole thing shimmered.
   It is a solid surface with a hairline under it now. Chrome should
   be the quietest thing on the screen, not the busiest, and a bar
   that changes colour as you scroll is the opposite of quiet. */
.page{padding:var(--s5) var(--s5) var(--s8);max-width:1560px;width:100%;margin:0 auto;flex:1}

/* the page header is a ruled masthead, not a floating block */
/* the masthead: context, then title, then the actions. The rule
   under it is a short gold standard rather than a full underline —
   the title is the thing, not the line. */
.page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--s4);
  padding-bottom:var(--s4);margin-bottom:var(--s5);flex-wrap:wrap;position:relative}
.page-head::after{content:"";position:absolute;left:0;bottom:0;width:190px;max-width:52%;height:2px;
  background:linear-gradient(90deg,var(--gold),rgba(232,176,75,0))}
.page-title{font-family:var(--font-head);text-transform:uppercase;font-size:var(--t-page);
  letter-spacing:var(--ls-caps);font-weight:var(--w-semi);line-height:var(--lh-solid)}

/* ---------------- PANELS ---------------- */
/* A panel sits above the ground: a lit top edge, its own faint
   gradient, and a shadow underneath. On a dark surface the lit
   edge is what does the work — a border alone reads as a hole. */
.card{background:var(--surface);background-image:var(--pane);
  border:1px solid var(--rule);border-radius:var(--r3);
  box-shadow:var(--lift),var(--sh1)}
.card.pad{padding:var(--s5)}
.card.tight{padding:var(--s4)}
.card.flat{box-shadow:none;background-image:none}
.card.hov{transition:border-color var(--dur) var(--ease),transform var(--dur) var(--ease),
  box-shadow var(--dur) var(--ease)}
.card.hov:hover{border-color:var(--rule-strong);transform:translateY(-1px);
  box-shadow:var(--lift-2),var(--sh2)}
.card.click{cursor:pointer;text-align:left;width:100%;display:block;
  transition:border-color var(--dur) var(--ease),transform var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease)}
.card.click:hover{border-color:var(--rule-strong);transform:translateY(-1px);
  box-shadow:var(--lift-2),var(--sh2)}
.card.click:active{transform:translateY(0)}
/* a panel led by a standard — the left rule carries meaning */
.card.led{border-left:var(--bar) solid var(--navy-700)}
.card.led.risk{border-left-color:var(--risk)} .card.led.warn{border-left-color:var(--warn)}
.card.led.ok{border-left-color:var(--ok)} .card.led.info{border-left-color:var(--info)}

/* section head: a ruled bar with a printed label */
.sec-head{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  margin-bottom:var(--s3);padding-bottom:var(--s2);border-bottom:1px solid var(--rule)}
.sec-title{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);
  font-size:var(--t-tiny);font-weight:var(--w-semi);color:var(--ink-2)}
.sec-head.bare{border-bottom:0;padding-bottom:0}

.grid{display:grid;gap:var(--s4)}
/* ------------------------------------------------------------
   COLUMNS THAT KNOW HOW NARROW THEY HAVE BECOME

   These were a fixed count, so a two-column grid stayed two
   columns no matter how little room it had. Inside a card on a
   tablet that meant 175px per column, and a field holding
   "Unit 4, Southampton Rd" simply ran out of box — the text was
   there, scrolled off the end of an input nobody would think to
   scroll.

   There was a phone breakpoint catching the worst of it, but a
   breakpoint asks the wrong question: it measures the window when
   the thing that ran out of room was a card. auto-fit measures the
   container, so a grid collapses when ITS columns get too narrow,
   whatever the window is doing. min(100%,…) keeps it from
   overflowing a container narrower than one column.
   ------------------------------------------------------------ */
.g2{grid-template-columns:repeat(auto-fit,minmax(min(100%,260px),1fr))}
.g3{grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr))}
.g4{grid-template-columns:repeat(auto-fit,minmax(min(100%,170px),1fr))}
.row{display:flex;align-items:center;gap:var(--s3)}
.row.wrap{flex-wrap:wrap}
.spread{display:flex;align-items:center;justify-content:space-between;gap:var(--s3)}
.stack{display:flex;flex-direction:column;gap:var(--s4)}
.stack.sm{gap:var(--s2)} .stack.md{gap:var(--s3)}
.grow{flex:1;min-width:0}
.hr{height:1px;background:var(--rule);margin:var(--s4) 0;border:0}
.trunc{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ---------------- BUTTONS — four levels, one dominant ---------------- */
/* Five levels: primary, secondary, ghost, destructive, accent —
   and every one of them answers hover, active, focus, disabled and
   loading. A button that cannot show it is working is a button
   people press twice. */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;height:38px;padding:0 16px;
  border-radius:var(--r2);font-weight:var(--w-semi);font-size:var(--t-meta);border:1px solid transparent;
  transition:background var(--dur) var(--ease),border-color var(--dur),color var(--dur),
    box-shadow var(--dur) var(--ease),transform .09s var(--ease);
  white-space:nowrap;position:relative}
.btn:active{transform:scale(.975)}

/* A flat fill. This was a vertical gradient with a coloured glow
   behind it, which is how a button looked when a button had to
   announce that it was clickable. It does not any more: it is the
   only saturated blue on the screen, and that is the whole signal.
   Light mode had already been flattened; the two now agree. */
.btn-p{background:var(--int);color:#fff;border-color:var(--int);box-shadow:none}
.btn-p:hover{background:var(--int-hi);border-color:var(--int-hi);box-shadow:none}
.btn-p:active{background:var(--int-lo);border-color:var(--int-lo)}
html[data-theme="light"] .btn-p{background:var(--navy-700);border-color:var(--navy-700);box-shadow:none}
html[data-theme="light"] .btn-p:hover{background:var(--navy-600);border-color:var(--navy-600)}

.btn-s{background:var(--surface-2);color:var(--ink);border-color:var(--rule-strong);
  box-shadow:var(--lift)}
.btn-s:hover{background:var(--surface-3);border-color:var(--ink-3)}
.btn-g{background:transparent;color:var(--ink-2)}
.btn-g:hover{background:var(--surface-2);color:var(--ink)}
.btn-d{background:transparent;color:var(--risk);border-color:var(--risk-line)}
.btn-d:hover{background:var(--risk-bg);border-color:var(--risk)}
.btn-w{background:transparent;color:var(--gold);border-color:var(--warn-line)}
.btn-w:hover{background:var(--warn-bg);border-color:var(--gold)}
.btn-a{background:var(--sky-600);color:#fff;border-color:var(--sky-600)}
.btn-a:hover{background:var(--sky-500);border-color:var(--sky-500)}
.btn.sm{height:31px;padding:0 12px;font-size:var(--t-small);border-radius:var(--r2)}
.btn.lg{height:46px;padding:0 24px;font-size:var(--t-body);border-radius:var(--r3)}
.btn.block{width:100%}
.btn[disabled],.btn.is-disabled{opacity:.38;pointer-events:none;box-shadow:none}
/* loading: the label stays put and a spinner takes its place, so
   the button does not resize and jump out from under the cursor */
.btn.is-loading{pointer-events:none;color:transparent!important}
.btn.is-loading::after{content:"";position:absolute;width:14px;height:14px;border-radius:var(--rp);
  border:2px solid rgba(255,255,255,.9);border-right-color:transparent;animation:sp .68s linear infinite}
.btn-s.is-loading::after,.btn-g.is-loading::after{border-color:var(--ink-2);border-right-color:transparent}
.icobtn{width:36px;height:36px;border-radius:var(--r2);display:grid;place-items:center;
  color:var(--ink-2);transition:background var(--dur),color var(--dur),transform .09s var(--ease);
  border:1px solid transparent}
.icobtn:hover{background:var(--surface-2);color:var(--ink);border-color:var(--rule)}
.icobtn:active{transform:scale(.94)}
.icobtn.on{background:var(--surface-3);color:var(--ink);border-color:var(--rule-strong)}
.burger{display:none}
@media (max-width:900px){.burger{display:grid}}
.lnk{color:var(--sky-700);font-weight:var(--w-semi);text-decoration:underline;text-underline-offset:2px;padding:0}
html[data-theme="dark"] .lnk{color:var(--sky-400)}

/* ---------------- STATE BADGES ----------------
   Squared off and set in condensed caps, so a state reads like a
   stamp on a docket rather than a SaaS chip. Reserved for real
   states; ordinary metadata is typography, not colour. */
.pill{display:inline-flex;align-items:center;gap:4px;height:21px;padding:0 8px;border-radius:var(--r1);
  font-size:var(--t-micro);font-weight:var(--w-semi);letter-spacing:var(--ls-caps);text-transform:uppercase;
  font-family:var(--font-head);border:1px solid transparent;white-space:nowrap}
/* Status is never carried by colour alone. Every state chip gets a
   mark as well as a hue, so the four people in this company who
   cannot separate the amber from the green still read the board —
   and so does anybody looking at a printed copy. */
.pill::before{font-size:var(--t-micro);line-height:var(--lh-solid);opacity:.9;font-family:var(--font)}
.pill.ok::before{content:"\2713"}       /* tick — done, valid, safe */
.pill.warn::before{content:"\25B3"}     /* triangle — watch it */
.pill.risk::before{content:"\2715"}     /* cross — stopped, expired, overdue */
.pill.info::before{content:"\25CF"}     /* dot — in progress */
.pill.grey::before{content:"\25CB"}     /* ring — nothing happening yet */
.pill.ok{background:var(--ok-bg);color:var(--ok);border-color:var(--ok-line)}
.pill.warn{background:var(--warn-bg);color:var(--warn);border-color:var(--warn-line)}
.pill.risk{background:var(--risk-bg);color:var(--risk);border-color:var(--risk-line)}
.pill.info{background:var(--info-bg);color:var(--info);border-color:var(--info-line)}
.pill.grey{background:var(--surface-2);color:var(--ink-2);border-color:var(--rule)}
.pill.navy{background:var(--navy-700);color:#fff;border-color:var(--navy-700)}
.dot{width:6px;height:6px;border-radius:var(--rp);background:currentColor;flex:none}
/* metadata: no colour, no pill — a printed value with a hairline box */
.tag{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:var(--rp);
  border:1px solid var(--rule);color:var(--ink-2);font-size:var(--t-small);font-weight:var(--w-semi);background:var(--surface-2)}

/* ---------------- INPUTS ---------------- */
.field{display:flex;flex-direction:column;gap:4px}
.lbl{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);
  font-size:var(--t-micro);font-weight:var(--w-med);color:var(--ink-3)}
/* an input is a recess, not a raised panel — it takes the inset
   surface so a form reads as things you fill in */
.inp,.sel,.ta{width:100%;height:40px;padding:0 12px;border-radius:var(--r2);
  border:1px solid var(--rule-strong);background:var(--surface-2);
  transition:border-color var(--dur),box-shadow var(--dur),background var(--dur);font-size:var(--t-meta)}
.ta{height:auto;padding:12px 12px;min-height:86px;resize:vertical;line-height:var(--lh-ui)}
.inp:hover,.sel:hover,.ta:hover{border-color:var(--ink-3)}
.inp:focus,.sel:focus,.ta:focus{outline:0;border-color:var(--int-hi);background:var(--surface);
  box-shadow:0 0 0 3px var(--int-glow)}
html[data-theme="light"] .inp:focus,html[data-theme="light"] .sel:focus,
html[data-theme="light"] .ta:focus{border-color:var(--sky-600);box-shadow:0 0 0 3px rgba(58,130,184,.18)}
.inp[disabled],.sel[disabled],.ta[disabled]{opacity:.45;pointer-events:none}
.inp::placeholder,.ta::placeholder{color:var(--ink-3)}
.inp[aria-invalid="true"],.ta[aria-invalid="true"]{border-color:var(--risk)}
.sel{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='7' viewBox='0 0 11 7'%3E%3Cpath d='M1 1L5.5 5.5L10 1' stroke='%23787D99' stroke-width='1.7' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 11px center;padding-right:32px}
.seg{display:inline-flex;border:1px solid var(--rule-strong);border-radius:var(--r2);overflow:hidden;
  background:var(--surface-2)}
.seg button{padding:6px 16px;font-size:var(--t-small);font-weight:var(--w-semi);color:var(--ink-2);
  transition:background var(--dur),color var(--dur);border-right:1px solid var(--rule)}
.seg button:last-child{border-right:0}
.seg button:hover{color:var(--ink);background:var(--surface-3)}
.seg button.on{background:var(--int);color:#fff}
html[data-theme="light"] .seg button.on{background:var(--navy-700)}
.chips{display:flex;gap:6px;flex-wrap:wrap}
.chip{padding:6px 12px;border-radius:var(--rp);border:1px solid var(--rule-strong);font-size:var(--t-small);
  font-weight:var(--w-semi);color:var(--ink-2);transition:var(--tr-ui);background:var(--surface-2)}
.chip:hover{border-color:var(--ink-3);color:var(--ink);background:var(--surface-3)}
/* Selected, not glowing. A chip that is filled with the one
     saturated blue on the screen has already said it is on; the
     halo behind it was saying it twice. */
.chip.on{background:var(--int);border-color:var(--int);color:#fff;box-shadow:none}
html[data-theme="light"] .chip.on{background:var(--navy-700);border-color:var(--navy-700);box-shadow:none}
.switch{width:38px;height:21px;border-radius:var(--rp);background:var(--rule-strong);position:relative;
  transition:background var(--dur);flex:none}
.switch::after{content:"";position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:var(--rp);
  background:#fff;transition:transform var(--dur) var(--ease);box-shadow:0 1px 2px rgba(0,0,0,.3)}
.switch.on{background:var(--ok);box-shadow:0 0 10px rgba(56,201,143,.35)}
.switch.on::after{transform:translateX(17px)}

.searchbar{flex:1;max-width:520px;height:38px;display:flex;align-items:center;gap:8px;padding:0 12px;
  border-radius:var(--r3);border:1px solid var(--rule);background:var(--surface-2);
  color:var(--ink-3);font-size:var(--t-meta);transition:border-color var(--dur),background var(--dur),
    box-shadow var(--dur)}
.searchbar:hover{border-color:var(--rule-strong);background:var(--surface-3)}
.searchbar:focus-visible{box-shadow:0 0 0 3px var(--int-glow)}
.searchbar>span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.kbd{margin-left:auto;font-size:var(--t-micro);font-family:var(--font);border:1px solid var(--rule-strong);
  border-radius:var(--r1);padding:0 4px;color:var(--ink-3)}

/* ---------------- TABLES — the operational workhorse ---------------- */
/* The operations console. Rows are taller than a spreadsheet's
   because these are read standing up, and the header stays put
   because these lists are long. */
.tbl{width:100%;border-collapse:separate;border-spacing:0;font-size:var(--t-meta)}
.tbl th{text-align:left;font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);
  font-size:var(--t-micro);font-weight:var(--w-semi);color:var(--ink-3);padding:12px var(--s4);white-space:nowrap;
  background:var(--surface-2);border-bottom:1px solid var(--rule-strong);position:sticky;top:0;z-index:2;
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
.tbl th.sortable{cursor:pointer;transition:color var(--dur)}
.tbl th.sortable:hover{color:var(--ink)}
.tbl td{padding:12px var(--s4);border-bottom:1px solid var(--rule);vertical-align:middle}
.tbl tbody tr:last-child td{border-bottom:0}
.tbl tbody tr{transition:background var(--dur),box-shadow var(--dur)}
.tbl tbody tr.click{cursor:pointer}
.tbl tbody tr.click:hover{background:var(--surface-2)}
.tbl tbody tr.click:hover td:first-child{box-shadow:inset var(--bar) 0 0 var(--int-hi)}
html[data-theme="light"] .tbl tbody tr.click:hover td:first-child{box-shadow:inset var(--bar) 0 0 var(--navy-700)}
.tbl tbody tr.sel{background:var(--int-glow)}
.tbl tbody tr.sel td:first-child{box-shadow:inset var(--bar) 0 0 var(--int-hi)}
.tbl td.r,.tbl th.r{text-align:right}
.tbl td.r{font-variant-numeric:tabular-nums}

/* ---------------- METERS ---------------- */
.meter{height:6px;border-radius:var(--rp);background:var(--surface-3);overflow:hidden}
.meter>i{display:block;height:100%;border-radius:var(--rp);transition:width .5s var(--ease-out);
  background:linear-gradient(90deg,var(--int),var(--int-hi))}
html[data-theme="light"] .meter>i{background:var(--sky-600)}
.meter.thin{height:3px}
.meter>i.ok{background:var(--ok)} .meter>i.warn{background:var(--warn)} .meter>i.risk{background:var(--risk)}
.meter>i.navy{background:var(--navy-700)}
html[data-theme="dark"] .meter>i.navy{background:var(--navy-300)}

/* ---------------- STAT RAIL ----------------
   Figures sit in one ruled cluster divided by hairlines, the way a
   gauge board reads — not four identical floating cards. */
.rail{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;
  border:1px solid var(--rule);border-radius:var(--r3);background:var(--surface);overflow:hidden;
  box-shadow:var(--lift),var(--sh1)}
.rail.wrap{grid-auto-flow:row;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));grid-auto-columns:auto}
.tile{padding:var(--s3) var(--s4) var(--s4);text-align:left;width:100%;background:var(--surface);
  border-right:1px solid var(--rule);transition:background var(--dur);position:relative;display:block}
.rail>.tile:last-child{border-right:0}
.tile:hover{background:var(--surface-2)}
.tile{cursor:inherit}
a.tile,button.tile{cursor:pointer}
.tile .k{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);font-size:var(--t-micro);
  font-weight:var(--w-med);color:var(--ink-3);display:flex;align-items:center;gap:4px}
.tile .v{font-family:var(--font-head);font-size:var(--t-figure);font-weight:var(--w-semi);letter-spacing:var(--ls-fig);
  margin-top:6px;line-height:var(--lh-solid);font-variant-numeric:tabular-nums}
.tile .sub{font-size:var(--t-small);color:var(--ink-2);margin-top:4px}
.tile .arrow{display:none}
/* standalone tiles (outside a rail) keep a hairline box */
.grid>.tile,.stack>.tile{border:1px solid var(--rule);border-radius:var(--r3)}

/* ---------------- ATTENTION ROWS ----------------
   A warning band, borrowed from the tag: a status rule down the
   left, no rounded box, no icon in a circle. */
/* An operational card, not a notification box. The severity is a
   standard down the left edge and a stamped word at the top; the
   card itself stays calm. Filling it red would make a screen of
   four of them unreadable, which is exactly when it matters. */
.att{display:flex;gap:var(--s4);padding:var(--s4) var(--s5);background:var(--surface);
  background-image:var(--pane);
  border:1px solid var(--rule);border-left:var(--bar) solid var(--rule-strong);
  border-radius:var(--r3);align-items:center;
  box-shadow:var(--lift),var(--sh1);
  transition:border-color var(--dur),transform var(--dur) var(--ease),box-shadow var(--dur) var(--ease)}
/* .click only ever meant a card or a table row. An attention block
   that opens something needs to look like it does — it already
   lifts on hover, it just never changed the cursor. */
.att.click{cursor:pointer}
.att.click:focus-visible{outline:2px solid var(--focus,var(--accent));outline-offset:2px}
.att:hover{border-color:var(--rule-strong);transform:translateY(-1px);
  box-shadow:var(--lift-2),var(--sh2)}
.att .btn{flex:none}
.att .sig{width:0;height:0;margin:0;flex:none;display:none}
.att.risk{border-left-color:var(--risk)} .att.warn{border-left-color:var(--warn)}
.att.ok{border-left-color:var(--ok)} .att.info{border-left-color:var(--info)}
.att-k{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);font-size:var(--t-micro);
  font-weight:var(--w-semi);margin-bottom:2px;display:block}
.att.risk .att-k{color:var(--risk)} .att.warn .att-k{color:var(--warn)}
.att.ok .att-k{color:var(--ok)} .att.info .att-k{color:var(--info)}

/* ---------------- EMPTY / LOADING ---------------- */
.empty{padding:var(--s7) var(--s5);text-align:center;color:var(--ink-3);
  border:1px dashed var(--rule-strong);border-radius:var(--r3);background:var(--surface-2)}
/* the diagonal hatching behind this was texture for texture's sake;
   an empty state should read as calm, not as a pattern swatch */
.empty .dsp-md{color:var(--ink-2);margin-bottom:4px}
.empty p{max-width:44ch;margin:0 auto}

/* ---------------- OVERLAYS ---------------- */
.scrim{position:fixed;inset:0;background:var(--scrim);z-index:100;opacity:0;
  backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);transition:opacity var(--dur-2)}
.scrim.in{opacity:1}
.sheet{position:fixed;z-index:101;background:var(--surface);box-shadow:var(--sh3);display:flex;flex-direction:column}
.sheet.modal{top:50%;left:50%;transform:translate(-50%,-46%) scale(.985);width:min(620px,calc(100vw - 32px));
  max-height:min(88vh,880px);border-radius:var(--r5);opacity:0;border:1px solid var(--rule-strong);
  transition:transform var(--dur-2) var(--ease-out),opacity var(--dur-2)}
.sheet.modal.in{transform:translate(-50%,-50%) scale(1)}
.sheet.modal.in{transform:translate(-50%,-50%);opacity:1}
.sheet.modal.wide{width:min(920px,calc(100vw - 32px))}
.sheet.modal.xwide{width:min(1160px,calc(100vw - 32px))}
.sheet.drawer{top:0;right:0;height:100dvh;width:min(540px,100vw);transform:translateX(100%);
  transition:transform .22s var(--ease);border-left:1px solid var(--rule)}
.sheet.drawer.in{transform:none}
.sheet.drawer.wide{width:min(740px,100vw)}
/* the sheet header is a navy band, as on the tag */
/* the header band, with the hazard stripe the yard already uses
   sitting under it as a hairline */
.sheet-head{padding:var(--s5);display:flex;align-items:flex-start;gap:var(--s3);
  background:var(--chrome);color:#fff;flex:none;border-radius:var(--r5) var(--r5) 0 0;
  position:relative}
/* a fine standard under the header band rather than the full
   hazard tape — at 620px wide the tape read as a dashed border */
.sheet-head::after{content:"";position:absolute;left:0;bottom:0;width:120px;height:2px;
  background:linear-gradient(90deg,var(--gold),rgba(232,176,75,0))}
.sheet.drawer .sheet-head{border-radius:0}
.sheet-head .eyebrow{color:rgba(255,255,255,.6)}
.sheet-head .lede{color:rgba(255,255,255,.78)}
.sheet-head .icobtn{color:#fff}
.sheet-head .icobtn:hover{background:rgba(255,255,255,.14);color:#fff}
.sheet-body{padding:var(--s5);overflow-y:auto;flex:1;background:var(--surface)}
.sheet-foot{padding:var(--s3) var(--s5);border-top:1px solid var(--rule);display:flex;
  align-items:center;gap:var(--s3);justify-content:flex-end;flex:none;background:var(--surface-2);
  border-radius:0 0 var(--r5) var(--r5)}
.sheet.drawer .sheet-foot{border-radius:0}

/* step rail — a run of bays, numbered */
.steps{display:flex;align-items:center;gap:0;padding:var(--s3) var(--s5);background:var(--surface-2);
  border-bottom:1px solid var(--rule);flex:none}
.step{display:flex;align-items:center;gap:6px;font-size:var(--t-small);font-weight:var(--w-semi);color:var(--ink-3)}
.step .n{width:21px;height:21px;border-radius:var(--rp);background:var(--surface-3);color:var(--ink-3);
  display:grid;place-items:center;font-size:var(--t-micro);font-weight:var(--w-bold);flex:none;transition:var(--tr-ui);
  font-family:var(--font-head)}
.step.on{color:var(--ink)} .step.on .n{background:var(--int);color:#fff;box-shadow:0 0 0 3px var(--int-glow)}
.step.done{color:var(--ok)} .step.done .n{background:var(--ok);color:#fff}
html[data-theme="light"] .step.on .n{background:var(--navy-700);box-shadow:none}
.step-line{flex:1;height:1px;background:var(--rule-strong);margin:0 8px;min-width:12px}
.step-line.done{background:var(--ok)}

/* ---------------- TOAST ---------------- */
#toasts{position:fixed;bottom:var(--s5);left:50%;transform:translateX(-50%);z-index:200;
  display:flex;flex-direction:column;gap:6px;align-items:center;pointer-events:none}
.toast{background:#0B1024;color:#fff;padding:12px 16px;border-radius:var(--r3);font-size:var(--t-meta);
  font-weight:var(--w-semi);box-shadow:var(--sh3);display:flex;align-items:center;gap:12px;
  border:1px solid rgba(255,255,255,.12);
  animation:tin .22s var(--ease-out);max-width:min(540px,92vw);border-left:var(--bar) solid var(--int-hi)}
.toast.ok{border-left-color:var(--ok)}
.toast.risk{border-left-color:var(--risk)}
.toast.warn{border-left-color:var(--gold)}
@keyframes tin{from{opacity:0;transform:translateY(12px) scale(.97)}}

/* ---------------- FAB ---------------- */
.fab{position:fixed;right:var(--s5);bottom:var(--s5);z-index:50;width:52px;height:52px;border-radius:var(--r4);
  background:linear-gradient(180deg,var(--int-hi),var(--int));color:#fff;display:grid;place-items:center;
  border:1px solid rgba(255,255,255,.16);
  box-shadow:var(--sh2),var(--lift-2);
  transition:transform var(--dur) var(--ease),box-shadow var(--dur)}
.fab:hover{transform:translateY(-2px);box-shadow:var(--sh3),var(--lift-2)}
.fab:active{transform:scale(.95)}
html[data-theme="light"] .fab{background:var(--navy-700);box-shadow:var(--sh2)}

/* ---------------- SAVE CHIP ---------------- */
.savechip{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 8px;border-radius:var(--r1);
  font-size:var(--t-tiny);font-weight:var(--w-semi);letter-spacing:var(--ls-micro);transition:var(--tr-ui);border:1px solid transparent}
.savechip.saved{background:var(--ok-bg);color:var(--ok);border-color:var(--ok-line)}
.savechip.local{background:var(--warn-bg);color:var(--warn);border-color:var(--warn-line)}
.savechip.err{background:var(--risk-bg);color:var(--risk);border-color:var(--risk-line)}
.savechip.busy{background:var(--info-bg);color:var(--info);border-color:var(--info-line)}
/* The short word is for a phone, where the full one is hidden. */
.chipword{display:none}
/* On the phone the chip sits in a button that reads out the whole
   message. The button is invisible and as tall as the header's other
   buttons (44px), so the chip keeps its look and is a fair thumb's
   height to press. */
.chipbtn{display:inline-flex;align-items:center;min-height:44px;padding:0;border:0;
  background:none;color:inherit;font:inherit;cursor:pointer;flex:none}
.spin{width:10px;height:10px;border:1.6px solid currentColor;border-right-color:transparent;
  border-radius:var(--rp);animation:sp .7s linear infinite}
@keyframes sp{to{transform:rotate(360deg)}}

.sk{background:linear-gradient(90deg,var(--surface-2) 25%,var(--surface-3) 50%,var(--surface-2) 75%);
  background-size:200% 100%;animation:skm 1.4s infinite;border-radius:var(--r2)}
@keyframes skm{to{background-position:-200% 0}}
.fade{animation:fadein .16s var(--ease)}
@keyframes fadein{from{opacity:0}}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important}
}

/* ---------- combobox: type anything, suggestions follow ---------- */
.townfield{position:relative}
.combo{position:relative}
.combo .inp{padding-right:40px}
.combo-ic{
  position:absolute; right:12px; top:50%; transform:translateY(-50%);
  color:var(--text-3); pointer-events:none; display:flex;
}
.combo-list{
  position:absolute; left:0; right:0; top:calc(100% + 4px); z-index:40;
  background:var(--surface); border:1px solid var(--border-strong);
  border-radius:var(--r3); box-shadow:0 12px 28px rgba(8,10,24,.16);
  max-height:264px; overflow-y:auto; padding:4px;
}
.combo-list[hidden]{display:none}
.combo-opt{
  display:flex; align-items:center; gap:12px; width:100%; text-align:left;
  padding:8px 12px; border-radius:var(--r2); font-size:var(--t-meta); color:var(--text);
  background:transparent; border:0; cursor:pointer; min-height:40px;
}
.combo-opt:hover,.combo-opt.on{background:var(--surface-3)}
.combo-opt b{font-weight:var(--w-bold); color:var(--brand)}
.combo-opt.add{color:var(--brand); font-weight:var(--w-semi); border-top:1px solid var(--border); margin-top:4px}
.combo-opt.add .ic{flex:none}
.combo-empty{padding:12px 12px}
@media (max-width:720px){
  .combo-opt{min-height:46px; font-size:var(--t-body)}
  .combo-list{max-height:50vh}
}
/* ===================== LOGO ===================== */
.logo{display:flex;align-items:center;gap:12px;flex:none}
.logo svg{flex:none}
.logo svg.wm{width:auto}
.wordmark{display:flex;flex-direction:column;line-height:1;gap:2px}
.wordmark b{font-family:var(--font-head);font-weight:600;font-size:var(--t-title);letter-spacing:.005em;text-transform:uppercase}
.wordmark i{font-family:var(--font-head);font-style:normal;font-weight:500;font-size:8.5px;
  letter-spacing:.235em;text-transform:uppercase;opacity:.72}
.logo.lg svg.mk{width:56px;height:56px}
.logo.lg svg.wm{height:30px;width:auto}
.logo.lg svg.wm-tag{height:43px;margin-top:12px}
.logo.lg .wordmark b{font-size:var(--t-figure)} .logo.lg .wordmark i{font-size:var(--t-small);letter-spacing:.2em}

/* ===================== HEALTH GAUGE ===================== */
.health{display:grid;grid-template-columns:minmax(0,260px) minmax(0,1fr);gap:var(--s6);align-items:center}
.gauge{display:block}
.gauge-fig{display:flex;align-items:baseline;gap:8px}
.gauge-fig b{font-size:var(--t-hero-lg);line-height:var(--lh-solid)}
.gauge-of{font-family:var(--font-head);font-size:var(--t-body);color:var(--ink-3);letter-spacing:var(--ls-fig)}
.gauge-mv{margin-left:auto;font-size:var(--t-tiny);font-weight:var(--w-bold);font-family:var(--font-head);
  letter-spacing:var(--ls-fig);padding:2px 6px;border:1px solid var(--rule);border-radius:var(--r1)}
.gauge-mv.up{color:var(--ok);border-color:var(--ok-line);background:var(--ok-bg)}
.gauge-mv.down{color:var(--risk);border-color:var(--risk-line);background:var(--risk-bg)}
.gauge-scale{position:relative;margin-top:16px;padding-bottom:16px}
.gauge-track{height:8px;background:var(--surface-3);overflow:hidden}
.gauge-track>i{display:block;height:100%;transition:width .5s var(--ease)}
.gauge-mark{position:absolute;top:-4px;width:2px;height:16px;background:var(--ink);
  transform:translateX(-1px);transition:left .5s var(--ease)}
html[data-theme="dark"] .gauge-mark{background:#fff}
.gauge-ticks{position:absolute;left:0;right:0;top:8px;height:14px}
.gauge-ticks span{position:absolute;transform:translateX(-50%);text-align:center}
.gauge-ticks i{display:block;width:1px;height:4px;background:var(--rule-strong);margin:0 auto}
.gauge-ticks s{text-decoration:none;font-size:var(--t-micro);color:var(--ink-3);font-family:var(--font-head);
  letter-spacing:var(--ls-fig);display:block;margin-top:2px}

/* factors read as a ruled schedule, not a stack of bars in boxes */
.factors{display:grid;gap:0}
.factor{display:grid;grid-template-columns:1fr 110px 44px;align-items:center;gap:var(--s4);
  font-size:var(--t-meta);padding:8px 0;border-bottom:1px solid var(--rule)}
.factor:last-child{border-bottom:0}
.factor .fn{color:var(--ink-2);font-weight:var(--w-med)}
.factor .fv{text-align:right;font-weight:var(--w-bold);font-size:var(--t-small);font-variant-numeric:tabular-nums;
  font-family:var(--font-head)}

/* ===================== DOCKET ROWS ===================== */
.kv{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s4);
  padding:6px 0;border-bottom:1px solid var(--rule)}
.kv:last-child{border-bottom:0}
.kv-k{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);font-size:var(--t-micro);
  font-weight:var(--w-med);color:var(--ink-3);flex:none}
.kv-v{font-weight:var(--w-semi);font-size:var(--t-meta);text-align:right;min-width:0}

/* ===================== INSIGHT ===================== */
.insight{padding:var(--s4) 0 var(--s4) var(--s4);border-left:var(--bar) solid var(--sky-600);
  background:var(--surface);border-top:1px solid var(--rule)}
.insight:first-child{border-top:0}
/* Three insights stacked inside one card. The card carries no
   padding of its own and each insight carries all four sides, so
   the run reads as one block with an even inset rather than a card
   whose top and bottom disagree. */
.insight-stack{padding:0;overflow:hidden}
.insight-stack>.insight{padding:var(--s4) var(--s5) var(--s4) var(--s4)}
.insight-stat{font-size:var(--t-title);color:var(--sky-700);flex:none;white-space:nowrap}
html[data-theme="dark"] .insight-stat{color:var(--sky-400)}

/* ===================== SCAFFOLD ELEVATION ===================== */
.elev{color:var(--navy-700);opacity:.9;max-width:100%;height:auto}
html[data-theme="dark"] .elev{color:var(--sky-400)}
.elev-wrap{display:flex;align-items:flex-end;gap:var(--s4);padding:var(--s3) 0}

/* ===================== PLANNING BOARD ===================== */
.board{display:grid;grid-template-columns:repeat(5,minmax(214px,1fr));gap:var(--s3);overflow-x:auto;padding-bottom:var(--s3)}
.bcol{background:var(--surface-2);border:1px solid var(--rule);border-radius:var(--r2);
  display:flex;flex-direction:column;min-height:400px}
.bcol.over{border-color:var(--sky-600);background:var(--info-bg);box-shadow:inset 0 0 0 1px var(--sky-600)}
.bcol-h{padding:var(--s3) var(--s4);border-bottom:1px solid var(--rule-strong);position:sticky;top:0;
  background:var(--surface-2);z-index:2}
.bcol-b{padding:var(--s3);display:flex;flex-direction:column;gap:var(--s3);flex:1}
.jcard{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r2);
  padding:var(--s3);transition:border-color var(--dur);text-align:left;width:100%;
  border-left:var(--bar) solid var(--rule-strong)}
.jcard:hover{border-color:var(--rule-strong)}
.jcard .bar{display:none}
.crew{display:flex;gap:-6px;align-items:center}
.crew .avatar{margin-left:-6px;border:2px solid var(--surface)}
.crew .avatar:first-child{margin-left:0}
.pchip{display:inline-flex;align-items:center;gap:6px;padding:4px 8px 4px 4px;border-radius:var(--r1);
  background:var(--surface-2);font-size:var(--t-small);font-weight:var(--w-semi);cursor:grab;transition:var(--tr-ui);
  border:1px solid var(--rule)}
.pchip:active{cursor:grabbing}
.pchip.drag{opacity:.4}
.pchip.picked{background:var(--navy-700);color:#fff;border-color:var(--navy-700);box-shadow:var(--sh2)}
html[data-theme="dark"] .pchip.picked{background:var(--sky-500);color:#06122A;border-color:var(--sky-500)}
.jcard.placing{border-style:dashed;border-color:var(--sky-500);cursor:copy}
.pickbar{position:sticky;top:calc(var(--topbar) + 8px);z-index:25;display:flex;align-items:center;gap:12px;
  padding:8px 12px;border-radius:var(--r2);background:var(--navy-700);color:#fff;
  box-shadow:var(--sh2);margin-bottom:var(--s3);font-size:var(--t-meta);border-left:var(--bar) solid var(--sky-500)}
html[data-theme="dark"] .pickbar{background:var(--sky-600)}
.tapword{display:none}
@media (hover:none){.dragword{display:none}.tapword{display:inline}}
.pchip:hover{border-color:var(--border-strong);background:var(--surface)}
.pchip.warnflag{background:var(--warn-bg);color:var(--warn)}
.pool{display:flex;flex-wrap:wrap;gap:6px}

/* ===================== CAPACITY STRIP ===================== */
.cap{display:grid;grid-template-columns:repeat(auto-fit,minmax(88px,1fr));gap:var(--s2)}
.capd{border:1px solid var(--rule);border-radius:var(--r2);padding:var(--s3);text-align:center;
  background:var(--surface);transition:border-color var(--dur);border-top:var(--bar) solid var(--rule-strong)}
.capd:hover{border-color:var(--border-strong)}
.capd .d{font-size:var(--t-micro);font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);color:var(--text-3)}
.capd .p{font-family:var(--font-head);font-size:var(--t-title);font-weight:var(--w-semi);margin:4px 0 6px}
.capd.free{border-top-color:var(--ok);background:var(--ok-bg)}
.capd.full{border-top-color:var(--warn)}

/* ===================== CHART ===================== */
.bars{display:flex;align-items:flex-end;gap:var(--s2);height:150px}
.bars .b{flex:1;display:flex;flex-direction:column;justify-content:flex-end;gap:6px;height:100%;position:relative}
.bars .b>i{display:block;background:var(--navy-700);transition:height .5s var(--ease),opacity var(--dur);min-height:2px}
html[data-theme="dark"] .bars .b>i{background:var(--sky-500)}
.bars .b:hover>i{opacity:.82}
.bars .b>s{text-decoration:none;text-align:center;font-size:var(--t-micro);color:var(--text-3);font-family:var(--font-head);
  text-transform:uppercase;letter-spacing:var(--ls-caps)}
.bars .b>u{text-decoration:none;position:absolute;top:-19px;left:0;right:0;text-align:center;font-size:var(--t-tiny);
  font-weight:var(--w-bold);opacity:0;transition:opacity var(--dur)}
.bars .b:hover>u{opacity:1}
.spark{display:flex;align-items:flex-end;gap:2px;height:34px}
.spark i{flex:1;background:var(--sky-600);min-height:2px;opacity:.7}
.spark i:last-child{opacity:1}

/* ===================== QR ===================== */
.qr{background:#fff;padding:8px;border-radius:var(--r1);display:inline-block;border:1px solid var(--rule-strong)}
.qr canvas,.qr svg{display:block;image-rendering:pixelated}

/* ===================== TIMELINE ===================== */
.tline{position:relative;padding-left:28px}
.tline::before{content:"";position:absolute;left:6px;top:6px;bottom:6px;width:1.5px;background:var(--border)}
.tev{position:relative;padding-bottom:var(--s4)}
.tev::before{content:"";position:absolute;left:-24px;top:5px;width:9px;height:9px;border-radius:0;
  background:var(--surface);border:2px solid var(--rule-strong)}
.tev.on::before{border-color:var(--sky-500);background:var(--sky-500)}
.tev.done::before{border-color:var(--ok);background:var(--ok)}

/* lifecycle rail */
.life{display:flex;align-items:center;gap:0;overflow-x:auto;padding:var(--s2) 0}
.lifestep{display:flex;align-items:center;gap:6px;flex:none}
.lifestep .p{padding:4px 12px;border-radius:0;font-size:var(--t-micro);font-weight:var(--w-semi);letter-spacing:var(--ls-caps);
  text-transform:uppercase;font-family:var(--font-head);background:var(--surface-2);color:var(--ink-3);
  white-space:nowrap;border:1px solid var(--rule);border-bottom-width:var(--bar);border-bottom-color:var(--rule-strong)}
.lifestep.done .p{color:var(--ok);border-bottom-color:var(--ok);background:var(--surface)}
.lifestep.on .p{background:var(--navy-700);color:#fff;border-color:var(--navy-700)}
html[data-theme="dark"] .lifestep.on .p{background:var(--sky-600);color:#fff;border-color:var(--sky-600)}
.lifestep .ln{width:0;height:0;margin:0}
.life{gap:2px}

/* tabs */
.tabs{display:flex;gap:2px;border-bottom:1px solid var(--border);overflow-x:auto}
.tabs button{padding:12px 16px;font-size:var(--t-meta);font-weight:var(--w-semi);color:var(--ink-3);position:relative;
  white-space:nowrap;transition:color var(--dur);font-family:var(--font);letter-spacing:var(--ls-0)}
.tabs button:hover{color:var(--text-2)}
.tabs button.on{color:var(--text)}
.tabs button.on::after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;
  background:var(--navy-700)}
html[data-theme="dark"] .tabs button.on::after{background:var(--sky-500)}

/* ===================== AI ===================== */
.ai-msg{display:flex;gap:var(--s3);padding:var(--s4) 0}
.ai-b{max-width:min(720px,100%);padding:12px 16px;border-radius:var(--r3);font-size:var(--t-body);line-height:var(--lh-prose)}
.ai-msg.me{justify-content:flex-end}
.ai-msg.me .ai-b{background:var(--navy-700);color:#fff;border-bottom-right-radius:6px}
html[data-theme="dark"] .ai-msg.me .ai-b{background:var(--sky-500);color:#06122A}
.ai-msg.ai .ai-b{background:var(--surface-2);border:1px solid var(--border);border-bottom-left-radius:6px}
/* ------------------------------------------------------------
   THE OFFER UNDER AN ANSWER

   A hairline, a small printed label and two quiet buttons — not a
   card inside the answer card. The answer is the thing being read;
   this is a footnote to it, and it should look like one. It sits
   inside the bubble on a rule rather than in a box of its own so
   the eye does not have to decide which of two boxes matters.
   ------------------------------------------------------------ */
.ai-voice{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;
  margin-top:12px;padding-top:12px;border-top:1px solid var(--rule)}
.ai-voice .eyebrow{color:var(--ink-3)}
.ai-voice.done{padding-top:8px}
/* while it is speaking, the label carries the state and the only
   button on the row is the one that stops it */
.ai-voice.on .eyebrow{color:var(--int)}
@media (max-width:520px){
  /* on a phone the two buttons take the row and the label sits over
     them, rather than three things fighting for one line */
  .ai-voice{gap:6px}
  .ai-voice .eyebrow{flex-basis:100%}
  .ai-voice .grow{display:none}
  .ai-voice .btn{flex:1 1 auto}
}

.ai-mark{width:28px;height:28px;border-radius:var(--r1);background:var(--navy-700);color:#fff;flex:none;
  display:grid;place-items:center}
html[data-theme="dark"] .ai-mark{background:var(--sky-500);color:#06122A}
.ai-src{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;border-radius:var(--r1);
  background:var(--surface);border:1px solid var(--border);font-size:var(--t-small);font-weight:var(--w-semi);
  color:var(--text-2);margin:4px 4px 0 0;transition:var(--tr-ui)}
.ai-src:hover{border-color:var(--sky-500);color:var(--text)}

/* ===================== DEVICE FRAMES ===================== */
.devwrap{display:grid;place-items:center;min-height:100dvh;padding:var(--s5);background:
  radial-gradient(1200px 600px at 50% -10%,color-mix(in srgb,var(--navy-700) 22%,var(--bg)),var(--bg))}
.phone{width:392px;max-width:100%;height:min(844px,calc(100dvh - 48px));background:var(--surface);
  border-radius:32px;box-shadow:var(--sh3),0 0 0 11px #17172c,0 0 0 13px #2c2c46;overflow:hidden;
  display:flex;flex-direction:column;position:relative}
.phone.bare{border-radius:0;box-shadow:none;width:100%;height:100dvh}
.notch{position:absolute;top:9px;left:50%;transform:translateX(-50%);width:104px;height:26px;
  background:#0a0a16;border-radius:var(--rp);z-index:20}
.mob-head{padding:44px var(--s4) var(--s3);background:var(--chrome);color:#fff;flex:none}
.phone.bare .mob-head{padding-top:max(20px,var(--sat))}
.mob-body{flex:1;overflow-y:auto;background:var(--bg);padding:var(--s4);padding-bottom:96px}
/* Solid, not frosted. The bottom bar was translucent with an 18px
   blur behind it, so the content scrolling under it showed through
   as a moving smear and the labels lost contrast exactly when
   somebody was scrolling — which is when they are looking for
   them. A bar a man taps with a glove on should be one flat
   colour with a hairline over it. */
.mob-tabs{position:absolute;bottom:0;left:0;right:0;height:74px;background:var(--surface);
  border-top:1px solid var(--border);
  display:grid;grid-auto-flow:column;padding-bottom:16px;z-index:15}
.phone.bare .mob-tabs{padding-bottom:max(16px,var(--sab));height:auto;padding-top:8px}
.mtab{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
  font-size:var(--t-micro);font-weight:var(--w-semi);color:var(--text-3);padding-top:12px;transition:color var(--dur)}
.mtab.on{color:var(--navy-700)}
html[data-theme="dark"] .mtab.on{color:var(--sky-500)}
.bigbtn{display:flex;align-items:center;gap:var(--s3);width:100%;padding:var(--s3) var(--s4);
  border-radius:var(--r2);background:var(--surface);border:1px solid var(--rule);text-align:left;
  border-left:var(--bar) solid var(--rule-strong);transition:border-color var(--dur)}
.bigbtn:active{transform:scale(.99)}
.bigbtn .ico{width:34px;height:34px;border-radius:var(--r1);display:grid;place-items:center;flex:none;
  color:var(--ink-2)}
.bigbtn.p{background:var(--navy-700);color:#fff;border-color:var(--navy-700)}
.bigbtn.p .ico{background:rgba(255,255,255,.14);color:#fff}
html[data-theme="dark"] .bigbtn.p{background:var(--sky-500);color:#06122A;border-color:var(--sky-500)}
html[data-theme="dark"] .bigbtn.p .ico{background:rgba(0,0,0,.15);color:#06122A}
.tabletframe{width:min(1120px,100%);height:min(800px,calc(100dvh - 48px));border-radius:28px;overflow:hidden;
  box-shadow:var(--sh3),0 0 0 10px #17172c;background:var(--bg);display:flex;flex-direction:column}

/* photo grid */
.photos{display:grid;grid-template-columns:repeat(auto-fill,minmax(92px,1fr));gap:6px}
.ph{aspect-ratio:1;border-radius:var(--r1);background:var(--surface-3);display:grid;place-items:center;
  color:var(--ink-3);font-size:var(--t-micro);overflow:hidden;position:relative;border:1px solid var(--rule)}
.ph.filled{background:linear-gradient(135deg,var(--navy-700),var(--sky-600));color:#fff}

/* signature pad */
.sigpad{border:1.5px dashed var(--border-strong);border-radius:var(--r3);background:var(--surface-2);
  height:172px;position:relative;touch-action:none;overflow:hidden}
.sigpad canvas{width:100%;height:100%;display:block;cursor:crosshair}
.sigpad .hint{position:absolute;inset:0;display:grid;place-items:center;color:var(--text-3);
  font-size:var(--t-meta);pointer-events:none}

/* client portal */
.portal-head{background:var(--chrome);color:#fff;padding:var(--s4) var(--s6)}
.portal-in{max-width:1180px;margin:0 auto;width:100%}
.portal-body{padding:var(--s6)}
@media (max-width:720px){
  .portal-head{padding:var(--s3) var(--s4)}
  .portal-body{padding:var(--s4) var(--s4) calc(var(--s7) + var(--sab))}
  .portal-label{display:none}
  .portal-head .logo svg.mk{width:28px;height:28px}
  .portal-head .wordmark b{font-size:var(--t-input)}
  .portal-head .wordmark i{font-size:7.5px}
}
.portal-hero{background:var(--navy-700);color:#fff;border-radius:var(--r3);padding:var(--s6);
  position:relative;overflow:hidden;border-left:6px solid var(--sky-500)}
.portal-hero::after{content:"";position:absolute;inset:0;opacity:.1;pointer-events:none;
  background-image:linear-gradient(rgba(255,255,255,.6) 1px,transparent 1px),
    linear-gradient(90deg,rgba(255,255,255,.6) 1px,transparent 1px);background-size:60px 38px}
.portal-hero>*{position:relative;z-index:1}

/* login */
.gate{min-height:100dvh;display:grid;grid-template-columns:1.05fr .95fr}
.gate-l{background:var(--navy-700);color:#fff;padding:var(--s8) var(--s7);display:flex;
  flex-direction:column;justify-content:space-between;position:relative;overflow:hidden}
.gate-l::after{content:"";position:absolute;inset:0;opacity:.16;pointer-events:none;
  background-image:linear-gradient(rgba(255,255,255,.55) 1.2px,transparent 1.2px),
    linear-gradient(90deg,rgba(255,255,255,.55) 1.2px,transparent 1.2px);background-size:64px 40px}
.gate-r{display:grid;place-items:center;padding:var(--s6);background:var(--bg)}
.rolecard{display:flex;align-items:center;gap:var(--s3);padding:12px var(--s4);border-radius:var(--r2);
  border:1px solid var(--rule);background:var(--surface);width:100%;text-align:left;
  border-left:var(--bar) solid transparent;transition:border-color var(--dur),background var(--dur)}
.rolecard:hover{border-color:var(--rule-strong);border-left-color:var(--sky-600);background:var(--surface-2)}

/* diagnostics */
.diag{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:var(--t-small);line-height:var(--lh-prose);
  background:var(--surface-2);border:1px solid var(--border);border-radius:var(--r2);padding:var(--s4);
  white-space:pre-wrap;word-break:break-word;max-height:280px;overflow:auto}

/* ===================== LAYOUT PRIMITIVES ===================== */
.split{display:grid;gap:var(--s4);align-items:start;
  grid-template-columns:minmax(0,var(--sa,1.5fr)) minmax(0,var(--sb,1fr))}
.trio{display:grid;gap:var(--s4);align-items:start;grid-template-columns:repeat(3,minmax(0,1fr))}
.tblwrap{overflow-x:auto;-webkit-overflow-scrolling:touch;overscroll-behavior-x:contain}
.tblwrap>.tbl{min-width:max-content}
.scores{display:grid;gap:var(--s4);align-items:end;grid-template-columns:repeat(3,minmax(0,1fr)) auto}
.scores-total{text-align:right}
html,body{overflow-x:clip}

/* sidebar backdrop — only exists on small screens */
#sidescrim{display:none}

.healthstrip{display:flex;align-items:center;gap:var(--s4);width:100%;text-align:left;
  padding:var(--s3) var(--s4);border-radius:var(--r2);background:var(--surface);
  border:1px solid var(--rule);border-left:var(--bar) solid var(--navy-700)}
.hs-score{font-family:var(--font-head);font-size:var(--t-figure);font-weight:var(--w-semi);line-height:var(--lh-solid);flex:none}

/* day picker — the phone's answer to a five-column board */
.dayrow{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:6px;align-items:stretch}
.dayrow .wknav{display:grid;place-items:center;border-radius:var(--r2);border:1px solid var(--border);
  background:var(--surface);color:var(--text-2);width:32px}
.dayrow{grid-template-columns:32px repeat(5,1fr) 32px;grid-auto-flow:row}
.daychip{display:flex;flex-direction:column;align-items:center;gap:2px;padding:8px 2px;
  border-radius:var(--r1);border:1px solid var(--rule);background:var(--surface);
  border-top:var(--bar) solid var(--rule-strong);transition:var(--tr-ui)}
.daychip b{font-family:var(--font-head);text-transform:uppercase;font-size:var(--t-micro);
  letter-spacing:var(--ls-caps);color:var(--text-3);font-weight:var(--w-med)}
.daychip span{font-family:var(--font-head);font-size:var(--t-lead);font-weight:var(--w-semi)}
.daychip i{font-style:normal;font-size:var(--t-micro);font-weight:var(--w-bold);color:var(--text-3)}
.daychip i.ok{color:var(--ok)} .daychip i.warn{color:var(--warn)}
.daychip.is-today{border-color:var(--border-strong)}
.daychip.on{background:var(--navy-700);border-color:var(--navy-700)}
.daychip.on b,.daychip.on span,.daychip.on i{color:#fff}
html[data-theme="dark"] .daychip.on{background:var(--sky-600)}

/* ===================== MOBILE SHELL ===================== */
.mshell{min-height:100dvh;display:flex;flex-direction:column;
  padding-bottom:calc(66px + var(--sab))}
.mtop{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:2px;
  padding:var(--sat) 6px 0;height:calc(56px + var(--sat));
  background:var(--chrome);color:var(--chrome-text);
  box-shadow:0 1px 0 rgba(0,0,0,.08)}
.mtop .icobtn{color:#fff}
.mtop .icobtn:hover{background:rgba(255,255,255,.12)}
.mtop-mark{width:40px;display:grid;place-items:center;flex:none;opacity:.95}
.mtop-title{flex:1;min-width:0;font-family:var(--font-head);text-transform:uppercase;
  letter-spacing:var(--ls-caps);font-size:var(--t-body);font-weight:var(--w-semi);padding:0 4px}
.mbar{position:fixed;left:0;right:0;bottom:0;z-index:45;display:grid;grid-auto-flow:column;
  grid-auto-columns:1fr;background:var(--surface);
  border-top:1px solid var(--border);padding-bottom:var(--sab)}
.mbtab{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
  padding:8px 2px 8px;font-size:var(--t-micro);font-weight:var(--w-semi);color:var(--text-3);
  letter-spacing:var(--ls-micro);transition:color var(--dur)}
.mbtab.on{color:var(--navy-700)}
.mbtab.on::before{content:"";position:absolute;top:0;left:22%;right:22%;height:2px;background:var(--navy-700)}
.mbtab{position:relative}
html[data-theme="dark"] .mbtab.on::before{background:var(--sky-400)}
html[data-theme="dark"] .mbtab.on{color:var(--sky-400)}
.dotbadge{position:absolute;top:-1px;right:-5px;width:9px;height:9px;border-radius:var(--rp);
  background:var(--risk);border:2px solid var(--surface)}
.mtop .dotbadge{border-color:var(--chrome)}

/* ===================== RESPONSIVE ===================== */
@media (max-width:1280px){
  .g4{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:1080px){
  .split,.trio{grid-template-columns:minmax(0,1fr)}
  .g3{grid-template-columns:repeat(2,minmax(0,1fr))}
  .page{padding:var(--s5)}
  .topbar{padding:0 var(--s5)}
}
@media (max-width:900px){
  .shell{grid-template-columns:1fr}
  .side{position:fixed;left:0;top:0;width:min(288px,86vw);transform:translateX(-101%);
    transition:transform .26s var(--ease);z-index:60;box-shadow:var(--sh3)}
  .side.open{transform:none}
  #sidescrim{display:block;position:fixed;inset:0;z-index:55;background:var(--scrim);
    opacity:0;pointer-events:none;transition:opacity .24s var(--ease)}
  #sidescrim.on{opacity:1;pointer-events:auto}
  .gate{grid-template-columns:1fr}
  .gate-l{display:none}
  .gate-r{padding:var(--s5) var(--s4)}
  .board{grid-auto-flow:column;grid-auto-columns:minmax(272px,78vw);
    grid-template-columns:none!important;scroll-snap-type:x proximity}
  .bcol{scroll-snap-align:start}
}
@media (max-width:720px){
  :root{--topbar:58px}
  body{font-size:var(--t-body);-webkit-text-size-adjust:100%}
  .page{padding:var(--s4) var(--s4) var(--s6)}
  .topbar{padding:0 var(--s4);gap:6px}
  .page-title{font-size:var(--t-title)}
  .dsp-xl{font-size:var(--t-figure)} .dsp-lg{font-size:var(--t-page)} .dsp-md{font-size:var(--t-title)}

  .g4{grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s3)}
  .g3,.g2{grid-template-columns:minmax(0,1fr)}
  .grid{gap:var(--s3)}
  .stack{gap:var(--s3)}

  .card.pad{padding:var(--s4)}
  .tile{padding:12px var(--s3) 12px}
  .tile .v{font-size:var(--t-page);margin-top:4px}
  .tile .meter{margin-top:8px!important}
  .tile .k{font-size:var(--t-micro);letter-spacing:var(--ls-caps)}
  .tile .sub{font-size:var(--t-tiny)}
  .tile .arrow{display:none}

  /* head + action rows stack cleanly instead of laddering down the right edge */
  .page-head{flex-direction:column;align-items:stretch;gap:var(--s3)}
  .page-head>.row{width:100%;flex-wrap:wrap}
  .page-head .sel,.page-head .inp{width:100%}
  .row{flex-wrap:wrap}
  .scores{grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--s3)}
  .scores-total{grid-column:1/-1;text-align:left;display:flex;align-items:baseline;
    gap:12px;padding-top:var(--s2);border-top:1px solid var(--border)}
  .row.wrap>.btn{flex:1 1 auto}
  .spread.wrap{flex-direction:column;align-items:stretch}
  .spread.wrap>.row.wrap{width:100%}

  /* search collapses to its icon; the save chip to its icon and one
     short word — the icon alone did not say saved from not saved */
  .searchbar{flex:none;width:40px;padding:0;justify-content:center}
  .searchbar>span,.kbd{display:none}
  .chiptxt{display:none}
  .chipword{display:inline;white-space:nowrap}
  .savechip{padding:0 8px}

  /* tables scroll rather than hide their columns */
  /* tables become stacked, labelled cards — nothing scrolls sideways,
     nothing is hidden */
  .tblwrap{overflow:visible}
  .tbl{display:block;min-width:0!important;border-spacing:0}
  .tbl thead{display:none}
  .tbl tbody{display:block}
  .tbl tbody tr{display:block;background:var(--surface);border:1px solid var(--border);
    border-radius:var(--r3);padding:var(--s3) var(--s4) 12px;margin-bottom:12px;box-shadow:var(--sh1)}
  .tbl tbody tr:hover{background:var(--surface)}
  .tbl td{display:flex;align-items:center;justify-content:space-between;gap:var(--s4);
    border:0;padding:6px 0;text-align:right;font-size:var(--t-meta);min-height:0}
  .tbl td::before{content:attr(data-l);color:var(--text-3);font-size:var(--t-micro);font-weight:var(--w-semi);
    text-transform:uppercase;letter-spacing:var(--ls-caps);font-family:var(--font-head);
    text-align:left;flex:none;white-space:nowrap}
  .tbl td.cell-lead{display:block;text-align:left;padding:0 0 8px;margin-bottom:4px;
    border-bottom:1px solid var(--border);font-size:var(--t-body)}
  .tbl td.cell-lead::before{display:none}
  .tbl td.cell-blank{display:none}
  .tbl td.r{text-align:right}
  .tbl tr.is-empty{background:transparent;border:0;box-shadow:none;padding:0}
  .tbl tr.is-empty td{display:block;text-align:left;color:var(--text-3)}
  .tbl tr.is-empty td::before{display:none}
  .tbl td .meter{width:100%;min-width:70px}
  .tbl .mob-x{display:none}
  .tbl tbody tr.click{cursor:pointer}
  .tbl tbody tr.click:active{transform:scale(.995);border-color:var(--sky-500)}
  .lede{font-size:var(--t-meta)}
  .page-head{gap:var(--s2);margin-bottom:var(--s4)}

  /* the top bar already names the screen and carries the back arrow */
  .page-head .page-title{display:none}
  .backlink{display:none}
  .page-head .eyebrow{font-size:var(--t-micro)}

  /* tabs read as pills on a touch screen */
  .tabs{border-bottom:0;gap:6px;padding:2px 0 var(--s2)}
  .tabs button{border:1px solid var(--border-strong);border-radius:var(--rp);
    background:var(--surface);padding:8px 16px;font-size:var(--t-meta);color:var(--text-2)}
  .tabs button.on{background:var(--navy-700);border-color:var(--navy-700);color:#fff}
  .tabs button.on::after{display:none}
  .tab-all{border:1px dashed var(--border-strong)!important;background:transparent!important;
    display:inline-flex;align-items:center;gap:6px}

  .tabs{scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch;
    mask-image:linear-gradient(90deg,#000 calc(100% - 26px),transparent);
    -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 26px),transparent)}
  .tabs::-webkit-scrollbar{display:none}
  .tabs button{scroll-snap-align:start;padding:12px 12px;font-size:var(--t-meta)}

  .sheet.modal,.sheet.modal.wide,.sheet.modal.xwide{border-radius:var(--r5) var(--r5) 0 0;
    top:auto;bottom:0;left:0;transform:translateY(100%);
    width:100vw;max-width:100vw;max-height:92dvh}
  .sheet.modal.in,.sheet.modal.wide.in,.sheet.modal.xwide.in{transform:none}
  .sheet.drawer,.sheet.drawer.wide{width:100vw}
  .sheet-head{padding:var(--s4) var(--s4) var(--s3)}
  .sheet-body{padding:var(--s4);padding-bottom:calc(var(--s4) + var(--sab))}
  .sheet-foot{border-radius:0;padding:var(--s3) var(--s4);flex-wrap:wrap;
    padding-bottom:calc(var(--s3) + var(--sab))}
  .sheet-foot>.btn{flex:1 1 auto}
  .steps{padding:var(--s3) var(--s4);overflow-x:auto}
  .steps .step span:not(.n){display:none}
  .steps .step.on span:not(.n){display:inline}
  .step-line{min-width:10px;margin:0 6px}

  .fab{display:none}
  #toasts{bottom:calc(84px + var(--sab))}
  /* 44px is the size a gloved thumb actually hits. Nothing tappable on a
     phone goes under it, small variants included. */
  .btn{min-height:44px}
  .btn.sm{min-height:44px;padding:0 12px}
  .icobtn{width:44px;height:44px}
  .chip{min-height:44px}
  .gbtn.mob{min-height:44px}

  .factor{grid-template-columns:1fr 58px 32px;font-size:var(--t-small)}
  .dial{--dial:116px}
  .health{align-items:center}
  .factors{width:100%}
  .health{flex-direction:column;align-items:stretch;gap:var(--s4)}
  .devwrap{padding:0}
  .sec-head{flex-wrap:wrap}
  .att{flex-wrap:wrap}
  .att>.btn{width:100%;margin-top:var(--s2)}
  .jcard,.bcol{scroll-snap-align:start}
  .cap{grid-template-columns:repeat(auto-fit,minmax(72px,1fr))}
  .photos{grid-template-columns:repeat(auto-fill,minmax(84px,1fr))}
  .portal-hero{padding:var(--s5) var(--s4);border-radius:var(--r4)}
  /* V1: bottom is set above, clear of the tab bar. This rule used
     to reset it to s5 and put every toast on top of the tabs. */
  #toasts{width:100%}
  .toast{max-width:calc(100vw - 32px)}
}
@media (max-width:400px){
  .page-title{font-size:var(--t-title)}
  .tile .v{font-size:var(--t-title)}
  .g4{gap:8px}
}
@media (hover:none){
  .card.hov:hover{transform:none;box-shadow:var(--sh1)}
  .tile:hover{transform:none}
}
@media print{
  .side,.topbar,.fab,.no-print,#sidescrim{display:none!important}
  .shell{grid-template-columns:1fr}
  body{background:#fff}
}

/* ===================== SCANNED TAG ===================== */
.tagwrap{min-height:100dvh;background:var(--bg)}
.taghead{background:var(--chrome);color:#fff;padding:var(--s4) var(--s4);
  padding-top:calc(var(--s4) + var(--sat));display:flex;align-items:center;
  justify-content:space-between;gap:var(--s3)}
.tagcard{max-width:640px;margin:0 auto;padding:var(--s4) var(--s4) 0}
.tagstatus{padding:var(--s5) var(--s4);border-radius:var(--r3) var(--r3) 0 0;color:#fff}
.tagstatus.valid{background:var(--ok)}
.tagstatus.due,.tagstatus.soon{background:var(--warn)}
.tagstatus.overdue{background:var(--risk)}
.lnk{color:var(--sky-600);font-weight:var(--w-semi);text-decoration:underline;padding:0}
html[data-theme="dark"] .lnk{color:var(--sky-400)}

/* ===================== PRINTED SCAFFTAGS ===================== */
#printsheet{display:none}
.ptag{width:88mm;border:1.4mm solid var(--navy-700);border-radius:3mm;overflow:hidden;
  background:#fff;color:#111;break-inside:avoid;page-break-inside:avoid;font-family:var(--font)}
.ptag-h{background:#0F0C5B;color:#fff;padding:3mm 4mm;display:flex;align-items:center;gap:2.5mm}
.ptag-h b{font-family:var(--font-head);font-size:11pt;letter-spacing:.02em;display:block;line-height:1}
.ptag-h i{font-family:var(--font-head);font-style:normal;font-size:5pt;letter-spacing:.22em;
  display:block;opacity:.8;margin-top:.6mm}
.ptag-tt{margin-left:auto;font-family:var(--font-head);text-transform:uppercase;
  letter-spacing:.14em;font-size:6.5pt;opacity:.85}
.ptag-b{padding:4mm}
.ptag-id{font-family:var(--font-head);font-size:20pt;font-weight:600;text-align:center;
  letter-spacing:.03em;line-height:1}
.ptag-qr{display:flex;justify-content:center;margin:3mm 0 1.5mm}
.ptag-qr svg{width:40mm;height:40mm}
.ptag-scan{text-align:center;font-size:6.5pt;color:#555;margin-bottom:3mm}
.ptag-rows>div{display:flex;justify-content:space-between;gap:3mm;font-size:7pt;
  padding:1.3mm 0;border-top:.2mm solid #ddd}
.ptag-rows span{color:#666;flex:none}
.ptag-rows b{text-align:right;font-weight:600}
.ptag-warn{margin-top:3mm;background:#C02B0A;color:#fff;text-align:center;
  font-family:var(--font-head);font-size:7pt;letter-spacing:.09em;padding:1.8mm}
.ptag-f{text-align:center;font-size:6pt;color:#666;margin-top:2.5mm}

@media print{
  body.printing>*{display:none!important}
  body.printing #printsheet{display:grid!important;grid-template-columns:repeat(2,88mm);
    gap:6mm;justify-content:center;padding:0}
  body.printing{background:#fff}
  @page{size:A4;margin:8mm}
}

/* the photographed tag */
.tagshot{border-radius:var(--r2);overflow:hidden;border:1px solid var(--border);background:var(--surface-2)}
.tagshot img{display:block;width:100%;height:auto;max-height:340px;object-fit:contain}
.tagshot.sm img{max-height:110px;object-fit:cover}
.shootbtn{display:flex;flex-direction:column;align-items:center;gap:6px;width:100%;
  padding:var(--s6) var(--s4);border-radius:var(--r2);border:1.5px dashed var(--rule-strong);
  background:var(--surface-2);text-align:center;transition:border-color var(--dur)}
.shootbtn:hover{border-color:var(--sky-500);background:var(--info-bg)}
.shootbtn b{font-size:var(--t-body)}
.shootbtn .tiny{max-width:300px}
.shoot-ic{width:46px;height:46px;border-radius:var(--r2);background:var(--navy-700);color:#fff;
  display:grid;place-items:center;margin-bottom:4px}
html[data-theme="dark"] .shoot-ic{background:var(--sky-500);color:#06122A}

/* on-screen preview of the printed tags */
.tagsheet{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:var(--s4);
  justify-items:center}
.tagsheet .ptag{width:300px}
@media (max-width:720px){.tagsheet{grid-template-columns:1fr}}
/* the handwritten strip — the tag is still filled in by hand */
.ptag-write{margin-top:3mm;border:.3mm solid #999;border-radius:1mm;padding:1.5mm 2mm}
.ptag-write>div{display:flex;align-items:baseline;gap:2mm;padding:.8mm 0}
.ptag-write span{font-size:6.5pt;color:#666;width:16mm;flex:none}
.ptag-write i{flex:1;border-bottom:.25mm solid #333;height:4.5mm}

/* ===================== THE DAY BOARD ===================== */
/* ============================================================
   THE CONTROL STRIP

   Six figures, six cards. They were one welded bar before, which
   made them read as a header rather than as six things you can
   open. Each one now carries a very quiet drawing of what it is
   counting — scaffold linework, a lorry elevation, the coastline
   this company actually works. The drawing is at 10% and sits
   behind the figure; if you notice it before the number, it is
   wrong.
   ============================================================ */
.today{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:var(--s3)}
.today-c{position:relative;overflow:hidden;min-width:0;padding:var(--s4) var(--s4) var(--s7);
  border-radius:var(--r3);border:1px solid var(--rule);
  background:var(--surface);background-image:var(--pane);
  box-shadow:var(--lift),var(--sh1);
  transition:transform var(--dur) var(--ease),box-shadow var(--dur) var(--ease),
    border-color var(--dur)}
a.today-c,button.today-c{cursor:pointer;text-align:left;display:block;width:100%;font:inherit;color:inherit}
a.today-c:hover,button.today-c:hover{transform:translateY(-2px);border-color:var(--rule-strong);
  box-shadow:var(--lift-2),var(--sh2)}
a.today-c:active,button.today-c:active{transform:translateY(0)}
.today-k{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);font-size:var(--t-micro);
  color:var(--ink-3);display:flex;align-items:center;gap:6px;position:relative;z-index:2}
.today-v{display:block;font-size:var(--t-figure);margin-top:8px;line-height:var(--lh-solid);position:relative;z-index:2;
  color:var(--ink)}
.today-s{display:block;font-size:var(--t-tiny);color:var(--ink-2);margin-top:6px;position:relative;z-index:2}
.today-c.flag .today-v{color:var(--gold)}
/* The drawing sits in the foot of the card, under the text, with
   its top edge faded out. The first pass ran it full-height and
   the sub-label landed on top of a scaffold — legible in a
   screenshot, not legible at 06:40. */
.today-art{position:absolute;right:0;bottom:0;left:0;height:52px;z-index:1;
  opacity:.4;pointer-events:none;display:block;
  -webkit-mask-image:linear-gradient(180deg,transparent,#000 55%);
  mask-image:linear-gradient(180deg,transparent,#000 55%)}
.today-art svg{width:100%;height:100%;display:block}
.today-c:hover .today-art{opacity:.62}
html[data-theme="light"] .today-art{opacity:.3}
/* the open affordance appears on hover rather than sitting there
   on all six at once */
.today-open{position:absolute;top:var(--s3);right:var(--s3);z-index:3;color:var(--ink-3);
  opacity:0;transform:translateX(-3px);transition:opacity var(--dur),transform var(--dur) var(--ease)}
a.today-c:hover .today-open,button.today-c:hover .today-open{opacity:1;transform:none}

/* brief rows read as a numbered schedule */
.brief-row{display:flex;align-items:flex-start;gap:12px;text-align:left;width:100%;
  padding:12px 0;border-bottom:1px solid var(--rule);transition:background var(--dur)}
.brief-row:last-child{border-bottom:0}
.brief-row:hover{background:var(--surface-2)}
.brief-n{width:22px;height:22px;border:1px solid var(--rule-strong);flex:none;
  display:grid;place-items:center;font-size:var(--t-tiny);color:var(--ink-2);border-radius:var(--r1)}

@media (max-width:1080px){
  .today{grid-template-columns:repeat(3,minmax(0,1fr))}
}
@media (max-width:600px){
  /* on a phone the strip swipes rather than stacking six deep and
     pushing everything that matters below the fold */
  .today{display:flex;gap:var(--s2);overflow-x:auto;scroll-snap-type:x mandatory;
    scrollbar-width:none;margin:0 calc(var(--s4) * -1);padding:0 var(--s4) var(--s2)}
  .today::-webkit-scrollbar{display:none}
  .today-c{flex:0 0 62%;scroll-snap-align:start;padding:var(--s3) var(--s4) var(--s6)}
  .today-v{font-size:var(--t-figure)}
  .today-art{height:44px}
}

/* ============================================================
   G — a command layer, not a chatbot. Understated, square,
   built from the same rules and bands as the rest of the app.
   ============================================================ */
/* G is a mark, not a menu item. Circular, one letter, its own
   quiet light — so it reads as a way in to the system rather than
   another control on the bar. */
.gbtn{display:inline-flex;align-items:center;justify-content:center;gap:0;
  width:38px;height:38px;padding:0;border-radius:var(--rp);position:relative;
  background:var(--surface-2);border:1px solid var(--rule-strong)!important;
  transition:background var(--dur),border-color var(--dur),box-shadow var(--dur),transform .1s var(--ease)}
.gbtn .gbtn-l{font-family:var(--font-head);font-size:var(--t-body);font-weight:var(--w-semi);letter-spacing:var(--ls-0);
  color:var(--ink);line-height:var(--lh-solid)}
.gbtn .gorb{position:absolute;top:5px;right:5px;width:7px;height:7px;background:transparent}
.gbtn .gorb>i{width:7px;height:7px;background:var(--int-hi);box-shadow:0 0 8px var(--int-hi)}
.gbtn:hover{background:var(--surface-3);box-shadow:0 0 0 3px var(--int-glow);transform:translateY(-1px)}
.gbtn:active{transform:scale(.94)}
.gbtn-legacy{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 12px 0 6px;
  border:1px solid var(--rule-strong);border-radius:var(--r2);background:var(--surface);
  color:var(--ink-2);font-weight:var(--w-bold);font-size:var(--t-small);transition:border-color var(--dur),color var(--dur)}
.gbtn:hover{border-color:var(--navy-700);color:var(--ink)}
.gbtn-l{font-family:var(--font-head);letter-spacing:var(--ls-caps);font-size:var(--t-tiny)}
.gbtn.mob{height:34px;width:34px;padding:0;justify-content:center;border:0;color:#fff}
.gbtn.mob .gbtn-l{display:flex;color:#fff;margin:6px 4px 0 0}
.gbtn.mob .gbtn-l svg{height:17px;width:14px}
.gbtn.mob .gorb{top:4px;right:4px}
.gbtn.mob:hover{background:rgba(255,255,255,.12)}

/* the orb: one dot, four states, no sci-fi */
.gorb{width:16px;height:16px;border-radius:var(--rp);flex:none;position:relative;
  display:grid;place-items:center;background:var(--surface-3)}
.gorb>i{width:7px;height:7px;border-radius:var(--rp);background:var(--navy-700);display:block;
  transition:background var(--dur),transform var(--dur)}
.gbtn.mob .gorb{background:rgba(255,255,255,.18)}
.gbtn.mob .gorb>i{background:#fff}
html[data-theme="dark"] .gorb>i{background:var(--sky-400)}
.gorb.sm{width:15px;height:15px}
.gorb.listening{background:var(--info-bg)}
.gorb.listening>i{background:var(--info);animation:gpulse 1.15s ease-in-out infinite}
.gorb.working>i{background:var(--sky-600);animation:gpulse .8s ease-in-out infinite}
.gorb.done>i{background:var(--ok)}
.gorb.error>i{background:var(--risk)}
@keyframes gpulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(.55);opacity:.55}}

.gwrap{position:fixed;inset:0;z-index:150;opacity:0;transition:opacity .15s var(--ease)}
.gwrap.in{opacity:1}
.gscrim{position:absolute;inset:0;background:var(--scrim)}
.gpanel{position:absolute;left:50%;top:11vh;transform:translate(-50%,-10px) scale(.99);
  width:min(680px,calc(100vw - 32px));
  background:var(--surface);background-image:var(--pane);
  border:1px solid var(--rule-strong);border-radius:var(--r5);
  box-shadow:var(--sh3),var(--lift-2),0 0 0 1px var(--int-glow);
  overflow:hidden;transition:transform var(--dur-2) var(--ease-out);
  max-height:78vh;display:flex;flex-direction:column}
.gwrap.in .gpanel{transform:translate(-50%,0) scale(1)}
/* the masthead — the one place in the system where a big mark is
   allowed, because it is what tells you what you are talking to */
.ghead{padding:var(--s5) var(--s5) var(--s3);display:flex;align-items:center;gap:var(--s3);flex:none;
  background:radial-gradient(340px 120px at 12% 0%,var(--int-glow),transparent 70%)}
.ghead-m{width:42px;height:42px;border-radius:var(--rp);flex:none;display:grid;place-items:center;
  font-family:var(--font-head);font-size:var(--t-title);font-weight:var(--w-semi);color:#fff;
  background:linear-gradient(180deg,var(--int-hi),var(--int));
  border:1px solid rgba(255,255,255,.2);box-shadow:0 4px 20px var(--int-glow)}
.ghead-t{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);
  font-size:var(--t-title);font-weight:var(--w-semi);line-height:var(--lh-solid)}
.ghead-s{font-size:var(--t-small);color:var(--ink-3);margin-top:2px}
.gwrap.in .gpanel{transform:translate(-50%,0)}

.gbar{display:flex;align-items:center;gap:12px;padding:12px var(--s4);margin:0 var(--s5) var(--s2);
  border:1px solid var(--rule-strong);border-radius:var(--r3);background:var(--surface-2);flex:none;
  transition:border-color var(--dur),box-shadow var(--dur)}
.gbar:focus-within{border-color:var(--int-hi);box-shadow:0 0 0 3px var(--int-glow)}
.ginput{flex:1;min-width:0;border:0;background:transparent;font-size:var(--t-input);font-weight:var(--w-med);
  outline:0;padding:4px 0;color:var(--ink)}
.ginput::placeholder{color:var(--ink-3);font-weight:var(--w-body)}
.gmic{width:32px;height:32px;border-radius:var(--r2);display:grid;place-items:center;
  color:var(--ink-2);border:1px solid var(--rule);transition:var(--tr-ui)}
.gmic:hover{border-color:var(--rule-strong);color:var(--ink)}
.gmic.on{background:var(--info-bg);border-color:var(--info);color:var(--info)}

.gstate{display:flex;align-items:center;gap:12px;padding:12px var(--s4);font-size:var(--t-meta);
  color:var(--ink-2);border-bottom:1px solid var(--rule);background:var(--surface-2)}
.gstate.err{color:var(--risk);background:var(--risk-bg)}
.gwave{display:flex;align-items:center;gap:2px;height:16px}
.gwave i{width:2px;height:5px;background:var(--info);display:block;
  animation:gw 1s ease-in-out infinite}
@keyframes gw{0%,100%{height:4px;opacity:.5}50%{height:15px;opacity:1}}

.gans{padding:var(--s4);overflow-y:auto}
.gans-t{font-size:var(--t-body);line-height:var(--lh-ui);font-weight:var(--w-med)}
.gans.no .gans-t{color:var(--ink-2)}
.gans-d{margin-top:12px;padding-top:12px;border-top:1px solid var(--rule);
  font-size:var(--t-meta);line-height:var(--lh-prose);color:var(--ink-2)}
.glist{margin-top:var(--s3);border-top:1px solid var(--rule)}
.glist-r{display:flex;align-items:center;gap:12px;width:100%;text-align:left;padding:8px 0;
  border-bottom:1px solid var(--rule);color:var(--ink-3);transition:background var(--dur)}
.glist-r:hover{background:var(--surface-2)}
.glist-k{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);font-size:var(--t-micro);
  color:var(--ink-3);width:78px;flex:none;text-align:right}
.glist-r b{display:block;font-size:var(--t-meta);color:var(--ink);font-weight:var(--w-semi)}
.glist-r span span{display:block;font-size:var(--t-small);color:var(--ink-3);margin-top:1px}

.gconf{padding:var(--s4);border-top:var(--bar) solid var(--warn);background:var(--warn-bg)}
.gconf-h{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);
  font-size:var(--t-input);font-weight:var(--w-semi)}
.gconf-rows{margin-top:var(--s3)}
.gconf-rows>div{display:flex;justify-content:space-between;gap:var(--s4);padding:6px 0;
  border-bottom:1px solid var(--warn-line);font-size:var(--t-meta)}
.gconf-rows span{color:var(--ink-2);flex:none}
.gconf-rows b{text-align:right}
.gconf-w{margin-top:12px;font-size:var(--t-small);color:var(--warn);display:flex;gap:6px;align-items:flex-start}

.gsug{padding:var(--s3) var(--s4) var(--s4);overflow-y:auto}
.gsug-h{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);font-size:var(--t-micro);
  color:var(--ink-3);margin-bottom:6px}
/* a suggestion is a command you can fire, so it is shaped like a
   control rather than a line in a list */
.gsug-b{display:flex;align-items:center;gap:12px;width:100%;text-align:left;padding:12px 12px;
  color:var(--ink);font-size:var(--t-meta);border:1px solid var(--rule);border-radius:var(--r2);
  background:var(--surface-2);margin-bottom:6px;
  transition:background var(--dur),border-color var(--dur),transform var(--dur) var(--ease)}
.gsug-b:last-child{margin-bottom:0}
.gsug-b:hover{background:var(--surface-3);border-color:var(--int);transform:translateX(2px)}
.gsug-b:hover svg{color:var(--int-hi)}
.gsug-b svg{color:var(--ink-3);flex:none}
.gsug-b.dim{color:var(--ink-2)}

.gfoot{display:flex;align-items:center;gap:var(--s3);padding:8px var(--s4);
  border-top:1px solid var(--rule);background:var(--surface-2);flex:none}
.gtog{display:flex;align-items:center;gap:6px;font-size:var(--t-small);color:var(--ink-2)}
.gtog.on{color:var(--ink)}
.gdbg{font-size:var(--t-tiny);color:var(--ink-3);text-decoration:underline}

/* first run */
.gintro{position:fixed;right:var(--s5);bottom:var(--s5);z-index:60;width:min(320px,calc(100vw - 32px));
  background:var(--chrome);color:#fff;border-radius:var(--r3);padding:var(--s4);
  box-shadow:var(--sh3);border-left:var(--bar) solid var(--sky-500);animation:fadein .3s var(--ease)}
.gintro b{font-family:var(--font-head);letter-spacing:var(--ls-0);font-size:var(--t-body);display:block}
.gintro p{font-size:var(--t-meta);color:rgba(255,255,255,.76);margin-top:6px;line-height:var(--lh-ui)}

@media (max-width:720px){
  .gpanel{top:auto;bottom:0;left:0;transform:translate(0,100%);width:100vw;
    border-radius:var(--r5) var(--r5) 0 0;max-height:82dvh;
    padding-bottom:var(--sab)}
  .gwrap.in .gpanel{transform:translate(0,0)}
  .ginput{font-size:var(--t-input)}
  .gintro{right:var(--s4);left:var(--s4);width:auto;bottom:calc(78px + var(--sab))}
}

/* a warning you can act on */
.warnlist{margin-top:8px;border-top:1px solid var(--rule);padding-top:6px}
.warnrow{display:flex;align-items:center;justify-content:space-between;gap:8px;
  font-size:var(--t-tiny);color:var(--warn);padding:2px 0}
.warnfix{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);font-size:var(--t-micro);
  font-weight:var(--w-semi);color:var(--warn);border:1px solid var(--warn-line);border-radius:var(--r1);
  padding:2px 6px;flex:none;background:var(--surface);transition:var(--tr-ui)}
.warnfix:hover{background:var(--warn);color:#fff;border-color:var(--warn)}

/* drawn stand-ins for site photographs */
:root,html[data-theme="light"]{
  --ph-sky:#DDE4EE; --ph-ground:#C9C4B6; --ph-line:#8E8B80; --ph-wall:#B9B3A6;
  --ph-roof:#7C7468; --ph-win:#7E8B9C; --ph-tube:#33384A; --ph-glass:#CFE0E8; --ph-net:#8FA3B8;
}
html[data-theme="dark"]{
  --ph-sky:#1C2334; --ph-ground:#2A2A28; --ph-line:#5A5A55; --ph-wall:#35332C;
  --ph-roof:#26241F; --ph-win:#4A5A70; --ph-tube:#B9C2D6; --ph-glass:#2B3D47; --ph-net:#46586B;
}
.phsvg{display:block;width:100%;height:100%}
.ph-t{font-family:var(--font-head);font-size:8px;letter-spacing:.12em;fill:var(--ph-tube);opacity:.75}
.ph.shot{background:var(--ph-sky);padding:0;overflow:hidden;position:relative}
.ph-cap{position:absolute;inset:auto 0 0 0;padding:4px 6px;font-size:var(--t-micro);font-weight:var(--w-bold);
  letter-spacing:var(--ls-caps);text-align:left;color:#fff;
  background:linear-gradient(transparent,rgba(0,0,0,.7))}

/* ===================== SMART DISPATCH ===================== */
.dispatch-h{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  padding:var(--s3) var(--s4);background:var(--surface-2);border-bottom:1px solid var(--rule);flex-wrap:wrap}
.dispatch-ic{width:34px;height:34px;border-radius:var(--r1);border:1px solid var(--rule-strong);
  display:grid;place-items:center;color:var(--ink-2);flex:none}
.dispatch-b{padding:var(--s5)}
.dispatch-n{width:20px;height:20px;border:1px solid var(--rule-strong);border-radius:var(--r1);
  display:grid;place-items:center;font-size:var(--t-micro);font-weight:var(--w-bold);font-family:var(--font-head);
  color:var(--ink-2);flex:none}

/* current against recommended */
.cmp{display:grid;grid-template-columns:repeat(3,1fr);border:1px solid var(--rule);border-radius:var(--r2)}
.cmp-c{padding:var(--s4);border-right:1px solid var(--rule)}
.cmp-c:last-child{border-right:0}
.cmp-c.best{background:var(--ok-bg)}
.cmp-v{font-size:var(--t-page);margin:4px 0 2px}

/* the day, hour by hour */
.tline-d{margin-top:var(--s4);border:1px solid var(--rule);border-radius:var(--r2);overflow:hidden}
.tl-row{display:flex;align-items:flex-start;gap:12px;padding:12px var(--s4);background:var(--surface)}
.tl-row.head{background:var(--surface-2)}
.tl-t{font-family:var(--font-head);font-size:var(--t-meta);font-weight:var(--w-semi);width:44px;flex:none;
  font-variant-numeric:tabular-nums;padding-top:1px}
.tl-dot{width:9px;height:9px;flex:none;margin-top:4px;background:var(--navy-700);border-radius:var(--rp)}
.tl-dot.yard{background:var(--ink-3);border-radius:0}
.tl-dot.late{background:var(--risk)}
html[data-theme="dark"] .tl-dot{background:var(--sky-400)}
.tl-leg{display:flex;align-items:center;gap:12px;padding:0 var(--s4) 0 calc(var(--s4) + 44px + 12px)}
.tl-line{width:1px;height:22px;background:var(--rule-strong);flex:none;margin-left:4px}
.tl-foot{display:flex;flex-wrap:wrap;gap:var(--s4);padding:12px var(--s4);background:var(--surface-2);
  border-top:1px solid var(--rule);font-size:var(--t-small);font-weight:var(--w-semi);color:var(--ink-2)}
.tl-foot span{white-space:nowrap}
@media (max-width:720px){
  .cmp{grid-template-columns:1fr}
  .cmp-c{border-right:0;border-bottom:1px solid var(--rule)}
  .cmp-c:last-child{border-bottom:0}
  .tl-leg{padding-left:calc(var(--s4) + 44px)}
  .tl-foot{gap:var(--s3);font-size:var(--t-tiny)}
}
.contentflag{display:inline-flex;align-items:center;gap:4px;font-family:var(--font-head);
  text-transform:uppercase;letter-spacing:var(--ls-caps);font-size:var(--t-micro);font-weight:var(--w-semi);color:var(--sky-700);
  background:var(--info-bg);border:1px solid var(--info-line);border-radius:var(--r1);
  padding:2px 6px;margin-bottom:6px}
html[data-theme="dark"] .contentflag{color:var(--sky-400)}
.daychip.content{border-top-color:var(--sky-500)}
.dimday{opacity:.55}
.dimday:hover{opacity:1}

/* ============================================================
   EMPLOYEE PORTAL — the four-digit way in
   ============================================================ */
.pinwrap{
  min-height:100dvh; display:flex; flex-direction:column;
  background:var(--navy-800); color:#fff;
}
.pinhead{
  padding:calc(var(--s7) + var(--sat)) var(--s5) var(--s6);
  text-align:center;
}
.pinhead .logo{justify-content:center}
/* short phones: the sign-in logo steps down so the keypad still fits */
@media (max-height:700px){ .pinhead .logo.lg svg.mk{width:44px;height:44px} .pinhead .logo.lg svg.wm-tag{height:34px;margin-top:12px} }
.pinbody{
  flex:1; display:flex; flex-direction:column; align-items:center;
  background:var(--surface); color:var(--text);
  border-radius:var(--r5) var(--r5) 0 0;
  padding:var(--s6) var(--s5) calc(var(--s6) + var(--sab));
}
.pindots{display:flex; gap:20px; margin-bottom:var(--s4)}
.pindot{
  width:16px; height:16px; border-radius:50%;
  border:2px solid var(--border-strong); background:transparent;
  transition:transform .12s var(--ease), background .12s var(--ease);
}
.pindot.on{background:var(--brand); border-color:var(--brand); transform:scale(1.15)}
.pindots.shake{animation:pinshake .34s}
@keyframes pinshake{
  0%,100%{transform:translateX(0)} 20%{transform:translateX(-9px)}
  40%{transform:translateX(9px)} 60%{transform:translateX(-6px)} 80%{transform:translateX(6px)}
}
@media (prefers-reduced-motion:reduce){.pindots.shake{animation:none}}
.pinmsg{
  min-height:38px; font-size:var(--t-meta); text-align:center; color:var(--text-2);
  max-width:30ch; line-height:var(--lh-ui); margin-bottom:var(--s3);
}
.pinmsg.bad{color:var(--risk); font-weight:var(--w-semi)}
.pinpad{
  display:grid; grid-template-columns:repeat(3,1fr); gap:16px;
  width:100%; max-width:330px;
}
.pinkey{
  height:74px; border-radius:var(--r3);
  background:var(--surface-2); border:1px solid var(--border);
  font-family:var(--font-head); font-size:var(--t-figure); font-weight:var(--w-med); color:var(--text);
  display:grid; place-items:center; cursor:pointer;
  transition:transform .08s var(--ease), background .08s var(--ease);
  -webkit-tap-highlight-color:transparent;
}
.pinkey:active{transform:scale(.95); background:var(--surface-3)}
.pinkey.ghost{background:transparent; border-color:transparent; color:var(--text-3)}
.pinfoot{margin-top:var(--s6); text-align:center}
@media (min-height:760px){ .pinkey{height:80px} }
@media (max-height:640px){ .pinkey{height:60px; font-size:var(--t-page)} .pinhead{padding-top:var(--s5)} }

/* the portal shell reuses the mobile chrome, with a badge for actions */
.dotbadge{
  position:absolute; top:-5px; right:-9px; min-width:17px; height:17px;
  border-radius:var(--rp); background:var(--risk); color:#fff;
  font-size:var(--t-micro); font-weight:var(--w-bold); font-style:normal;
  display:grid; place-items:center; padding:0 4px; line-height:var(--lh-solid);
}
.mshell.portal .page{padding-bottom:calc(84px + var(--sab))}
.av.lg{width:56px; height:56px; font-size:var(--t-title); flex:none}

/* a job card being dragged to another day */
.jcard[draggable="true"]{cursor:grab}
.jcard.dragging{opacity:.45; cursor:grabbing}
.daycol.over .jcard{pointer-events:none}


/* ============================================================
   ROUTE COMMAND VIEW

   The map is the screen. The run is on the left because that is
   what gets read down; the levers are on the right because they
   are what gets changed; the summary sits under the lot because
   it is the answer, not the input.
   ============================================================ */
.cmdbar{display:flex;align-items:center;justify-content:space-between;gap:var(--s4);
  flex-wrap:wrap;margin-bottom:var(--s4)}
.lorrytabs{display:flex;gap:var(--s2);flex-wrap:wrap}
.lorrytab{display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 16px;
  border-radius:var(--r3);border:1px solid var(--rule);background:var(--surface);
  color:var(--ink-2);font-weight:var(--w-semi);font-size:var(--t-meta);
  transition:background var(--dur),color var(--dur),border-color var(--dur),box-shadow var(--dur)}
.lorrytab:hover{color:var(--ink);border-color:var(--rule-strong);background:var(--surface-2)}
.lorrytab.on{color:#fff;background:linear-gradient(180deg,var(--int-hi),var(--int));
  border-color:rgba(255,255,255,.18);box-shadow:0 3px 16px var(--int-glow),var(--lift-2)}
.lt-n{font-style:normal;font-family:var(--font-head);font-size:var(--t-tiny);padding:1px 6px;border-radius:var(--rp);
  background:rgba(255,255,255,.1);color:inherit;min-width:20px;text-align:center}
.lorrytab.on .lt-n{background:rgba(255,255,255,.22)}

.cmdview{display:grid;grid-template-columns:300px minmax(0,1fr) 176px;gap:var(--s3);
  align-items:stretch}
.cmdpanel,.cmdmap,.cmdrail{border:1px solid var(--rule);border-radius:var(--r4);
  background:var(--surface);background-image:var(--pane);box-shadow:var(--lift),var(--sh1);
  overflow:hidden}
.cmdpanel{display:flex;flex-direction:column;min-height:480px}
.cmdpanel-h{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);
  padding:var(--s3) var(--s4);border-bottom:1px solid var(--rule);background:var(--surface-2)}
.cmdcrew{padding:var(--s4);border-bottom:1px solid var(--rule)}
.cmdstops{flex:1;overflow-y:auto;padding:var(--s2)}
.cmdstop{display:flex;align-items:center;gap:12px;width:100%;padding:12px;border-radius:var(--r2);
  border-left:var(--bar) solid transparent;transition:background var(--dur),border-color var(--dur)}
.cmdstop:hover{background:var(--surface-2)}
.cmdstop.ok{border-left-color:var(--ok)}
.cmdstop.warn{border-left-color:var(--gold)}
.cmdstop.risk{border-left-color:var(--risk)}
.cs-n{width:22px;height:22px;border-radius:var(--rp);flex:none;display:grid;place-items:center;
  font-family:var(--font-head);font-size:var(--t-tiny);font-weight:var(--w-semi);
  background:var(--surface-3);color:var(--ink-2);border:1px solid var(--rule-strong)}
.cmdstop.ok .cs-n{background:var(--int);color:#fff;border-color:transparent}
.cs-t{font-size:var(--t-small);font-weight:var(--w-bold);color:var(--ink-2);flex:none}
.cmdadd{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;
  padding:12px;border-top:1px solid var(--rule);color:var(--ink-2);font-size:var(--t-small);font-weight:var(--w-semi);
  transition:background var(--dur),color var(--dur)}
.cmdadd:hover{background:var(--surface-2);color:var(--ink)}

/* The map column is a scroll container, not a flex row. Making it
   a row stretched the legend swatches to the height of the panel —
   the key ended up bigger than the map it explained. */
.cmdmap{position:relative;min-height:480px;display:block;overflow-y:auto;padding:var(--s3)}
.cmdmap .rmap{border:0;background:transparent;border-radius:var(--r3)}
.cmdmap .rmap-key{display:flex;flex-wrap:wrap;gap:var(--s2) var(--s4);
  font-size:var(--t-tiny);color:var(--ink-3);margin-top:var(--s3)}
.cmdmap .rmap-key span{display:inline-flex;align-items:center;gap:6px}
.cmdmap .rmap-key svg{flex:none;width:13px;height:13px}
/* the honesty notes belong here but not at body size */
.cmdmap .lede.tiny{font-size:var(--t-tiny);line-height:var(--lh-ui);color:var(--ink-3);max-width:72ch}

.cmdrail{display:flex;flex-direction:column}
.lever{padding:var(--s3) var(--s4);border-bottom:1px solid var(--rule);flex:1;
  display:flex;flex-direction:column;justify-content:center;gap:2px}
.lever:last-child{border-bottom:0}
.lever-v{font-size:var(--t-title);display:block}
.lever.ok .lever-v{color:var(--ok)}
.lever.warn .lever-v{color:var(--gold)}
.lever.risk .lever-v{color:var(--risk)}

.cmdsum{display:grid;grid-template-columns:repeat(6,minmax(0,1fr)) auto;gap:0;margin-top:var(--s3);
  border:1px solid var(--rule);border-radius:var(--r4);overflow:hidden;
  background:var(--surface);background-image:var(--pane);box-shadow:var(--lift),var(--sh1)}
.cmdsum-c{padding:var(--s3) var(--s4);border-right:1px solid var(--rule);min-width:0}
.cmdsum-v{display:block;font-size:var(--t-page);margin-top:4px}
.cmdsum-a{padding:var(--s3) var(--s4);display:flex;align-items:center}

@media (max-width:1180px){
  .cmdview{grid-template-columns:280px minmax(0,1fr)}
  .cmdrail{grid-column:1 / -1;flex-direction:row}
  .lever{border-bottom:0;border-right:1px solid var(--rule)}
  .lever:last-child{border-right:0}
  .cmdsum{grid-template-columns:repeat(3,minmax(0,1fr))}
  .cmdsum-c:nth-child(3n){border-right:0}
  .cmdsum-a{grid-column:1 / -1;border-top:1px solid var(--rule)}
}
@media (max-width:820px){
  /* on a phone the map still leads, then the run, then the numbers */
  .cmdview{grid-template-columns:minmax(0,1fr)}
  .cmdmap{order:-1;min-height:280px}
  .cmdpanel{min-height:0}
  .cmdrail{flex-wrap:wrap}
  .lever{flex:1 1 44%;border-bottom:1px solid var(--rule)}
  .cmdsum{grid-template-columns:repeat(2,minmax(0,1fr))}
  .cmdsum-c:nth-child(2n){border-right:0}
  .lorrytabs{width:100%;overflow-x:auto;flex-wrap:nowrap;scrollbar-width:none}
  .lorrytabs::-webkit-scrollbar{display:none}
  .lorrytab{flex:none}
}

/* ---------- the overall rating: the one figure that counts ---------- */
.ratebox{padding:var(--s4) var(--s5);border-radius:var(--r3);border:1px solid var(--rule-strong);
  background:var(--surface-2);background-image:var(--pane);box-shadow:var(--lift);
  border-left:var(--bar) solid var(--int)}
.rateval{display:flex;align-items:baseline;gap:6px;flex:none}
.rateval .fig{font-size:var(--t-hero);color:var(--int-hi)}
html[data-theme="light"] .rateval .fig{color:var(--navy-700)}
.rateval span{font-size:var(--t-small);color:var(--ink-3)}
.rateslider{width:100%;margin-top:var(--s3)}

/* ---------- the master-code shell ----------
   Deliberately bare. Holding the master code gets you one screen,
   and it should be obvious from the chrome that it is not the
   application. */
.mastershell{min-height:100dvh;background:var(--paper)}
.masterbar{display:flex;align-items:center;gap:var(--s3);padding:var(--s3) var(--s5);
  background:var(--chrome);color:#fff;border-bottom:2px solid var(--gold);position:sticky;top:0;z-index:20}
.mastershell .page{max-width:1240px;margin:0 auto;padding:var(--s5) var(--s5) var(--s8)}

/* ---------- typing the code instead of tapping it ---------- */
.pinentry{width:100%;max-width:220px;margin:var(--s4) auto 0;display:block;text-align:center;
  height:44px;border-radius:var(--r2);border:1px solid var(--rule-strong);background:var(--surface-2);
  color:var(--ink);font-family:var(--font-head);font-size:var(--t-page);letter-spacing:.5em;text-indent:.5em;
  transition:border-color var(--dur),box-shadow var(--dur)}
.pinentry:focus{outline:0;border-color:var(--int-hi);box-shadow:0 0 0 3px var(--int-glow)}
.pintype{display:block;text-align:center;margin-top:6px;font-size:var(--t-tiny);color:var(--ink-3);cursor:pointer}
@media (max-width:720px){
  /* on a phone the pad is the point — the field is there for a
     bluetooth keyboard, not for thumbs */
  .pinentry{max-width:180px;height:40px;font-size:var(--t-title)}
}

/* a state chip that is also a way in — it has to look pressable
   without becoming a button in a table full of them */
.pill-go{cursor:pointer;transition:filter var(--dur),box-shadow var(--dur)}
.pill-go:hover{filter:brightness(1.18);box-shadow:0 0 0 2px var(--int-glow)}
.pill-go::after{content:"\203A";margin-left:2px;opacity:.65;font-weight:var(--w-bold)}

/* ============================================================
   THE LORRY BOARD
   A run drawn as a line from the yard to the yard, with a marker
   at every drop and a flag at the end. The filled part is the
   planned day elapsed — deliberately a different thing from the
   markers, which only fill when something has actually been logged.
   ============================================================ */
.lorries{display:grid;grid-template-columns:repeat(auto-fit,minmax(310px,1fr));gap:var(--s4)}
.lorry{display:block;width:100%;text-align:left;padding:var(--s4);border-radius:var(--r3);
  border:1px solid var(--rule);border-left:var(--bar) solid var(--rule-strong);
  background:var(--surface);background-image:var(--pane);box-shadow:var(--lift),var(--sh1);
  transition:transform var(--dur) var(--ease),box-shadow var(--dur),border-color var(--dur)}
.lorry:hover{transform:translateY(-2px);box-shadow:var(--lift-2),var(--sh2);border-color:var(--rule-strong)}
.lorry:active{transform:translateY(0)}
.lorry.onplan{border-left-color:var(--ok)}
.lorry.behind{border-left-color:var(--risk)}
.lorry.finished{border-left-color:var(--ok);opacity:.82}
.lorry.notyet,.lorry.quiet{border-left-color:var(--rule-strong)}
.lorry-ic{width:32px;height:32px;border-radius:var(--r2);flex:none;display:grid;place-items:center;
  background:var(--surface-3);color:var(--ink-2)}
.lorry.onplan .lorry-ic{color:var(--ok)}
.lorry.behind .lorry-ic{color:var(--risk)}

.lorryline{position:relative;height:30px;margin-top:var(--s4)}
.ll-track{position:absolute;left:0;right:0;top:13px;height:4px;border-radius:var(--rp);background:var(--surface-3)}
.ll-fill{position:absolute;left:0;top:13px;height:4px;border-radius:var(--rp);
  background:linear-gradient(90deg,var(--int),var(--int-hi));max-width:100%}
.lorry.behind .ll-fill{background:linear-gradient(90deg,var(--risk),var(--gold))}
.lorry.finished .ll-fill{background:var(--ok)}
/* where the clock is now */
.ll-now{position:absolute;top:6px;width:2px;height:18px;border-radius:var(--rp);background:var(--ink);
  transform:translateX(-1px);opacity:.55}
.ll-stop{position:absolute;top:6px;width:19px;height:19px;transform:translateX(-9.5px);
  border-radius:var(--rp);background:var(--surface);border:1.5px solid var(--rule-strong);
  display:grid;place-items:center}
.ll-stop i{font-style:normal;font-family:var(--font-head);font-size:var(--t-micro);font-weight:var(--w-semi);color:var(--ink-3)}
.ll-stop.due{border-color:var(--gold)}
.ll-stop.due i{color:var(--gold)}
.ll-stop.done{background:var(--ok);border-color:var(--ok)}
.ll-stop.done i{color:#04150F}
.ll-flag{position:absolute;right:-2px;top:5px;color:var(--ink-3);display:flex}
.lorry.finished .ll-flag{color:var(--ok)}
.lorryfoot{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  margin-top:var(--s3);padding-top:var(--s3);border-top:1px solid var(--rule)}
@media (max-width:600px){
  .lorries{grid-template-columns:minmax(0,1fr)}
}

/* ============================================================
   WORK READY — proof it, then send it
   The form on the left, the document as it will actually read on
   the right. Not a preview of a preview: the same markup that
   prints.
   ============================================================ */
.wrwrap{display:grid;grid-template-columns:minmax(0,340px) minmax(0,1fr);gap:var(--s5);align-items:start}
.wrform{min-width:0}
.wrpaper{min-width:0;max-height:62vh;overflow-y:auto;border:1px solid var(--rule-strong);
  border-radius:var(--r3);background:#fff;color:#111}
.wrpage{padding:28px 32px}
.wrhead{text-align:right;font-size:var(--t-tiny);color:#555;border-bottom:1px solid #ddd;padding-bottom:6px;
  letter-spacing:var(--ls-caps)}
.wrtitle{font-size:var(--t-input);margin:20px 0 12px;color:#111;font-weight:var(--w-bold);font-family:var(--font)}
.wrmeta div{font-size:var(--t-tiny);margin-bottom:2px;color:#333;line-height:var(--lh-ui)}
.wrh{font-size:var(--t-small);margin:20px 0 6px;color:#111;font-weight:var(--w-bold);font-family:var(--font)}
.wrp{font-size:var(--t-small);line-height:var(--lh-ui);color:#333}
.wrp.muted{color:#888}
.wrphotos{display:block}
.wrphoto{margin:0 0 16px}
.wrphoto img{width:100%;display:block;border:1px solid #ddd}
.wrphoto figcaption{font-size:var(--t-micro);color:#666;margin-top:4px;text-align:center}
@media (max-width:900px){
  .wrwrap{grid-template-columns:minmax(0,1fr)}
  .wrpaper{max-height:48vh}
}

/* profit or loss on a job — a number with a sign, not a band */
.money{font-family:var(--font-head);font-weight:var(--w-semi);font-size:var(--t-body);
  font-variant-numeric:tabular-nums;letter-spacing:var(--ls-fig)}
.money.up{color:var(--ok)}
.money.down{color:var(--risk)}

/* the tag number tells you whether the job under it is earning */
.dsp.tag-ok{color:var(--ok)}
.dsp.tag-off{color:var(--risk)}

/* ============================================================
   THE YARD SCREEN

   Bigger than anything else in the system on purpose. It is read
   standing up, in a coat, often in the dark, and frequently by
   somebody reading their second or third language. Numbers are
   large, targets are large, and there is very little to read.
   ============================================================ */
.yardhead{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--s4);
  flex-wrap:wrap;padding-bottom:var(--s4);margin-bottom:var(--s4);position:relative}
.yardhead::after{content:"";position:absolute;left:0;bottom:0;width:190px;max-width:52%;height:2px;
  background:linear-gradient(90deg,var(--gold),rgba(232,176,75,0))}
.yardbig{text-align:right;flex:none}
.yardbig .fig{font-size:var(--t-hero-lg);display:block;line-height:var(--lh-solid);color:var(--int-hi)}
.yardbig span{font-size:var(--t-small);color:var(--ink-3);text-transform:uppercase;letter-spacing:var(--ls-caps);
  font-family:var(--font-head)}

/* one thing to do, one big row */
.yardtask{display:flex;align-items:center;gap:var(--s4);width:100%;text-align:left;
  padding:var(--s4) var(--s5);margin-bottom:var(--s3);border-radius:var(--r3);
  border:1px solid var(--rule-strong);border-left:var(--bar) solid var(--gold);
  background:var(--surface-2);color:var(--ink);min-height:76px;
  transition:transform var(--dur) var(--ease),box-shadow var(--dur)}
.yardtask:hover{transform:translateY(-2px);box-shadow:var(--sh2)}
.yardtask b{display:block;font-size:var(--t-lead);font-weight:var(--w-semi)}
.yardtask span span{display:block;font-size:var(--t-meta);color:var(--ink-2);margin-top:2px}
.yt-ic{width:48px;height:48px;border-radius:var(--r3);flex:none;display:grid;place-items:center;
  background:var(--warn-bg);color:var(--gold)}

.yardloads{display:grid;grid-template-columns:repeat(auto-fit,minmax(330px,1fr));gap:var(--s4)}
.yardload{border:1px solid var(--rule);border-radius:var(--r4);overflow:hidden;
  background:var(--surface);background-image:var(--pane);box-shadow:var(--lift),var(--sh1);
  display:flex;flex-direction:column}
.yardload.done{opacity:.72;border-color:var(--ok-line)}
.yl-head{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  padding:var(--s4) var(--s5);background:var(--chrome);color:#fff}
.yl-name{display:flex;align-items:center;gap:12px;min-width:0}
.yl-name b{font-family:var(--font-head);font-size:var(--t-title);letter-spacing:var(--ls-2)}
.yl-name .tiny{color:rgba(255,255,255,.6)}
.yl-jobs{padding:var(--s4) var(--s5);border-bottom:1px solid var(--rule)}
.yl-job{display:flex;align-items:flex-start;gap:12px;padding:6px 0}
.yl-job b{font-size:var(--t-body);display:block}
.yl-n{width:26px;height:26px;border-radius:var(--rp);flex:none;display:grid;place-items:center;
  font-family:var(--font-head);font-size:var(--t-meta);font-weight:var(--w-semi);
  background:var(--int);color:#fff}
.yl-kit{padding:var(--s4) var(--s5);flex:1}
.kitgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(96px,1fr));gap:var(--s2);
  margin-top:var(--s3)}
.kit{padding:var(--s3);border-radius:var(--r2);background:var(--surface-2);
  border:1px solid var(--rule);text-align:center}
.kit .fig{font-size:var(--t-page);display:block;line-height:var(--lh-solid)}
.kit span{display:block;font-size:var(--t-tiny);color:var(--ink-2);margin-top:4px;line-height:var(--lh-heading)}
.kit.short{border-color:var(--risk);background:var(--risk-bg)}
.kit.short .fig{color:var(--risk)}
.yl-short{display:flex;align-items:center;gap:8px;margin-top:var(--s3);padding:12px 12px;
  border-radius:var(--r2);background:var(--risk-bg);color:var(--risk);font-size:var(--t-meta)}
.yardload .btn.block{border-radius:0;height:56px;font-size:var(--t-input)}
@media (max-width:600px){
  .yardloads{grid-template-columns:minmax(0,1fr)}
  .kitgrid{grid-template-columns:repeat(3,minmax(0,1fr))}
}

/* ===================== CLIENT PORTAL TABS ===================== */
/* The portal was one long scroll. On a phone that put a client's
   own approvals above the invoice they came for, and a finished
   job below the fold of a list that excluded it. Five tabs, in
   the chrome, so the way in is the same on every screen size. */
.portal-tabs{display:flex;gap:2px;overflow-x:auto;scrollbar-width:none;
  margin-top:var(--s4);padding-bottom:1px}
.portal-tabs::-webkit-scrollbar{display:none}
.ptab{display:inline-flex;align-items:center;gap:6px;flex:none;
  padding:8px 16px;border:0;background:transparent;cursor:pointer;
  color:rgba(255,255,255,.62);font:inherit;font-size:var(--t-meta);font-weight:var(--w-semi);
  border-bottom:2px solid transparent;white-space:nowrap;
  transition:color .14s ease,border-color .14s ease}
.ptab:hover{color:rgba(255,255,255,.9)}
.ptab.on{color:#fff;border-bottom-color:var(--sky-400)}
.ptab .ic{opacity:.8}
.ptab-n{display:inline-grid;place-items:center;min-width:18px;height:18px;padding:0 4px;
  border-radius:var(--r-pill);background:rgba(255,255,255,.2);color:#fff;font-size:var(--t-tiny);font-weight:var(--w-bold)}
.ptab-n.warn{background:var(--warn);color:#1a1204}
/* ON A PHONE THE LAST TWO TABS WERE NOWHERE. Five tabs are 560px wide
   and a phone is 390, so Invoices and Messages sat past the edge of a
   strip with its scrollbar hidden and nothing else to say there was
   more — and Documents ended exactly at the edge, so it looked like the
   whole set. Phone sweep, 10 Oct 2026. The strip now behaves like the
   app's other phone tab strips (.tabs, above): it scrolls with a snap,
   the tabs are narrower so the next one always shows part of itself,
   and the right edge fades while there is more — the fade goes once
   you reach the end (.more / .at-end, set by scrollHints() as it is
   for a wide table). Every tab is a full 44px to press. */
@media (max-width:720px){
  .portal-tabs{scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch}
  .portal-tabs.more{
    -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 36px),transparent 100%);
    mask-image:linear-gradient(to right,#000 calc(100% - 36px),transparent 100%)}
  .portal-tabs.more.at-end{-webkit-mask-image:none;mask-image:none}
  .ptab{min-height:44px;padding:12px 12px;gap:6px;scroll-snap-align:start}
}

/* ===================== PAPER ===================== */
/* A document rendered inside the app, so that what a client reads
   on screen and what comes out of the printer are the same
   layout. The print window carries its own copy of these rules
   (PAPER_PRINT_CSS) because a print window that inherits app
   tokens prints a screenshot of an application. */
.paper{background:var(--surface);padding:var(--s5);border-radius:var(--r2)}
.paper-head{display:flex;justify-content:space-between;align-items:flex-start;
  gap:var(--s5);border-bottom:2px solid var(--text);padding-bottom:12px}
.paper-co{font-size:var(--t-lead);font-weight:700;letter-spacing:-.01em}
.paper .r{text-align:right}
.sigbox{border-bottom:1px solid var(--text);height:58px;display:flex;align-items:flex-end}
.sigbox img{max-height:56px;max-width:100%}

/* ===================== A WAY INTO THE DEMONSTRATION ===================== */
/* Sits under the keypad on the dark sign-in screen. Folded shut by
   default: a person who has a code should see a keypad, not a menu
   of other people to be. */
.demoway{margin-top:var(--s5);width:100%;max-width:340px}
.demoway-t{width:100%;display:flex;align-items:center;gap:8px;
  padding:12px 16px;border-radius:var(--r1);cursor:pointer;font:inherit;font-size:var(--t-meta);
  background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.14);color:rgba(255,255,255,.82);
  transition:background .14s ease}
.demoway-t:hover{background:rgba(255,255,255,.12)}
.demoway-t span:first-of-type{flex:1;text-align:left}
.demoway-c{font-size:var(--t-input);opacity:.6;flex:none}
.demoway-b{margin-top:12px;padding:16px;border-radius:var(--r1);
  background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.1);color:#fff}
.demoway-g{display:grid;gap:8px}
.demoway-p{display:grid;grid-template-columns:auto 1fr auto;gap:2px 12px;align-items:center;
  width:100%;text-align:left;padding:12px 12px;border-radius:var(--r1);cursor:pointer;font:inherit;
  background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);color:#fff;
  transition:background .14s ease,border-color .14s ease}
.demoway-p:hover{background:rgba(255,255,255,.14);border-color:rgba(255,255,255,.28)}
.demoway-p b{font-size:var(--t-meta)}
.demoway-r{font-size:var(--t-tiny);letter-spacing:var(--ls-caps);text-transform:uppercase;opacity:.62;justify-self:start}
.demoway-n{grid-row:1/3;font-size:var(--t-body);letter-spacing:var(--ls-caps);opacity:.75;font-variant-numeric:tabular-nums}
.demoway-w{grid-column:1/3;font-size:var(--t-tiny);opacity:.6;line-height:var(--lh-ui)}
.demoway .lnk{color:var(--sky-400)}

/* A group's clear rows, folded. Openable, because "everything is
   fine" is a claim somebody is entitled to check. */
.ovclear{border:1px solid var(--border);border-radius:var(--r1);
  background:var(--surface-2);padding:8px 12px}
.ovclear>summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:6px;
  font-size:var(--t-small);color:var(--text-2)}
.ovclear>summary::-webkit-details-marker{display:none}
.ovclear>summary:hover{color:var(--text)}
.ovclear>summary .ic{color:var(--ok);flex:none}
.ovclear[open]>summary{margin-bottom:2px}

/* ===================== CONTEXTUAL HELP ===================== */
/* A small ⓘ beside a heading or a label, and the panel it opens.
   Sized to sit inside a line of text without changing its rhythm:
   it inherits the line's colour at a lower opacity, so it reads as
   part of the label rather than as a control competing with it. */
.ihelp{display:inline-flex;align-items:center;justify-content:center;
  width:17px;height:17px;padding:0;margin-left:4px;vertical-align:-3px;
  border:0;border-radius:50%;background:transparent;cursor:pointer;
  color:inherit;opacity:.42;flex:none;
  transition:opacity .13s ease,background .13s ease}
.ihelp:hover,.ihelp[aria-expanded="true"]{opacity:1;background:var(--surface-3)}
.ihelp:focus-visible{opacity:1;outline:2px solid var(--sky-500);outline-offset:1px}
.ihelp .ic{width:14px;height:14px;stroke-width:1.9}
/* Inside a table heading the label is already small and uppercase,
   so the icon shrinks with it rather than sitting proud of it. */
th .ihelp{width:15px;height:15px;margin-left:4px;vertical-align:-2px}
th .ihelp .ic{width:12px;height:12px}
/* A help BUTTON is never allowed inside a control — that is invalid
   markup and it would be a second thing to press where the person
   expected one. The tile MARK is the sanctioned exception: it is a
   span, it is aria-hidden, and the tile's own handler routes the
   press. Excluding it here is what makes tiles work at all — the
   first version of this rule hid every one of them. */
button .ihelp:not(.ihelp-mark),a .ihelp:not(.ihelp-mark){display:none}
@media print{.ihelp,.helppop{display:none!important}}

/* The panel. z-index 102 clears the sheet at 101, so help works
   INSIDE a modal — which is where most of it is needed — and stays
   under the toasts at 200. */
.helppop{position:fixed;z-index:102;width:min(340px,calc(100vw - 20px));
  background:var(--surface);border:1px solid var(--rule-strong);
  border-radius:var(--r2);box-shadow:0 18px 44px rgba(0,0,0,.28),0 2px 8px rgba(0,0,0,.14);
  padding:16px 16px 12px;color:var(--ink);
  animation:helpin .13s cubic-bezier(.2,.7,.35,1)}
@keyframes helpin{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.helppop{animation:none}}
.helppop:focus{outline:none}
.helppop-head{display:flex;align-items:flex-start;gap:12px;margin-bottom:6px}
.helppop-head b{flex:1;font-size:var(--t-meta);letter-spacing:var(--ls-0);line-height:var(--lh-heading)}
.helppop-head .icobtn{flex:none;width:24px;height:24px;margin:-2px -4px 0 0}
.helppop p{margin:0 0 8px;font-size:var(--t-meta);line-height:var(--lh-ui);color:var(--ink-2)}
.helppop p:last-child{margin-bottom:0}
.helppop-note{padding-top:8px;border-top:1px solid var(--rule);color:var(--ink-3)}

/* On a phone it stops pretending to be anchored to a 14px target
   and sits along the bottom, where a thumb is. */
.helppop.sheet-ish{left:8px;right:8px;bottom:calc(8px + var(--sab,0px));
  top:auto;width:auto;max-height:62vh;overflow:auto;
  border-radius:var(--r2);padding:16px 16px 16px;
  box-shadow:0 -12px 40px rgba(0,0,0,.3)}
.helppop.sheet-ish .helppop-head b{font-size:var(--t-body)}
.helppop.sheet-ish p{font-size:var(--t-meta)}

/* The mark inside a tile: same size and weight as the button
   version, but it is a span, so it needs the pointer explicitly. */
.ihelp-mark{cursor:pointer}
.tile .k{display:inline-flex;align-items:center;gap:4px}

/* A 17px target is fine for a mouse and too small for a thumb, and
   the app's own rule is 44px. The icon stays 17px to the eye; on a
   touch screen the PRESSABLE area grows to 44 and the extra is
   cancelled by negative margins, so nothing around it moves. */
@media (pointer:coarse){
  .ihelp{width:44px;height:44px;margin:-12px -12px -12px -8px}
  th .ihelp{width:44px;height:44px;margin:-16px -16px -16px -8px}
}
/* And on any device the mouse target is a little larger than it
   looks — the icon is small on purpose, the hit box need not be. */
.ihelp{position:relative}
.ihelp::before{content:'';position:absolute;inset:-7px;border-radius:50%}

/* ============================================================
   THE FIRST SCREEN SOMEBODY EVER SEES

   Typographic, not a stack of cards. This is the one place in the
   app that is being read rather than worked, so it gets a measure,
   a rule and some air — the same discipline as the handover
   certificate, for the same reason.
   ============================================================ */
.frwrap{min-height:100dvh;display:flex;align-items:center;justify-content:center;
  padding:var(--s6);background:var(--bg)}
.frinner{width:100%;max-width:620px}
.freyebrow{margin-bottom:var(--s6);opacity:.9}
.frtitle{font-family:var(--font-head);font-size:var(--t-figure);line-height:var(--lh-display);
  letter-spacing:var(--ls-6);margin:0 0 var(--s4)}
.frlede{font-size:var(--t-body);line-height:var(--lh-prose);color:var(--ink-2);margin:0;max-width:56ch}
.frrule{height:1px;background:var(--rule);margin:var(--s6) 0 var(--s5)}
.frwho{font-size:var(--t-meta);line-height:var(--lh-ui);color:var(--ink-2);margin:0 0 var(--s5)}
.frwho b{color:var(--ink)}
.frlist{list-style:none;margin:0;padding:0;display:grid;gap:var(--s5)}
.frlist li{display:flex;gap:16px;align-items:flex-start}
.frn{flex:none;width:24px;height:24px;border-radius:var(--rp);border:1px solid var(--rule);
  display:grid;place-items:center;font-family:var(--font-head);font-size:var(--t-small);
  color:var(--ink-2);margin-top:1px}
/* The heading only. `.frlist b` caught every bold inside the
   paragraphs too, so "Open <b>Jobs</b>, pick one" came out as three
   lines with the comma stranded on its own. */
.frlist > li > div > b{display:block;font-size:var(--t-body);margin-bottom:2px}
.frlist p b{color:var(--ink);font-weight:var(--w-semi)}
.frlist p{margin:0;font-size:var(--t-meta);line-height:var(--lh-ui);color:var(--ink-2);max-width:52ch}
.frfoot{display:flex;gap:12px;flex-wrap:wrap;margin-top:var(--s6)}
.frfoot .btn{min-height:44px}
.frtiny{margin:var(--s4) 0 0;font-size:var(--t-small);color:var(--ink-3);opacity:.85}

@media (max-width:720px){
  .frwrap{align-items:flex-start;padding:var(--s5) var(--s4) var(--s7)}
  .frtitle{font-size:var(--t-page)}
  .frlede{font-size:var(--t-body)}
  /* Both buttons full width and stacked: on a phone a row of two
     puts the one you want under your thumb only half the time. */
  .frfoot{flex-direction:column;gap:8px}
  .frfoot .btn{width:100%;justify-content:center}
}

/* The photograph counter on a field button: one pip per photograph
   the press needs, filled as they are taken. Small, quiet, and it
   answers "how many more" without reading a sentence — which is the
   question somebody asks with their gloves on. */
.fphotos{display:inline-flex;gap:4px;align-items:center;margin-right:8px}
.fphotos i{width:7px;height:7px;border-radius:var(--rp);background:currentColor;opacity:.3}
.fphotos i.on{opacity:1}

/* ============================================================
   THE ROTA SHEET

   Seven boxes. It is the sheet of A4 on White's office wall and it
   is meant to look like it — a grid, a tick or a cross, big enough
   to hit with a thumb in a glove.

   Sized in fr rather than fixed columns so seven of them fit a
   320px phone and spread on a tablet without a second layout, and
   with a real 52px minimum height because this is the control the
   whole feature is: if it is fiddly, nobody fills it in and Dicko
   is back on the telephone.
   ============================================================ */
.rotarow{
  display:grid; grid-template-columns:repeat(7,1fr);
  gap:4px;
}
.rotabox{
  display:flex; flex-direction:column; align-items:center; gap:2px;
  min-height:56px; padding:6px 2px;
  border:1px solid var(--border); border-radius:var(--r2);
  background:var(--surface-2); color:var(--text-2);
  cursor:pointer; position:relative;
  font:inherit; touch-action:manipulation;
  transition:background .12s ease, border-color .12s ease, color .12s ease;
}
.rotabox .rb-d{font-size:var(--t-micro); letter-spacing:var(--ls-caps); text-transform:uppercase; opacity:.75}
.rotabox .rb-n{font-size:var(--t-meta); font-weight:var(--w-bold); line-height:var(--lh-solid)}
.rotabox .rb-m{display:grid; place-items:center; height:16px}
.rotabox .rb-l{position:absolute; top:3px; right:3px; opacity:.55}
.rotabox.yes{background:var(--ok-bg); border-color:var(--ok); color:var(--ok)}
.rotabox.no{background:var(--risk-bg); border-color:var(--risk); color:var(--risk)}
.rotabox.leave{background:var(--info-bg); border-color:var(--info); color:var(--info)}
.rotabox.unset{border-style:dashed}
.rotabox.locked{opacity:.62; cursor:not-allowed}
.rotabox:focus-visible{outline:2px solid var(--sky-500); outline-offset:2px}
/* An unread notice is marked on the row, not only on the tab. */
.att.unread{border-left:var(--bar) solid var(--sky-500)}

/* The office rota grid — seven narrow columns beside a name. */
.rota-t th.c,.rota-t td.c{text-align:center;width:44px;padding-left:2px;padding-right:2px}
.rota-t th.wknd,.rota-t td.wknd{background:var(--surface-2)}
.rdot{
  display:inline-grid; place-items:center;
  width:26px; height:26px; border-radius:var(--r1);
  border:1px solid var(--border); background:var(--surface-2);
  color:var(--text-3); font:inherit; line-height:var(--lh-solid);
}
button.rdot{cursor:pointer}
.rdot.yes{background:var(--ok-bg); border-color:var(--ok); color:var(--ok)}
.rdot.no{background:var(--risk-bg); border-color:var(--risk); color:var(--risk)}
.rdot.leave{background:var(--info-bg); border-color:var(--info); color:var(--info)}
.rdot.unset{border-style:dashed}

/* The addresses at a postcode. A list somebody scans and taps —
   scrolls at ten of them rather than pushing the Save button off
   the bottom of a phone. */
.addrlist{
  display:flex; flex-direction:column; gap:2px;
  max-height:230px; overflow-y:auto;
  border:1px solid var(--border); border-radius:var(--r2);
  padding:4px; background:var(--surface-2);
}
.addrpick{
  display:flex; align-items:center; gap:8px;
  width:100%; text-align:left; min-height:40px; padding:8px 12px;
  border:1px solid transparent; border-radius:var(--r1);
  background:transparent; color:var(--text); font:inherit;
  font-size:var(--t-meta); cursor:pointer; touch-action:manipulation;
}
.addrpick:hover{background:var(--surface-3)}
.addrpick.on{background:var(--ok-bg); border-color:var(--ok); color:var(--ok); font-weight:var(--w-semi)}
@media (max-width:720px){ .addrpick{min-height:44px} }

/* ============================================================
   A DRIVER'S LIVE POSITION

   Green while it is current, grey the moment it is not, and the
   grey one says LAST KNOWN in words rather than relying on
   somebody noticing a colour. Nothing animates on the stale
   state: a pulsing pin reads as "live" from across a room.
   ============================================================ */
.livepin circle{transition:fill .3s ease}
.livepin.stale{opacity:.8}
.opslbl.stale{font-size:var(--t-micro);font-weight:var(--w-bold);letter-spacing:var(--ls-caps);fill:var(--warn)}
.livepins{margin-top:var(--s4);border-top:1px solid var(--border);padding-top:var(--s3)}
.livepin-r{display:flex;align-items:flex-start;gap:12px;padding:8px 0;
  border-bottom:1px solid var(--border)}
.livepin-r:last-of-type{border-bottom:0}
.livepin-d{flex:none;width:9px;height:9px;border-radius:999px;background:var(--ok);margin-top:6px;
  box-shadow:0 0 0 3px color-mix(in srgb,var(--ok) 22%,transparent)}
.livepin-r.stale .livepin-d{background:var(--ink-3);box-shadow:none}
.livepin-r .warn{color:var(--warn)}

/* Import records — one line per spreadsheet row */
.imp-row{padding:12px 0;border-top:1px solid var(--border);display:grid;gap:2px}
.imp-row:first-child{border-top:0}
/* Hours — one block per person, the week as seven taps */
.clk-list{display:grid;gap:0}
.clk-p{padding:var(--s3) 0;border-top:1px solid var(--rule)}
.clk-p:first-child{border-top:0}
.clk-days{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:4px;margin-top:6px}
.clk-d{min-height:48px;border:1px solid var(--rule);border-radius:var(--r1);background:var(--surface);display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:1px;font:inherit;color:var(--ink);cursor:pointer;padding:2px}
.clk-d b{font-size:var(--t-meta);font-variant-numeric:tabular-nums}
.clk-d.ok{background:var(--ok-bg);border-color:var(--ok)}
.clk-d.on{background:var(--int-soft,var(--surface-2));border-color:var(--int-line,var(--navy-700))}
.clk-d.warn{background:var(--warn-bg);border-color:var(--warn)}
.clk-d.edited b::after{content:'*';color:var(--warn)}
.clk-d:disabled{opacity:.35;cursor:default}
.mo-yard.clock{padding:var(--s3) 0;border-top:1px solid var(--rule);border-bottom:1px solid var(--rule);margin:var(--s2) 0}
/* ============================================================
   DEVICES

   Three tiers, one language. The app already had a phone layout and
   a laptop layout; this fills in the tablet, and — more importantly —
   gives the two roles that were locked to a phone frame a real layout
   on anything bigger.

     phone   <= 720   full screen, bottom tabs
     tablet  721-1180 full screen, wider measure, bottom tabs,
                      sidebar as an icon rail for the office
     desk    > 1180   sidebar or side rail, content on a measure

   Nothing here introduces a colour, a radius or a component. It is
   the same chrome, the same rules, arranged for the width.
   ============================================================ */

/* ---- the office shell on a landscape tablet ----
   The sidebar is a drawer up to 900. Between 901 and 1180 it was
   holding 250px of a 1024px screen — a quarter of the desk given to
   navigation. It becomes the icon rail the app already has. */
@media (min-width:901px) and (max-width:1180px){
  .shell{grid-template-columns:64px 1fr}
  .shell .nav-label,.shell .nav-lbl,.shell .wordmark,.shell .side-foot .who{display:none}
  .shell .nav-item{justify-content:center;padding:8px 0;border-left-width:0}
  .shell .side-head{justify-content:center}
  .shell .nav-group-label{display:none}
  /* On the icon rail the count has to shrink to a corner badge —
     at full size it sat squarely on top of the glyph it was
     counting. The ring is the rail's own colour, so the badge
     reads as sitting on the icon rather than behind it. */
  .shell .side .nav-badge{position:absolute;top:1px;right:1px;min-width:16px;height:16px;
    font-size:var(--t-micro);padding:0 2px;border-radius:var(--rp);
    box-shadow:0 0 0 2px var(--chrome)}
  .shell .nav-item{position:relative}
}

/* ---- reading measure ----
   A paragraph 1100px wide is not a paragraph anybody reads. Cap the
   running text, not the tables and boards that genuinely want width. */
@media (min-width:1181px){
  .page .lede, .page p,
  .appmain .lede, .appmain p,
  .portal-body .lede, .portal-body p{max-width:78ch}
  /* meta lines, figures and anything in a table keep their own width */
  /* .tiny.muted was blanket-exempted so short captions inside tables
   would not be boxed in — but the same class carries the
   explanatory paragraph at the top of half the screens in the
   system, and those were running to 1220px. Exempt it only where
   it genuinely is a caption. */
  .page .tiny, .appmain .tiny, .portal-body .tiny{max-width:92ch}
  .tbl .lede, .tbl p, .tbl .tiny, .kv p, .kv .lede, .kv .tiny,
  .fig, .today .tiny, .lever .tiny, .cmdstop .tiny{max-width:none}
}

/* ============================================================
   THE FIELD APP AND THE EMPLOYEE PORTAL

   Both are bottom-tab apps. On a phone they are the phone. Above
   that they were a 392px mock-up marooned on an empty screen — the
   app showing a picture of itself rather than running.
   ============================================================ */

/* Above phone width there is no device mock-up: it is the app. */
html[data-device="tablet"] .devwrap,
html[data-device="desk"] .devwrap{padding:0;background:var(--bg);display:block;min-height:100dvh}
html[data-device="tablet"] .phone,
html[data-device="desk"] .phone{
  width:100%;max-width:none;height:100dvh;border-radius:0;box-shadow:none;
}
html[data-device="tablet"] .notch,
html[data-device="desk"] .notch{display:none}

/* --- tablet: the same app, given room --- */
html[data-device="tablet"] .mob-head,
html[data-device="tablet"] .mshell.portal .mob-head{padding:var(--s5) var(--s6) var(--s4)}
html[data-device="tablet"] .mob-head > .spread,
html[data-device="tablet"] .mob-body > *,
html[data-device="tablet"] .mshell.portal .page > *{max-width:760px;margin-left:auto;margin-right:auto}
html[data-device="tablet"] .mob-body{padding:var(--s5) var(--s6) 110px}
html[data-device="tablet"] .mshell.portal .page{padding:var(--s5) var(--s6) 110px}
/* The tab bar sits under the content column rather than running the
   full width. left:50% with the inherited right:0 sized it at half the
   viewport, so it has to reclaim its own width. */
html[data-device="tablet"] .mob-tabs{
  left:50%;right:auto;width:100%;max-width:760px;transform:translateX(-50%);
  border-left:1px solid var(--border);border-right:1px solid var(--border);
  /* the same transform, named, so the tab bar's bounce on a press
     (51b-sidebar.js) is drawn after it rather than in its place: written
     over it, the bar was thrown half its width to the right on every
     press (7 November, night). Change one, change both. */
  --bar-tf:translateX(-50%)}
html[data-device="tablet"] .mshell.portal .mob-tabs{position:fixed;bottom:0}
html[data-device="tablet"] .fab{right:calc(50% - 380px + 18px)}

/* --- desk: a side rail, and the content on a measure --- */
.appshell{display:grid;grid-template-columns:76px 1fr;min-height:100dvh}
/* the field rail carries the same standard as the office rail —
   one control language across every role */
.apprail{background:var(--chrome);color:var(--chrome-text);display:flex;flex-direction:column;
  align-items:center;gap:var(--s2);padding:var(--s4) 0;position:sticky;top:0;height:100dvh;
  border-right:1px solid var(--chrome-rule);position:sticky}
.apprail::after{content:"";position:absolute;top:0;right:0;bottom:0;width:8px;pointer-events:none;
  background:linear-gradient(90deg,rgba(255,255,255,0),rgba(255,255,255,.16) 50%,rgba(255,255,255,0))}
.apprail .logo{margin-bottom:var(--s4)}
.apprail .logo svg.mk{width:30px;height:30px}
.apprail .wordmark{display:none}
.railtab{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;
  width:60px;padding:12px 0;border-radius:var(--r3);color:rgba(255,255,255,.62);
  font-size:var(--t-micro);font-weight:var(--w-semi);letter-spacing:var(--ls-micro);text-align:center;position:relative;
  border:1px solid transparent;
  transition:color var(--dur),background var(--dur),box-shadow var(--dur)}
.railtab:hover{color:#fff;background:var(--chrome-hover)}
.railtab.on{color:#fff;background:linear-gradient(180deg,var(--int),var(--int-lo));
  border-color:rgba(255,255,255,.16);box-shadow:0 2px 14px var(--int-glow),var(--lift-2)}
.railtab.on::before{content:"";position:absolute;left:-1px;top:10px;bottom:10px;width:2.5px;
  border-radius:var(--rp);background:var(--gold)}
html[data-theme="light"] .railtab.on{background:rgba(255,255,255,.16);box-shadow:none}
.railtab .dotbadge{position:absolute;top:4px;right:10px}
.railtab span{position:relative}
.apprail .railfoot{margin-top:auto;display:flex;flex-direction:column;gap:var(--s2);align-items:center}
.appmain{display:flex;flex-direction:column;min-width:0;min-height:100dvh;background:var(--bg)}
.appmain .mob-head{flex:none}
.appmain .mob-body,.appmain .page{flex:1;overflow-y:auto;padding:var(--s6);padding-bottom:var(--s8)}
.appmain .mob-head > .spread,
.appmain .mob-body > *,
.appmain .page > *{max-width:860px;margin-left:auto;margin-right:auto}
.appshell .mob-tabs{display:none}
.appshell .fab{display:none}
/* One brand mark, on the rail. The head carries who is signed in. */
.appmain .mob-head .logo{display:none}
.appmain .mob-head{padding:var(--s4) var(--s6)}
.appwho{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);
  font-size:var(--t-tiny);color:rgba(255,255,255,.6)}
.appwho b{display:block;font-family:var(--font);text-transform:none;letter-spacing:var(--ls-0);
  font-size:var(--t-body);color:#fff;font-weight:var(--w-semi);margin-top:2px}

/* the quick action lives on the rail at this width */
.apprail .railquick{width:44px;height:44px;border-radius:var(--r3);flex:none;
  background:linear-gradient(180deg,var(--int-hi),var(--int));color:#fff;display:grid;place-items:center;
  border:1px solid rgba(255,255,255,.16);box-shadow:0 4px 18px var(--int-glow);
  transition:transform var(--dur) var(--ease)}
.apprail .railquick:hover{transform:translateY(-2px)}
.apprail .railquick:active{transform:scale(.94)}
html[data-theme="light"] .apprail .railquick{background:var(--navy-700);box-shadow:none}

/* ============================================================
   THE CLIENT PORTAL
   ============================================================ */
html[data-device="tablet"] .portal-body{padding:var(--s6) var(--s6) var(--s8)}
/* The panels already split two-up on a grid. What ran the full width was
   the prose inside them — a variation description 918px across. The
   footer line is deliberately left alone; it is a footer, not a read. */
@media (min-width:721px){
  .portal-body p,
  .portal-body .lede{max-width:72ch}
  .portal-body > .tiny{max-width:none}
  .portal-hero p,.portal-hero .lede{max-width:64ch}
}

/* ============================================================
   THE KEYPADS

   Both gates were full-bleed: at 1440 the sign-in prompt ran the whole
   width of the screen. They are a card now, on every tier.
   ============================================================ */
@media (min-width:721px){
  .pinwrap{align-items:center;justify-content:center;padding:var(--s6)}
  .pinhead{padding:var(--s7) var(--s6) var(--s5);width:100%;max-width:460px;margin:0 auto}
  .pinbody{flex:none;width:100%;max-width:460px;margin:0 auto;
    border-radius:var(--r3);border:1px solid var(--border);
    padding:var(--s6) var(--s6) var(--s7)}
}
@media (min-width:1181px){
  .pinwrap{min-height:100dvh}
  .pinhead{padding-top:var(--s8)}
}

/* ---- touch tiers ----
   A tablet is a touch screen with a big surface: keep the targets a
   finger can hit even though the layout is roomy. */
@media (hover:none) and (min-width:721px){
  .btn,.chip,.mtab,.railtab{min-height:44px}
  .icobtn{min-width:40px;min-height:40px}
}

/* ============================================================
   FORTY-FOUR PIXELS, ON THE PHONE, EVERYWHERE

   This system holds a 44px floor for anything you press, and held
   it — on the controls somebody remembered. A walk of every role
   through its own menu at 390px found five that were never
   measured: the job search at 21.8px, the planning "Fix" at 20.3,
   an expiry pill at 22, every segmented control at 32.8, and every
   settings switch at 21. They are on the screens a scaffolding
   company uses standing up, outdoors, one-handed, in gloves.

   Two fixes, because the right answer is not the same.

   Where there is room and the control was only small by accident —
   a search field, a segment — it simply grows.

   Where the SIZE IS THE DESIGN — a toggle track, an inline status
   pill — it must not. Those get padding to make the hit area 44px,
   `background-clip:content-box` so the paint stays on the original
   shape, and a negative margin so the row around them does not
   move. The control reaches further than it looks and looks exactly
   as it did.

   NOT a `::after` overlay, which is the obvious trick and is wrong
   here: `.switch::after` is the toggle's knob and `.pill::before`
   is its glyph. The first version of this used `::after` anyway and
   silently replaced the knob on every switch in the app — caught
   only because the hit test was fixed to scroll a control into view
   before pressing it, which is a second thing that had been quietly
   passing everything below the fold.

   Phone tier only. Nothing above 720px moves.
   ============================================================ */
@media (max-width:720px){

  /* --- grow --- */
  .jsearch .inp{min-height:44px}
  .seg > button{min-height:44px;padding-top:0;padding-bottom:0}

  /* --- reach without growing --- */
  .switch{
    box-sizing:border-box;
    height:44px; padding-block:11.5px;
    background-clip:content-box;
    margin-block:-11.5px;
  }
  /* The knob is absolutely positioned against the padding box, so
     it has to come down by however much padding was added or it
     sits above the track it belongs on. */
  .switch::after{top:14px}

  .warnfix,button.pill{
    box-sizing:content-box;
    padding-block:11.5px;
    background-clip:content-box;
    margin-block:-11.5px;
  }
  /* 9.5px uppercase, so 11.5px each side still left it at 39. */
  .warnfix{padding-block:16px;margin-block:-16px}

  /* The permissions grid is native checkboxes in table cells, with
     no label around them to widen the target — 13px, forty-two of
     them, on the screen where somebody decides what their staff can
     see. The box grows and the row grows with it; nothing else on
     the page moves, and a 13px checkbox was never a deliberate size,
     it is the one the browser gives you when nobody says. */
  td.r > input[type=checkbox],
  td.r > input[type=radio]{width:24px;height:24px;margin:0}
  td.r{height:44px}

  /* A JOB REFERENCE IN A TABLE IS A LINK, AND ON A PHONE IT WAS 16
     PIXELS TALL.

     Found by the walk, on #/equipment, where the hired-in table
     names the job each item is standing on — the yard man's screen,
     outdoors, one-handed. It is every table in the app, not that
     one: a bare <a> in a cell takes the line-height of its text and
     nothing else.

     Padding rather than height. An inline element's vertical
     padding paints and answers a tap without adding a single pixel
     to the line it sits on, so every table in the system keeps its
     rows exactly as they are and the reference reaches 44px. Paint
     is held to the text with content-box clipping, so an underline
     or a hover ground does not suddenly become a 44px slab. */
  /* The office rota grid. 26px is right beside a name on a desktop
     and is not a target on a phone — Dicko turns these boxes over
     standing in the yard. The cell grows, the table scrolls inside
     its own wrapper, and nothing else on the page moves. */
  button.rdot{width:44px; height:44px}
  .rota-t th.c,.rota-t td.c{width:48px}

  .tbl td a, .tbl th a{
    box-sizing:content-box;
    padding-block:16px;
    background-clip:content-box;
  }
}

/* ============================================================
   THE SCREEN STAYS PUT

   Double-tapping zoomed. `touch-action:manipulation` is already on
   the document and on every control, which is the modern answer and
   is not enough on its own — Safari keeps double-tap-to-zoom unless
   the viewport itself rules it out, so that is pinned in the head
   as well.

   The other half of the same complaint, and the one nobody reports
   as zooming because it looks like the page jumping: iOS zooms in
   when you focus an input whose text is under 16px, and does not
   zoom back out. On a form filled in on a wet morning that is worse
   than the double tap. Every field a finger can land in is 16px on
   a phone.

   This does give up pinch-to-zoom, deliberately. It is the right
   trade for something used one-handed in gloves on scaffolding —
   but it IS a trade, and if this ever holds a person who needs to
   magnify the screen, the viewport line is where to undo it.

   It was undone on 17 October (V1: pinch-zoom on again, for WCAG
   1.4.4) and put back on 8 November, 23:22, by Ahmed: "the mobile
   website shouldn't be zoomable, it should be fixed". Three parts
   now, because no one of them holds on every phone: the viewport in
   the head (maximum-scale=1, user-scalable=no), the root's
   touch-action (pan-x pan-y, 19d section 40) and Safari's gesture
   events refused on a touch screen (30-shell.js). The phone's own
   Zoom in its accessibility settings still works over all three.
   waytest holds them, and puts a pinch on a phone to prove it.
   ============================================================ */
@media (max-width:720px){
  html{-webkit-text-size-adjust:100%;text-size-adjust:100%}
}
/* The 16px field rule lives at the end of the cascade, in
   17-fixes.css — 15-command.css sets 14.5px on inputs and is
   concatenated after this file, so a rule here loses. */
/* ============================================================
   ART — the scaffolding layer

   Everything here is drawn, not photographed. Inline SVG and
   gradients, no image files: the whole application is still one
   HTML file that opens from a memory stick in a site cabin with no
   signal, and that is worth more than a stock photograph.

   The rule for all of it: it sits behind the data, at low opacity,
   and it is always a drawing of something this company actually
   owns — a bay of tube, a lorry, the coastline they work.
   ============================================================ */

/* ---- the hero band ----
   Sits behind the greeting on Home. A bay of scaffold in elevation,
   fading out before it reaches the text. */
.hero{position:relative;margin:calc(var(--s5) * -1) calc(var(--s5) * -1) 0;
  padding:var(--s6) var(--s5) var(--s2);overflow:hidden;isolation:isolate}
.hero::before{content:"";position:absolute;inset:0;z-index:-2;
  background:
    radial-gradient(1100px 340px at 78% -12%,rgba(42,47,184,.24),transparent 62%),
    linear-gradient(180deg,var(--paper-2),var(--paper) 88%)}
.hero-art{position:absolute;top:0;right:0;bottom:0;width:min(62%,900px);z-index:-1;
  opacity:.3;pointer-events:none;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 46%);
  mask-image:linear-gradient(90deg,transparent,#000 46%)}
.hero-art svg{width:100%;height:100%;display:block}
html[data-theme="light"] .hero-art{opacity:.16}
html[data-theme="light"] .hero::before{
  background:radial-gradient(1100px 340px at 78% -12%,rgba(15,12,91,.09),transparent 62%),
    linear-gradient(180deg,var(--paper-2),var(--paper) 88%)}
/* the hazard stripe that closes the band — the same tape that goes
   round a base lift */
.hero::after{content:"";position:absolute;left:var(--s5);right:var(--s5);bottom:0;height:3px;z-index:-1;
  background:repeating-linear-gradient(115deg,var(--gold) 0 9px,transparent 9px 19px);opacity:.2}
.hero .page-head{margin-bottom:var(--s4)}
.hero .page-head::after{display:none}
@media (max-width:900px){
  .hero{margin:calc(var(--s4) * -1) calc(var(--s4) * -1) 0;padding:var(--s5) var(--s4) var(--s2)}
  .hero::after{left:var(--s4);right:var(--s4)}
  .hero-art{width:100%;opacity:.16}
}

/* ---- the drawn KPI illustrations ----
   Stroke colour is inherited so each one reads in either theme. */
.art-line{fill:none;stroke:var(--ink-2);stroke-width:1;vector-effect:non-scaling-stroke}
.art-line.thin{stroke-width:.75;opacity:.62}
.art-line.idle{opacity:.28}
.art-fill{fill:var(--ink-2);opacity:.5}
.art-key{fill:none;stroke:var(--int-hi);stroke-width:1.4;vector-effect:non-scaling-stroke}
html[data-theme="light"] .art-key{stroke:var(--sky-600)}
.art-gold{fill:none;stroke:var(--gold);stroke-width:1.5;vector-effect:non-scaling-stroke}
.art-gold-f{fill:var(--gold);opacity:.16}
.art-pin{fill:var(--int-hi)}
html[data-theme="light"] .art-pin{fill:var(--navy-700)}

/* ---- blueprint ground ----
   A drawing-board grid, for the surfaces that are genuinely about
   setting something out: the planner, the route sheet, an empty
   state waiting to be filled. */
.blueprint{position:relative}
.blueprint::before{content:"";position:absolute;inset:0;pointer-events:none;border-radius:inherit;
  background-image:
    linear-gradient(var(--rule) 1px,transparent 1px),
    linear-gradient(90deg,var(--rule) 1px,transparent 1px);
  background-size:28px 28px;opacity:.5;
  -webkit-mask-image:radial-gradient(120% 90% at 50% 0%,#000,transparent 78%);
  mask-image:radial-gradient(120% 90% at 50% 0%,#000,transparent 78%)}

/* ---- the standard ----
   A short gold rule used to open a major section. Deliberately not
   a full-width underline: it points at the heading rather than
   boxing it. */
.standard{position:relative;padding-left:12px}
.standard::before{content:"";position:absolute;left:0;top:2px;bottom:2px;width:3px;border-radius:var(--rp);
  background:linear-gradient(180deg,var(--gold),rgba(232,176,75,.15))}

/* ---- number transitions ----
   A figure that has just changed lifts once. Used by the KPI strip
   after a recalculation so a change is noticed without a toast. */
@keyframes figtick{
  0%{transform:translateY(5px);opacity:0}
  100%{transform:none;opacity:1}
}
.fig.tick{animation:figtick var(--dur-3) var(--ease-out)}

/* ---- page transition ----
   150ms. Long enough to register as a move between screens, short
   enough that nobody waiting on a job number ever feels it. */
@keyframes pagein{from{opacity:0;transform:translateY(6px)}}
.page>*,.appmain>*{animation:pagein .15s var(--ease-out) both}
.page>.hero{animation:none}
/* V2 QA: a re-render of the SAME screen (a filter, a tab, a toggle) must
   not replay the entrance. 51-motion.js marks those with html.rerender. */
html.rerender .page>*,html.rerender .appmain>*{animation:none}
@media (prefers-reduced-motion:reduce){
  .page>*,.appmain>*{animation:none}
}
/* ============================================================
   THE COMMAND CENTRE

   This layer sits on top of the components and gives them the
   depth, hierarchy and restraint the direction asks for. It does
   not invent new markup: every selector here is a class the
   application already renders, restyled.

   Three things it is deliberately NOT doing:

   It is not frosting everything. Backdrop blur is expensive, and
   on a dark ground it mostly produces grey. Depth here comes from
   a measured surface ladder and a lit top edge.

   It is not lighting everything. Illumination is a scarce resource
   spent on the control the user is about to press and on the panel
   carrying the live picture. If everything glows, nothing is
   important.

   It is not decorating with colour. Blue means interactive or
   active. Purple means a second route. Amber and red are states.
   Anything that is merely information is typography.
   ============================================================ */

/* ------------------------------------------------------------
   THE GROUND

   A single very large, very faint radial behind everything, sat
   under the sidebar's edge, so the workspace has a top-left light
   source and the bottom-right of a long page falls away. It is
   two percent of white. You are not meant to see it; you are
   meant to notice its absence if it is removed.
   ------------------------------------------------------------ */
html[data-theme="dark"] body{
  background:
    radial-gradient(1200px 620px at 12% -8%,rgba(22,139,255,.055),transparent 62%),
    radial-gradient(900px 500px at 96% 4%,rgba(139,109,246,.028),transparent 58%),
    var(--paper);
  background-attachment:fixed;
}

/* The technical grid. Scaffold is a grid before it is anything
   else, so the reference is honest rather than decorative — but it
   is set at three percent and only on the workspace, never behind
   a card, because a pattern behind data is noise. */
html[data-theme="dark"] .main::before{
  content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
  background-image:
    linear-gradient(rgba(255,255,255,.018) 1px,transparent 1px),
    linear-gradient(90deg,rgba(255,255,255,.018) 1px,transparent 1px);
  background-size:64px 64px;
  mask-image:radial-gradient(1000px 700px at 20% 0%,#000,transparent 78%);
  -webkit-mask-image:radial-gradient(1000px 700px at 20% 0%,#000,transparent 78%);
}
.main>*{position:relative;z-index:1}

/* ============================================================
   CARD HIERARCHY

   Seven kinds, and the difference between them is legible without
   reading a word: radius says importance, the ladder says depth,
   and the left rule says state. A screen where every card is
   identical is a screen with no hierarchy, which is the fault the
   brief is describing.
   ============================================================ */
.card{
  background:var(--surface);
  background-image:var(--pane);
  border:1px solid var(--rule);
  border-radius:var(--r3);
  box-shadow:var(--lift),var(--sh1);
}

/* PRIMARY — the operating picture, the day board, the one panel on
   a screen that is the reason the screen exists. Rounder, carries
   its own tonal gradient, and is the only card allowed a glow. */
.card.primary,.card.hero-card{
  border-radius:var(--r5);
  background-image:var(--pane-hero);
  border-color:var(--rule-strong);
  box-shadow:var(--lift-2),var(--sh2);
}

/* SECONDARY — supporting information beside the primary. */
.card.sub{
  border-radius:var(--r3);
  background:var(--surface);
  background-image:none;
  box-shadow:var(--lift),var(--sh1);
}

/* STAT — compact, numerical, usually in a row of four or six. */
.card.stat,.tile{
  border-radius:var(--r2);
  background:var(--surface-2);
  background-image:none;
  border:1px solid var(--rule);
  box-shadow:none;
}

/* ACTION — something the user has to do. Led in blue, because
   blue in this system means "you can act on this". */
.card.action{
  border-radius:var(--r3);
  border-left:var(--bar) solid var(--int);
  background:linear-gradient(90deg,var(--int-soft),transparent 30%),var(--surface);
}

/* ALERT and STATUS — the left rule carries the state, and the
   ground is tinted by two percent so it separates from its
   neighbours without shouting. */
.card.led{border-left:var(--bar) solid var(--rule-strong)}
.card.led.risk{border-left-color:var(--risk);background:linear-gradient(90deg,var(--risk-bg),transparent 26%),var(--surface)}
.card.led.warn{border-left-color:var(--warn);background:linear-gradient(90deg,var(--warn-bg),transparent 26%),var(--surface)}
.card.led.ok{border-left-color:var(--ok);background:linear-gradient(90deg,var(--ok-bg),transparent 26%),var(--surface)}
.card.led.info{border-left-color:var(--int);background:linear-gradient(90deg,var(--int-soft),transparent 26%),var(--surface)}

/* DATA — a chart or a meter. Inset rather than raised, because a
   measurement is something you look into, not something sitting on
   the page. */
.card.data{
  border-radius:var(--r3);
  background:var(--paper-2);
  background-image:none;
  box-shadow:inset 0 1px 3px rgba(0,0,0,.35);
  border-color:var(--rule);
}

/* the hover lift the brief asks for: felt, not seen */
.card.hov,.card.click{transition:border-color 140ms var(--ease),transform 140ms var(--ease),
  box-shadow 140ms var(--ease),background-color 140ms var(--ease)}
.card.hov:hover,.card.click:hover{
  border-color:var(--rule-strong);transform:translateY(-2px);
  box-shadow:var(--lift-2),var(--sh2);
}
.card.click:active{transform:translateY(0);transition-duration:60ms}

/* ============================================================
   THE RAIL

   Grouped, quiet, and darker than the workspace so the eye falls
   into the data. The selected item is the only lit thing on it.
   ============================================================ */
.side{
  background:var(--chrome);
  border-right:1px solid var(--rule);
}
/* The scaffold standard down the sidebar edge was the one literal
   piece of scaffolding in the interface, and at this scale of
   restraint it reads as decoration. A single hairline of light
   does the same job — it separates the rail from the workspace —
   without illustrating anything. */
html[data-theme="dark"] .side::after{
  background:linear-gradient(180deg,
    rgba(22,139,255,0) 0%,rgba(22,139,255,.20) 18%,
    rgba(22,139,255,.08) 55%,rgba(22,139,255,0) 100%);
  width:1px;right:0;opacity:1;
}
.side-top{border-bottom:1px solid var(--rule);min-height:64px}

/* a group is a group: the label sits close to its items and far
   from the group above, which is the only thing that makes a
   twenty-eight item list read as seven short ones */
.nav-group{margin-top:var(--s5)}
.nav-group:first-child{margin-top:0}
.nav-label{
  font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);
  font-size:var(--t-micro);font-weight:var(--w-med);color:var(--ink-3);
  padding:0 12px var(--s2);
}
.nav-item{
  border-radius:var(--r2);padding:8px 12px;font-size:var(--t-meta);font-weight:var(--w-med);
  color:var(--chrome-2);gap:12px;
  transition:background 120ms var(--ease),color 120ms var(--ease);
  border:1px solid transparent;
}
.nav-item:hover{background:var(--chrome-hover);color:var(--ink)}
/* Flat, here too — this is the rule that wins. The current item on
   the rail is on screen every second of the working day, which is
   the strongest possible argument for it being quiet. */
.nav-item.on{
  color:#fff;font-weight:var(--w-semi);
  background:var(--int);
  border-color:var(--int);
  box-shadow:none;
}
/* the gold marker was a second accent on an item that is already
   the loudest thing on the rail */
.nav-item.on::before{display:none}
html[data-theme="light"] .nav-item.on{background:var(--int);border-color:var(--int)}
.nav-item .ic{opacity:.7}
.nav-item.on .ic,.nav-item:hover .ic{opacity:1}

/* ============================================================
   THE TOPBAR
   ============================================================ */
/* Solid, for the same reason the bottom bar is: chrome should be
   the quietest thing on the screen, and a bar that takes its colour
   from whatever happens to be scrolling under it is the opposite of
   quiet. The hairline underneath is what separates it now. */
.topbar{
  height:64px;background:var(--surface);
  border-bottom:1px solid var(--rule);
}
.searchbar{
  background:var(--surface-2);border:1px solid var(--rule);
  border-radius:var(--rp);height:38px;
  transition:border-color 140ms var(--ease),background 140ms var(--ease);
}
.searchbar:hover{border-color:var(--rule-strong);background:var(--surface-3)}

/* ============================================================
   PAGE MASTHEAD

   The short amber standard under the title stays. It is not a
   full underline — it points at the heading rather than boxing
   it — and it is the one piece of the trade's colour that opens
   every screen.
   ============================================================ */
.page-head{padding-bottom:var(--s4);margin-bottom:var(--s5)}
.page{padding:var(--s6) var(--s6) var(--s9);max-width:1680px}

/* ============================================================
   BUTTONS — five kinds and no more.
   ============================================================ */
.btn{
  border-radius:var(--r2);height:38px;padding:0 16px;font-weight:var(--w-semi);
  transition:background 130ms var(--ease),border-color 130ms var(--ease),
    box-shadow 130ms var(--ease),transform 90ms var(--ease);
  letter-spacing:var(--ls-0);
}
.btn:active{transform:scale(.98)}
/* Flat. This is the file that wins the cascade, so flattening the
   button in 11-components.css did nothing until here — which is
   the same trap that let a rounded corner take the table scrolling
   with it. A primary button is the only saturated blue on the
   screen; a gradient and a coloured glow behind it were saying a
   second time what the colour had already said. */
.btn-p{
  background:var(--int);
  border-color:var(--int);color:#fff;
  box-shadow:none;
}
.btn-p:hover{background:var(--int-hi);border-color:var(--int-hi);box-shadow:none}
.btn-p:active{background:var(--int-lo);border-color:var(--int-lo)}
html[data-theme="light"] .btn-p{background:var(--int);border-color:var(--int);box-shadow:none}
html[data-theme="light"] .btn-p:hover{background:var(--int-hi);border-color:var(--int-hi)}
.btn-s{background:var(--surface-2);border-color:var(--rule-strong);box-shadow:var(--lift)}
.btn-s:hover{background:var(--surface-3);border-color:var(--ink-3)}
.btn-g{background:transparent;border-color:transparent;color:var(--ink-2)}
.btn-g:hover{background:var(--surface-2);color:var(--ink)}
.btn.lg{height:48px;padding:0 24px;font-size:var(--t-body);border-radius:var(--r3)}
.btn.sm{height:32px;padding:0 12px;font-size:var(--t-small);border-radius:var(--r1)}
.icobtn{border-radius:var(--r2)}
/* the sky-blue link was a third blue in a system that has one */
.lnk,html[data-theme="dark"] .lnk{color:var(--int-hi)}
html[data-theme="light"] .lnk{color:var(--int)}

/* ============================================================
   FORMS — label above, four states, and a target a thumb can hit.
   ============================================================ */
.lbl{
  font-family:var(--font);text-transform:none;letter-spacing:var(--ls-0);
  font-size:var(--t-small);font-weight:var(--w-semi);color:var(--ink-2);
}
.inp,.sel,.ta{
  height:44px;border-radius:var(--r2);font-size:var(--t-body);padding:0 12px;
  background:var(--surface-3);border-color:var(--rule);
}
.ta{height:auto;padding:12px 12px;min-height:96px}
.inp:hover,.sel:hover,.ta:hover{border-color:var(--rule-strong)}
.inp:focus,.sel:focus,.ta:focus{
  border-color:var(--int);background:var(--surface-2);
  box-shadow:0 0 0 3px var(--int-glow);
}
html[data-theme="light"] .inp:focus,html[data-theme="light"] .sel:focus,
html[data-theme="light"] .ta:focus{border-color:var(--int);box-shadow:0 0 0 3px var(--int-glow)}
/* Error and success are a border, a ring AND a mark on the label —
   never the border alone, which is colour carrying meaning by
   itself. */
.inp[aria-invalid="true"],.ta[aria-invalid="true"],.sel[aria-invalid="true"]{
  border-color:var(--risk);box-shadow:0 0 0 3px var(--risk-bg);
}
.field.bad .lbl::after{content:": check this";color:var(--risk);font-weight:var(--w-semi)}
.inp.good,.ta.good,.sel.good{border-color:var(--ok-line)}
.field.good .lbl::after{content:" \2713";color:var(--ok)}
.inp[disabled],.sel[disabled],.ta[disabled]{opacity:.42;background:var(--paper-2)}
.sel{padding-right:36px}

/* ============================================================
   STATE CHIPS
   ============================================================ */
.pill{
  font-family:var(--font-head);height:22px;border-radius:var(--rp);
  letter-spacing:var(--ls-caps);font-weight:var(--w-semi);font-size:var(--t-micro);padding:0 8px;
}
.tag{background:var(--surface-2);border-color:var(--rule);font-weight:var(--w-med);font-size:var(--t-small)}

/* ============================================================
   TABLES — kept, but quieter. A ruled grid on a dark ground is
   a cage; the row is defined by its own hairline and nothing else.
   ============================================================ */
.tbl th{
  font-family:var(--font-head);letter-spacing:var(--ls-caps);font-size:var(--t-micro);font-weight:var(--w-semi);
  background:var(--paper-2);border-bottom:1px solid var(--rule);
}
.tbl td{border-bottom:1px solid var(--line-soft,var(--rule))}
/* THE ROUNDED CORNER TOOK THE SCROLLING WITH IT.

   This was `overflow:hidden`, added so the border radius clipped the
   table's own corners — and the shorthand sets BOTH axes, so it
   quietly turned off horizontal scrolling on every table in the app.
   A table wider than its card simply lost its right-hand columns:
   the decision buttons on time off, the last columns of the money
   screens. Nothing looked broken, the content was just not there.

   It survived because on a phone tables are not tables — they become
   stacked labelled cards, and the phone rule sets overflow:visible —
   so every mobile check passed while the desktop lost columns.

   The corners still clip. The columns come back. */
.tblwrap{border-radius:var(--r3);border:1px solid var(--rule);
  overflow-x:auto;overflow-y:hidden}

/* AND SOMEBODY HAS TO KNOW THERE IS MORE.

   A table you can scroll and cannot tell you can scroll is the same
   fault wearing a better hat. When there is more to the right, the
   edge fades; scroll to the end and the fade goes. Set by scrollHints()
   after each render, because only the browser knows whether it fits. */
.tblwrap{position:relative}
.tblwrap.more{
  -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 44px),transparent 100%);
  mask-image:linear-gradient(to right,#000 calc(100% - 44px),transparent 100%);
}
.tblwrap.more.at-end{-webkit-mask-image:none;mask-image:none}
/* The scrollbar itself, made visible rather than left to the platform
   to hide — on a trackpad Mac it is invisible until you already know. */
.tblwrap::-webkit-scrollbar{height:8px}
.tblwrap::-webkit-scrollbar-track{background:transparent}
.tblwrap::-webkit-scrollbar-thumb{background:var(--rule);border-radius:var(--rp)}
.tblwrap:hover::-webkit-scrollbar-thumb{background:var(--ink-3,var(--rule))}
.tblwrap .tblwrap{border:0;border-radius:0}

/* ============================================================
   THE KPI ROW

   Six figures across the top of the day. Each is a control that
   opens the screen behind it, so they get the affordances of a
   control: a hover state, a lift, and an arrow that appears.
   ============================================================ */
.today{
  gap:1px;background:var(--rule);
  border:1px solid var(--rule);border-radius:var(--r4);
  overflow:hidden;
}
.today-c{
  background:var(--surface);
  border:0;border-radius:0;
  padding:var(--s5) var(--s5) var(--s5);
  transition:background 140ms var(--ease);
  position:relative;
}
.today-c:hover{background:var(--surface-2)}
.today-k{
  font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);
  font-size:var(--t-micro);font-weight:var(--w-med);color:var(--ink-3);
}
.today-v{
  font-size:var(--t-hero);letter-spacing:var(--ls-fig);font-weight:var(--w-semi);
  margin-top:8px;display:block;line-height:var(--lh-solid);
}
.today-s{font-size:var(--t-small);color:var(--ink-3);margin-top:6px;display:block;line-height:var(--lh-ui)}
/* A flagged figure gets an amber left rule and an amber number.
   Two signals, so it is not colour alone. */
.today-c.flag::before{
  content:"";position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--warn);
}
.today-c.flag .today-v{color:var(--warn)}
.today-open{opacity:0;transition:opacity 140ms var(--ease),transform 140ms var(--ease);
  transform:translateX(-3px);color:var(--int-hi)}
.today-c:hover .today-open{opacity:1;transform:none}

/* ============================================================
   MOTION — restrained, and off entirely if the machine says so.
   ============================================================ */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important;scroll-behavior:auto!important;
  }
}

/* ------------------------------------------------------------
   THE DRAWN ILLUSTRATIONS

   A bay of tube behind the job count, a gang behind the crew
   count, a lorry behind the lorries, a radar behind the first
   arrival, a trace behind the money at risk, a shield behind the
   checks. Every one of them is a drawing of something this
   company owns, drawn in line rather than photographed, and they
   are the reason the day board looks like White's rather than
   like a template.

   They sit BEHIND the figure and stop well short of it. The
   balance that matters is between two failures: too strong and
   the number has to compete with a picture; too faint and the
   board is six numbers in six boxes. This is the level where the
   drawing is clearly there and the figure is still the first
   thing you read.
   ------------------------------------------------------------ */
.today-c svg{opacity:.30;transition:opacity 260ms var(--ease)}
.today-c:hover svg{opacity:.44}
.today-c .art-key,.today-c .art-pin{opacity:.85}
.today-c .art-gold{opacity:.9}
html[data-theme="light"] .today-c svg{opacity:.2}

/* the hatched rule that closes the day board — the one place the
   amber hazard stripe belongs, because it is a boundary marker */
.hero{background:none;margin-bottom:var(--s5)}
.hero::before{
  background:radial-gradient(1100px 380px at 76% -20%,rgba(22,139,255,.12),transparent 64%);
}
.hero-art{opacity:.16}
html[data-theme="light"] .hero-art{opacity:.08}

/* a figure at risk: an amber rule down the cell and an amber
   number, which is two signals rather than one colour */
.today-c.flag{border:0;background:var(--surface)}
.today-c.flag:hover{background:var(--surface-2)}

/* ============================================================
   LIVE OPERATIONS

   The one panel on the dashboard allowed to be large, rounded and
   lit. Everything about it says "this is the picture" — and the
   note at the bottom says, in words rather than a footnote, that
   the picture is the plan.
   ============================================================ */
.opswrap{padding:0;overflow:hidden}
.ops-head{
  display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;
  padding:var(--s5) var(--s5) var(--s4);
}
.ops-map{
  position:relative;
  background:
    radial-gradient(680px 340px at 30% 0%,rgba(22,139,255,.07),transparent 66%),
    var(--paper-2);
  border-top:1px solid var(--rule);border-bottom:1px solid var(--rule);
}
.ops-map svg{display:block;width:100%;height:auto}
.opslbl{font-size:var(--t-tiny);fill:var(--ink-3);font-family:var(--font);font-weight:var(--w-semi)}
.opsn{font-size:var(--t-tiny);fill:var(--ink);font-family:var(--font);font-weight:var(--w-bold);
  font-variant-numeric:tabular-nums}
.opsstop{cursor:pointer}
.opsstop circle:first-child{transition:r 130ms var(--ease),fill 130ms var(--ease)}
.opsstop:hover circle:first-child,.opsstop:focus-visible circle:first-child{
  r:15;fill:var(--surface-2)}
.opsstop:focus-visible{outline:none}
.opsstop:focus-visible circle:first-child{stroke-width:3}
/* the route line draws itself once, which is the only animation on
   this panel and the only one that tells you something: the order */
@media (prefers-reduced-motion:no-preference){
  .opsline{stroke-dashoffset:280;animation:opsdraw 1.5s var(--ease-out) forwards}
}
@keyframes opsdraw{to{stroke-dashoffset:0}}
.ops-scale{
  position:absolute;left:var(--s4);bottom:var(--s3);
  display:flex;align-items:center;gap:8px;font-size:var(--t-tiny);color:var(--ink-3);
}
.ops-scale i{height:5px;border-left:1px solid var(--ink-3);border-right:1px solid var(--ink-3);
  border-bottom:1px solid var(--ink-3);display:block}
.ops-card{
  position:absolute;right:var(--s4);top:var(--s4);width:min(290px,calc(100% - var(--s5)*2));
  background:var(--surface-2);border:1px solid var(--rule-strong);border-radius:var(--r3);
  padding:var(--s4);box-shadow:var(--sh2),var(--lift-2);
}
.ops-x{position:absolute;top:6px;right:6px;width:30px;height:30px}
.ops-kv{display:grid;grid-template-columns:auto 1fr;gap:6px var(--s3);margin-top:var(--s3);
  align-items:baseline}
.ops-kv b{font-weight:var(--w-semi);font-size:var(--t-meta);text-align:right}
.ops-legend{display:flex;flex-wrap:wrap;gap:var(--s2);padding:var(--s4) var(--s5)}
.opskey{
  display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:var(--rp);
  background:var(--surface-2);border:1px solid var(--rule);font-size:var(--t-small);
  transition:border-color 130ms var(--ease),background 130ms var(--ease);min-height:38px;
}
.opskey:not(.flat):hover{border-color:var(--rule-strong);background:var(--surface-3)}
.opskey.flat{cursor:default}
.opskey i{width:9px;height:9px;border-radius:var(--rp);flex:none}
.opskey i.hollow{background:none;border:1.5px dashed var(--ink-3)}
.opskey b{font-weight:var(--w-semi)}
.ops-note{
  padding:0 var(--s5) var(--s5);color:var(--ink-3);max-width:82ch;line-height:var(--lh-ui);margin:0;
}

/* ============================================================
   TODAY'S PLAN
   ============================================================ */
.plan-card{overflow:hidden}
.plan{list-style:none;margin:0;padding:0 var(--s5) var(--s3)}
.plan-row{
  display:grid;grid-template-columns:52px 22px 1fr auto;gap:0 var(--s3);
  align-items:start;padding:var(--s3) 0;position:relative;
}
.plan-t{font-size:var(--t-meta);font-weight:var(--w-bold);padding-top:1px;color:var(--ink)}
.plan-row.yard .plan-t,.plan-row.yard .plan-j{color:var(--ink-2);font-weight:var(--w-semi)}
/* the rail is one continuous line with a node on it, which is what
   makes a list of times read as a day rather than as rows */
.plan-rail{position:relative;display:block;align-self:stretch}
.plan-rail::before{
  content:"";position:absolute;left:50%;top:-14px;bottom:-14px;width:1px;
  background:var(--rule-strong);transform:translateX(-.5px);
}
.plan-row:first-child .plan-rail::before{top:9px}
.plan-row:last-child .plan-rail::before{bottom:auto;height:9px}
.plan-rail i{
  position:absolute;left:50%;top:5px;width:9px;height:9px;border-radius:var(--rp);
  transform:translateX(-50%);box-shadow:0 0 0 3px var(--surface);
}
.plan-b{min-width:0}
.plan-w{font-size:var(--t-tiny);letter-spacing:var(--ls-caps);text-transform:uppercase;color:var(--ink-3);
  font-weight:var(--w-semi);display:block}
.plan-j{display:block;font-size:var(--t-body);font-weight:var(--w-semi);margin-top:2px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.plan-m{display:flex;align-items:center;gap:8px;margin-top:6px}
.faces{display:flex}
.faces .avatar{margin-left:-6px;border:2px solid var(--surface);background:var(--surface-3);
  color:var(--ink-2);font-style:normal}
.faces .avatar:first-child{margin-left:0}
.faces .avatar.more{background:var(--surface-3);color:var(--ink-3)}
.plan-go{width:34px;height:34px;align-self:center}
.plan-more{border-top:1px solid var(--rule);border-radius:0;height:46px}

/* the dashboard's two-column spine: the picture, then the plan */
.opsgrid{display:grid;grid-template-columns:minmax(0,1.9fr) minmax(320px,1fr);
  gap:var(--s5);align-items:start;margin-bottom:var(--s5)}
@media (max-width:1180px){.opsgrid{grid-template-columns:1fr}}

/* ============================================================
   THE MONEY
   ============================================================ */
.moneyrow{display:grid;grid-template-columns:repeat(auto-fit,minmax(172px,1fr));gap:var(--s3)}
.moneyc{
  padding:var(--s4) var(--s4) var(--s5);text-align:left;display:flex;flex-direction:column;
  gap:6px;transition:border-color 130ms var(--ease),background 130ms var(--ease),transform 130ms var(--ease);
}
.moneyc:hover{border-color:var(--rule-strong);background:var(--surface-3);transform:translateY(-2px)}
.money-v{font-size:var(--t-page);letter-spacing:var(--ls-fig);font-weight:var(--w-bold)}
.moneyc.flag .money-v{color:var(--warn)}
.moneyc.flag{border-left:2px solid var(--warn)}

/* the four-across KPI row */
.today.kpi4{grid-template-columns:repeat(4,minmax(0,1fr))}
@media (max-width:900px){.today.kpi4{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* the plan heading and its meta must not fight for one narrow
   column — the heading owns its line */
.plan-card .sec-head{flex-direction:column;align-items:flex-start;gap:2px}
.plan-row.yard .plan-rail i{background:var(--ink-3)!important;width:7px;height:7px}
/* a job name is allowed two lines in a narrow column — truncating
   "Guildford Chimney Scaff…" tells nobody which job it is */
.plan-j{white-space:normal;overflow:visible;line-height:var(--lh-heading);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}

/* ============================================================
   THE JOB BOARD
   ============================================================ */
.board-bar{
  display:flex;align-items:center;gap:var(--s4);flex-wrap:wrap;margin-bottom:var(--s3);
}
.board-bar .chips{flex:1 1 420px;min-width:0}
.jsearch{
  display:flex;align-items:center;gap:8px;flex:0 1 340px;min-width:220px;
  background:var(--surface-2);border:1px solid var(--rule);border-radius:var(--rp);
  padding:0 6px 0 16px;height:44px;color:var(--ink-3);
  transition:border-color 130ms var(--ease),background 130ms var(--ease);
}
.jsearch:focus-within{border-color:var(--int);background:var(--surface);
  box-shadow:0 0 0 3px var(--int-glow)}
.jsearch .inp{
  border:0;background:none;height:auto;padding:0;flex:1;min-width:0;
  box-shadow:none!important;color:var(--ink);
}
.jsearch .inp:focus{box-shadow:none!important;background:none}
.jsearch .inp::-webkit-search-cancel-button{display:none}
.jsearch .icobtn.sm{width:32px;height:32px;flex:none}

.jboard{display:flex;flex-direction:column;gap:var(--s3)}
.jcard.card,.jcard.card.click{
  display:grid;
  grid-template-columns:92px minmax(0,1fr) auto;
  gap:var(--s3) var(--s5);
  align-items:start;
  padding:var(--s4) var(--s5);
  text-align:left;width:100%;
}
.jc-when{display:flex;flex-direction:column;gap:2px;min-width:0}
.jc-when .fig{font-size:var(--t-body);letter-spacing:var(--ls-fig)}
.jc-main{min-width:0}
.jc-t{font-size:var(--t-input);font-weight:var(--w-semi);letter-spacing:var(--ls-0);line-height:var(--lh-heading);margin:0}
.jc-sub{font-size:var(--t-meta);color:var(--ink-2);margin-top:2px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.jc-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:12px}
.jc-right{display:flex;flex-direction:column;align-items:flex-end;gap:2px;text-align:right;flex:none}
.jc-v{font-size:var(--t-title);letter-spacing:var(--ls-fig)}
.jc-probs{grid-column:1/-1;display:flex;gap:6px;flex-wrap:wrap;align-items:center;
  padding-top:var(--s3);border-top:1px solid var(--rule)}
/* "no gang yet" is a warning, and it says so in words as well as
   in amber — the board has to work for somebody who cannot
   separate the two colours */
.warnable{color:var(--warn);font-weight:var(--w-semi)}
.tag.warnable{border-color:var(--warn-line);background:var(--warn-bg);color:var(--warn)}
.tag.tiny{font-size:var(--t-tiny);padding:2px 8px;height:auto}

@media (max-width:760px){
  .jcard.card,.jcard.card.click{grid-template-columns:1fr auto;gap:var(--s2) var(--s3);padding:var(--s4)}
  .jc-when{grid-column:1/-1;flex-direction:row;align-items:baseline;gap:8px}
  .jc-right{align-items:flex-end}
  .jsearch{flex:1 1 100%}
}
/* the value column sits with the job, not adrift at the bottom of
   a tall row */
.jcard.card{align-items:center}
.jc-when,.jc-right{align-self:start}
.jc-ref{font-variant-numeric:tabular-nums;letter-spacing:var(--ls-0)}

/* ============================================================
   THE DISPATCH DESK
   ============================================================ */
/* ============================================================
   THE PLANNING DESK — ROOM TO READ IT

   "Clean the spacing up in this screen and every other screen."

   Three columns of dense operational information at sixteen
   pixels apart read as one column of noise: the eye has nothing
   to tell it where the free men stop and the gangs begin. The
   gutter between distinct areas is now the same step the rest of
   the app uses between sections, and the gang cards — the busiest
   thing on the screen, five or six facts in a box — get the card
   padding rather than the tight one.

   Nothing here is simply bigger. The chips, the day strip and the
   tabs are deliberately close together because they are one
   control each, and they have not moved. What moved is the space
   BETWEEN things that are not related to each other, which is the
   only kind of space that helps somebody scan.
   ============================================================ */
.desk{
  display:grid;grid-template-columns:300px minmax(0,1fr) 300px;
  gap:var(--s5);align-items:start;
}
@media (max-width:1400px){.desk{grid-template-columns:280px minmax(0,1fr)}
  .desk-r{grid-column:1/-1;display:grid;grid-template-columns:1fr 1fr;gap:var(--s4)}
  .desk-r>*+*{margin-top:0!important}}
@media (max-width:1000px){.desk{grid-template-columns:1fr}
  .desk-r{grid-template-columns:1fr}}

.gangs{display:grid;grid-template-columns:repeat(auto-fill,minmax(290px,1fr));gap:var(--s3)}
.gangc{padding:var(--s5);display:flex;flex-direction:column;gap:var(--s4)}
.gangc-h{display:flex;align-items:center;gap:12px}
/* the gang letter is the thing the yard calls them by, so it is the
   thing the eye lands on */
.gangc-l{
  width:34px;height:34px;border-radius:var(--r2);flex:none;
  display:grid;place-items:center;font-weight:var(--w-bold);font-size:var(--t-body);
  background:var(--surface-3);border:1px solid var(--rule-strong);color:var(--ink);
}
.gangc-n{font-size:var(--t-body);font-weight:var(--w-semi);letter-spacing:var(--ls-0);display:block}
.gangc-v{font-size:var(--t-lead);letter-spacing:var(--ls-fig);flex:none}
/* Overridden by the roster grid further down this file — kept as the
   fallback shape for anything that draws a .gangc-p outside a card. */
.gangc-p{display:flex;flex-wrap:wrap;gap:6px}
.gperson{
  display:inline-flex;align-items:center;gap:6px;padding:4px 12px 4px 4px;
  border-radius:var(--rp);background:var(--surface-3);border:1px solid var(--rule);
  font-size:var(--t-small);font-weight:var(--w-semi);min-height:34px;
  transition:border-color 120ms var(--ease),background 120ms var(--ease);
}
.gperson:hover{border-color:var(--rule-strong);background:var(--surface-4)}
.gperson .avatar{width:24px;height:24px;font-size:var(--t-micro)}
.gperson em{font-style:normal;font-size:var(--t-micro);letter-spacing:var(--ls-caps);text-transform:uppercase;
  font-weight:var(--w-bold);padding:1px 4px;border-radius:var(--r1)}
.gsup{background:var(--int-soft);color:var(--int-hi)}
.goff{background:var(--warn-bg);color:var(--warn)}
.gperson.off{opacity:.62}
.gperson.off .avatar{background:var(--surface-4);color:var(--ink-3)}
.gangc-cap{display:flex;align-items:center;gap:12px}
.gangc-cap .meter{flex:1;min-width:0}
.gangc-j{list-style:none;margin:0;padding:0;border-top:1px solid var(--rule);padding-top:var(--s3)}
.gangc-j li+li{margin-top:4px}
.gangc-j button{
  display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  padding:8px 8px;border-radius:var(--r1);min-height:40px;
  transition:background 120ms var(--ease);
}
.gangc-j button:hover{background:var(--surface-3)}
.gj-t{flex:1;min-width:0;font-size:var(--t-meta);font-weight:var(--w-semi);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.gangc-j .ic{color:var(--ink-3);flex:none}
.gangc-e{border-top:1px solid var(--rule);padding-top:var(--s3);margin:0}

.waitj{
  display:flex;align-items:center;gap:var(--s3);width:100%;text-align:left;
  padding:12px 12px;border-radius:var(--r2);background:var(--surface-3);
  border:1px solid var(--rule);min-height:52px;
  transition:border-color 120ms var(--ease),background 120ms var(--ease);
}
.waitj:hover{border-color:var(--rule-strong);background:var(--surface-4)}
.waitj b{font-size:var(--t-meta);font-weight:var(--w-semi);display:block;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.waitw{display:flex;flex-direction:column;gap:2px;align-items:flex-end;flex:none}
.waitw em{font-style:normal;font-size:var(--t-micro);letter-spacing:var(--ls-caps);text-transform:uppercase;
  font-weight:var(--w-bold);color:var(--warn);background:var(--warn-bg);
  border:1px solid var(--warn-line);padding:2px 6px;border-radius:var(--r1);white-space:nowrap}

.dsum{display:flex;flex-direction:column;gap:var(--s4);margin-top:var(--s3)}
.dsum .fig{font-size:var(--t-page);letter-spacing:var(--ls-fig);display:block;margin-top:2px}
.warnrow{display:flex;align-items:center;gap:8px}

/* ============================================================
   THE PERSON BAND
   ============================================================ */
.pband{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));
  gap:1px;background:var(--rule);border:1px solid var(--rule);
  border-radius:var(--r3);overflow:hidden;margin-bottom:var(--s4);
}
.pband-c{
  background:var(--surface);padding:var(--s4) var(--s5) var(--s5);
  display:flex;flex-direction:column;gap:4px;text-align:left;
  transition:background 130ms var(--ease);position:relative;
}
button.pband-c:hover{background:var(--surface-2)}
.pband-v{font-size:var(--t-figure);letter-spacing:var(--ls-fig);line-height:var(--lh-solid)}
.pband-c.flag::before{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--warn)}
.pband-c.flag .pband-v{color:var(--warn)}

/* ============================================================
   THE FIELD APP

   Everything here is sized for a gloved thumb on a wet screen.
   The one rule: if he has to press it before he sets off, it is
   the width of the phone.
   ============================================================ */
.fgreet{
  font-size:var(--t-body);color:var(--ink-2);font-weight:var(--w-semi);margin:0 0 var(--s1);
}
.fhero{
  background:var(--pane-hero);
  border:1px solid var(--rule-strong);
  border-radius:var(--r5);
  padding:var(--s5);
  box-shadow:var(--lift-2),var(--sh2);
  position:relative;overflow:hidden;
}
/* one soft light in the corner, so the card that matters most is
   the one carrying the light */
.fhero::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(420px 200px at 88% -12%,var(--int-soft),transparent 66%);
}
.fhero>*{position:relative}
.fhero-top{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s3)}
.fhero-t{font-size:var(--t-title);letter-spacing:var(--ls-2);color:var(--int-hi)}
.fhero-w{font-size:var(--t-figure);letter-spacing:var(--ls-6);font-weight:var(--w-bold);line-height:var(--lh-solid);margin:var(--s3) 0 0}
.fhero-c{font-size:var(--t-body);color:var(--ink-2);margin-top:4px;font-weight:var(--w-semi)}
.fhero-j{font-size:var(--t-body);font-weight:var(--w-semi);margin-top:8px;line-height:var(--lh-ui)}
.fhero-a{font-size:var(--t-meta);color:var(--ink-2);margin-top:4px;line-height:var(--lh-ui)}
.fhero-crew{margin-top:var(--s4);padding-top:var(--s4);border-top:1px solid var(--rule)}
.fcrew{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.fperson{
  display:inline-flex;align-items:center;gap:6px;padding:4px 12px 4px 4px;
  border-radius:var(--rp);background:var(--surface-3);border:1px solid var(--rule);
  font-size:var(--t-meta);font-weight:var(--w-semi);min-height:36px;
}
.fperson em{font-style:normal;font-size:var(--t-micro);letter-spacing:var(--ls-caps);text-transform:uppercase;
  font-weight:var(--w-bold);padding:1px 6px;border-radius:var(--r1);
  background:var(--int-soft);color:var(--int-hi)}
.fhero-go{margin-top:var(--s5)}
/* the one button he presses in the rain */
.fbig{height:58px;font-size:var(--t-lead);border-radius:var(--r3);gap:12px}
.fhero-v{display:block;text-align:center;margin-top:12px}

.fnext{margin-top:0}
.fnextj{
  display:flex;align-items:center;gap:var(--s3);width:100%;text-align:left;
  padding:12px 16px;border-radius:var(--r2);background:var(--surface-3);
  border:1px solid var(--rule);min-height:60px;
}
.fnextj:active{background:var(--surface-4)}
.fnextj b{font-size:var(--t-body);font-weight:var(--w-semi);display:block}
.fnextj .ic{color:var(--ink-3);flex:none}

/* the driver's run */
.fhero-run .frun{
  display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s3);
  margin-top:var(--s5);padding-top:var(--s4);border-top:1px solid var(--rule);
}
.frun>div{display:flex;flex-direction:column;gap:2px}
.frun .fig{font-size:var(--t-page);letter-spacing:var(--ls-fig)}
.frun span{font-size:var(--t-tiny);color:var(--ink-3);letter-spacing:var(--ls-caps);text-transform:uppercase;font-weight:var(--w-semi)}
.fstops{list-style:none;margin:0;padding:0}
.fstop{
  display:flex;align-items:center;gap:var(--s3);
  padding:var(--s4);border:1px solid var(--rule);border-radius:var(--r3);
  background:var(--surface);margin-top:var(--s2);min-height:72px;
}
.fstop.yard{background:var(--surface-2)}
.fs-n{
  width:38px;height:38px;border-radius:var(--rp);flex:none;display:grid;place-items:center;
  background:var(--surface-3);border:1px solid var(--rule-strong);
  font-weight:var(--w-bold);font-size:var(--t-body);color:var(--ink);
}
.fstop.yard .fs-n{color:var(--ink-2)}
.fs-b{flex:1;min-width:0}
.fs-b b{font-size:var(--t-body);font-weight:var(--w-semi);display:block}
.fs-go{width:44px;height:44px;flex:none}

/* ============================================================
   THE FLOATING BOTTOM NAVIGATION

   Lifted off the bottom edge and rounded, so it reads as a
   control the app is holding rather than a strip glued to the
   glass — and so the home indicator has somewhere to be.
   ============================================================ */
.mob-tabs,.mbar{
  left:var(--s3);right:var(--s3);
  bottom:calc(var(--s3) + var(--sab));
  border-radius:var(--r5);
  border:1px solid var(--rule-strong);
  /* Solid. This was translucent with a 22px blur and a 170%
     saturation boost behind it, so whatever scrolled underneath
     showed through the bar as a moving, over-coloured smear and
     the five labels lost contrast exactly when somebody was
     scrolling — which is when they are reading them. Flattening
     it in 12-modules.css did nothing, because this rule is later
     and won. A bar a man taps with a glove on is one colour. */
  background:var(--surface-2);
  box-shadow:var(--sh2),var(--lift-2);
  padding-bottom:0;
  height:auto;padding-top:8px;padding-bottom:8px;
}
.phone.bare .mob-tabs{padding-bottom:8px}
.mtab,.mbtab{
  min-height:52px;border-radius:var(--r3);gap:4px;
  transition:color 120ms var(--ease),background 120ms var(--ease);
}
.mtab.on,.mbtab.on,html[data-theme="dark"] .mtab.on{color:var(--int-hi)}
.mtab.on::before,.mbtab.on::before{
  content:"";position:absolute;inset:2px;border-radius:var(--r3);
  background:var(--int-soft);z-index:-1;
}
.mtab,.mbtab{position:relative;z-index:0}
/* The body has to clear a bar that now floats — and the bar is
   taller than it was, because it is lifted off the edge and the
   home indicator sits under it. `.mob-body` already set a
   padding-bottom, so this has to out-specify it rather than sit
   alongside and lose. */
.mshell{padding-bottom:calc(96px + var(--sab))}
.phone .mob-body,.appmain .mob-body,.mob-body{
  padding-bottom:calc(108px + var(--sab));
}
.phone.bare .mob-tabs{padding-bottom:8px;padding-top:8px}
/* the day the field app is showing, when it is not today */
.fahead{
  display:flex;align-items:center;gap:8px;margin:0 0 var(--s2);
  padding:12px 12px;border-radius:var(--r2);
  background:var(--warn-bg);border:1px solid var(--warn-line);color:var(--warn);
  font-size:var(--t-meta);font-weight:var(--w-semi);
}
.fahead b{font-weight:var(--w-bold)}
.fahead .ic{flex:none}

/* ============================================================
   G — THE COMMAND PANEL

   Not a chat bubble. A command control: it opens from the top of
   the workspace, it is the width of a sentence rather than the
   width of the screen, and the box you type in is the first thing
   in it. The examples underneath are pressable, because the
   fastest way to learn what a command bar can do is to press
   something and watch.
   ============================================================ */
.gpanel{
  border-radius:var(--r5);
  background:var(--surface-2);
  border:1px solid var(--rule-strong);
  box-shadow:var(--sh3),var(--lift-2),0 0 0 1px var(--int-line);
}
.gwrap .gscrim,.gscrim{background:var(--scrim)}
.ghead-t{letter-spacing:var(--ls-2)}
.gbar{
  border-radius:var(--r3);background:var(--surface-3);
  border:1px solid var(--rule-strong);
}
.gbar:focus-within{border-color:var(--int);box-shadow:0 0 0 3px var(--int-glow)}
.gsug-b,.glist-i{
  border-radius:var(--r2);
  transition:background 120ms var(--ease),border-color 120ms var(--ease);
}
.gsug-b:hover,.glist-i:hover{background:var(--surface-4)}
/* the trigger in the topbar: a control, lit only when it has
   something to say */
.gbtn{
  border-radius:var(--rp);border:1px solid var(--rule-strong);
  background:var(--surface-2);
  transition:border-color 130ms var(--ease),background 130ms var(--ease),
    box-shadow 130ms var(--ease);
}
.gbtn:hover{border-color:var(--int-line);background:var(--surface-3);
  box-shadow:0 0 0 3px var(--int-soft)}
/* On the phone the G sits in the navy top bar beside search and +, so it
   is drawn like them — white on the bar — not as a pale pill with the
   white G invisible on it. */
.gbtn.mob{background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.22)}
.gbtn.mob:hover{background:rgba(255,255,255,.2);border-color:rgba(255,255,255,.3)}

/* ============================================================
   MODALS AND SHEETS
   ============================================================ */
.sheet{
  background:var(--surface);
  border:1px solid var(--rule-strong);
}
.sheet.modal{border-radius:var(--r5);box-shadow:var(--sh3),var(--lift-2)}
.sheet-head{
  background:var(--surface-2);
  border-bottom:1px solid var(--rule);
  border-radius:var(--r5) var(--r5) 0 0;
  color:var(--ink);
}
.sheet-head::after{display:none}
.sheet-head .eyebrow{color:var(--ink-3)}
.sheet-head .lede{color:var(--ink-2)}
.sheet-head .icobtn{color:var(--ink-2)}
.sheet-head .icobtn:hover{background:var(--surface-3);color:var(--ink)}
.sheet-body{background:var(--surface)}
.sheet-foot{
  background:var(--surface-2);border-top:1px solid var(--rule);
  border-radius:0 0 var(--r5) var(--r5);
}
/* NOT sticky. The sheet is a flex column with a scrolling body, so
   the footer is already pinned to the bottom by the layout —
   adding position:sticky changed nothing visually and created a
   stacking context that swallowed clicks aimed at the last control
   in the body. The regression suite caught it: a button that was
   "visible, enabled and stable" and could not be pressed 57 times
   in a row. */
.scrim{background:var(--scrim);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}
.sheet.drawer{border-radius:0;border-right:0;border-top:0;border-bottom:0}
.steps{background:var(--surface-2);border-bottom:1px solid var(--rule)}

/* ------------------------------------------------------------
   THE PLANNER'S ACTION ROW ON A TABLET

   Four buttons and a week stepper across the top of the planner
   fit on a laptop and ran 12px off the right of an iPad. The row
   was `.row`, which does not wrap.

   A page's actions wrap. That is true of every masthead in the
   system, so it is set on the masthead rather than patched here
   for one screen.
   ------------------------------------------------------------ */
.page-head>.row{flex-wrap:wrap;justify-content:flex-end;min-width:0}
.page-head>.row>*{flex:0 1 auto;min-width:0}
@media (max-width:820px){
  .page-head{gap:var(--s3)}
  .page-head>.row{width:100%}
  .page-head>.row>.btn{flex:1 1 auto}
}

/* ============================================================
   DEPTH — build 15's interaction layer, on the dark ground

   Lewis asked for these back by name. They are the friend's work,
   brought forward and retuned: on a light glass panel a 26% white
   highlight is a soft sheen, and on a near-black one it is a
   flashbulb. The values are the same effects at the strength this
   surface can take.

   All of it is gated on a real cursor and on motion being allowed.
   A phone has no pointer to follow, and spending the work there
   would be spending it for nobody.
   ============================================================ */

/* ---- the light under the cursor on the day board ---- */
.today{position:relative;overflow:hidden}
.today::after{
  content:"";position:absolute;inset:0;pointer-events:none;opacity:0;
  transition:opacity .35s var(--ease);
  background:radial-gradient(360px 240px at var(--mx,50%) var(--my,50%),
    rgba(22,139,255,.16),rgba(22,139,255,.05) 45%,transparent 66%);
}
.today:hover::after{opacity:1}

@media (hover:hover) and (prefers-reduced-motion:no-preference){
  .card.click,.card.hov,.att,.jcard,.rolecard,.gangc,.moneyc{position:relative}

  /* the card leans towards the cursor — under two degrees, which is
     felt as depth rather than seen as a trick.
     27 October: 51-motion.js writes the lean, the lift and the press as one
     inline transform on every frame, on a spring, and that inline style is
     what shows. All this rule does now is keep the transform out of the
     card's own transitions while it moves. It used to switch every
     transition off (transition:none), so the lean and the lift landed in
     one frame and a leaning card's border and shadow changed in one frame
     as well. A press keeps the same list (the press rule below would
     otherwise put the transform back into it). The transform here is only
     the resting one: the lift is the script's to give, a frame at a time,
     and this rule used to hand it over in a single frame. */
  .tilting,html .tilting.pressing{
    transition-property:background-color,border-color,box-shadow,color,opacity!important;
    transform:perspective(950px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg))!important;
  }

  /* and a sheen crosses it. On a light panel this was 30% white;
     here it is 9%, because the eye reads brightness against its
     ground and this ground is #0D111C. */
  .glare{
    position:absolute;inset:0;border-radius:inherit;overflow:hidden;
    pointer-events:none;opacity:0;transition:opacity .22s var(--ease);z-index:0;
  }
  .glare>i{
    position:absolute;top:-40%;left:-40%;width:80%;height:180%;display:block;
    background:radial-gradient(closest-side,
      rgba(255,255,255,.09),rgba(255,255,255,.035) 45%,transparent 72%);
    will-change:transform;
  }
  html[data-theme="light"] .glare>i{
    background:radial-gradient(closest-side,
      rgba(255,255,255,.62),rgba(255,255,255,.22) 45%,transparent 72%);
  }
  .tilting .glare{opacity:1}
  /* the card's own content sits above the sheen */
  .card.click>*:not(.glare),.card.hov>*:not(.glare),
  .jcard>*:not(.glare),.att>*:not(.glare){position:relative;z-index:1}
}

/* ---- the press lands before the render does ----
   Applied on pointerdown rather than on click, so the control
   answers the thumb in the same frame it is touched instead of
   waiting for the screen to rebuild. */
.pressing{transform:scale(.978) translateZ(0)!important;
  transition:transform .07s cubic-bezier(.2,0,0,1)!important}
.btn.pressing{transform:scale(.96) translateZ(0)!important}
.nav-item.pressing,.mbtab.pressing,.mtab.pressing{transform:scale(.97) translateZ(0)!important}
.today-c.pressing,.jcard.pressing{transform:scale(.992) translateZ(0)!important}

/* each section isolated, so one panel rebuilding cannot invalidate
   the layout of the whole page.
   27 October: layout and style only. Paint containment clips everything
   inside a section to the section's own box, so a card in the top row of
   a grid had its top edge sliced off when it lifted under the pointer,
   every card's shadow stopped dead at the edge of its grid, and anything
   drawn past a section's edge (a focus ring, a picked card's ring) was cut
   short. Layout containment keeps the isolation this rule was for. */
.page>*{contain:layout style}

/* ============================================================
   THE MEASUREMENT READ-OUT

   Four figures in a row: the sum, the quantity it produces, the
   rate, and the money. The quantity and the money are the two
   somebody checks, so they are the two that are lit.
   ============================================================ */
.mout{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));
  gap:1px;background:var(--rule);
  border:1px solid var(--rule);border-radius:var(--r2);
  overflow:hidden;margin-top:var(--s3);
}
.mout-r{background:var(--surface-2);padding:12px 12px;display:flex;flex-direction:column;gap:4px}
.mout-r .fig{font-size:var(--t-lead);letter-spacing:var(--ls-fig)}
.mout-q{color:var(--int-hi)}
.mout-v{color:var(--ok)}
.mout-wait{
  margin-top:var(--s3);padding:12px 12px;border-radius:var(--r2);
  background:var(--surface-3);border:1px dashed var(--rule-strong);
}
/* a field with a problem: an amber ring AND the reason in words
   under it, never the ring on its own */
.field.bad .inp{border-color:var(--warn);box-shadow:0 0 0 3px var(--warn-bg)}

/* ------------------------------------------------------------
   THE CODE SCREEN

   The input is present for the software keyboard and for a screen
   reader, and hidden from the eye: at a desk you type and the dots
   fill, which is what somebody at a keyboard expects. It stays
   focusable and it stays in the accessibility tree — this is
   `.sr`, not `display:none`.
   ------------------------------------------------------------ */
.pinentry.sr{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;
}
.pintype{display:none}
/* the keys answer a keystroke as well as a tap */
.pinkey{transition:background 120ms var(--ease),transform 90ms var(--ease)}
.pinkey:active,.pinkey.lit{transform:scale(.94);background:var(--surface-3)}

/* ============================================================
   SPACING — two things that measured badly

   "The spacing is all wrong" is a hard report to act on, so both
   of these were found by measuring rather than by eye.
   ============================================================ */

/* ------------------------------------------------------------
   1. THE DAY SUMMARY

   Three figures were taking 253 pixels. Each row measured 71–80px
   for about 48px of actual type — the rest was line-height on an
   uppercase label and a caption, plus a 16px gap on top of that.
   A number with two words round it does not need eighty pixels,
   and three of them stacked is what made the column look padded
   out to fill space it had not earned.
   ------------------------------------------------------------ */
.dsum{gap:var(--s4)}
.dsum>div{display:flex;flex-direction:column;gap:2px}
.dsum .eyebrow{line-height:var(--lh-display)}
.dsum .fig{font-size:var(--t-page);line-height:var(--lh-solid);margin-top:2px}
.dsum .meter{margin-top:6px!important}
.dsum .tiny{line-height:var(--lh-heading)}

/* ------------------------------------------------------------
   2. THE ROSTER OF WHO IS FREE

   Twelve people came out as twelve chips at six different widths
   — 93, 100, 104, 107, 110 and 112 — wrapping into a ragged
   block. Names are different lengths, so a flex wrap will always
   do that, and no amount of gap tuning fixes it.

   A roster is a list of people, so it is set as a grid: equal
   columns, one name each, aligned down the left. It reads as a
   crew rather than as a paragraph of chips.
   ------------------------------------------------------------ */
.pool{
  display:grid;
  /* 132px was too wide for a 300px rail: auto-fill could only fit
     one column, so twelve people became a 544px vertical list —
     worse than the ragged wrap it replaced. 104 gives two columns
     in the rail and more where there is room. */
  grid-template-columns:repeat(auto-fill,minmax(104px,1fr));
  gap:var(--s2);
  align-items:stretch;
}
.pool>*{width:100%;min-width:0;justify-content:flex-start}
.pchip{
  display:flex;align-items:center;gap:8px;
  padding:4px 12px 4px 4px;min-height:38px;border-radius:var(--rp);
  background:var(--surface-3);border:1px solid var(--rule);
  font-size:var(--t-small);font-weight:var(--w-semi);
  overflow:hidden;
}
.pchip>span,.pchip>b{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.pchip .avatar{flex:none}

/* the same grid wherever a roster of people appears */
.gangc-p{display:grid;grid-template-columns:repeat(auto-fill,minmax(112px,1fr));gap:6px}
.gangc-p>*{width:100%;justify-content:flex-start}

/* ============================================================
   FUEL
   ============================================================ */
.fuelgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:1px;
  background:var(--rule);border:1px solid var(--rule);border-radius:var(--r2);overflow:hidden}
.fuelgrid.big{border-radius:var(--r3)}
.fuelc{background:var(--surface-2);padding:var(--s4);display:flex;flex-direction:column;gap:4px}
.fuelc .fig{font-size:var(--t-page);line-height:var(--lh-solid);margin-top:2px}
.fuelgrid.big .fuelc .fig{font-size:var(--t-figure)}
.fuelc .fig.ppm{color:var(--int-hi)}
.goodable{color:var(--ok);font-weight:var(--w-semi)}
/* the photo, which is the first thing he does */
.fxshot{
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;
  min-height:132px;border-radius:var(--r3);cursor:pointer;position:relative;overflow:hidden;
  background:var(--surface-3);border:1px dashed var(--rule-strong);color:var(--ink-2);
  transition:border-color 130ms var(--ease),background 130ms var(--ease);
}
.fxshot:hover{border-color:var(--int-line);background:var(--surface-4)}
.fxshot.has{border-style:solid;padding:0;min-height:0}
.fxshot img{width:100%;display:block;max-height:280px;object-fit:contain;background:var(--paper-2)}
.fxshot-x{position:absolute;top:8px;right:8px;width:32px;height:32px;border-radius:var(--rp);
  display:grid;place-items:center;background:var(--scrim);color:#fff}
.fxone{display:flex;align-items:center;gap:12px;padding:12px 16px;border-radius:var(--r2);
  background:var(--surface-3);border:1px solid var(--rule);min-height:52px}
.fxconfirm{display:flex;align-items:center;gap:12px;padding:12px 16px;border-radius:var(--r2);
  background:var(--warn-bg);border:1px solid var(--warn-line);color:var(--warn);
  font-size:var(--t-meta);font-weight:var(--w-semi);cursor:pointer;min-height:48px}
.fxmore{border-top:1px solid var(--rule);padding-top:var(--s3)}
.fxmore summary{cursor:pointer;font-size:var(--t-meta);font-weight:var(--w-semi);color:var(--ink-2);
  padding:8px 0;list-style:none}
.fxmore summary::-webkit-details-marker{display:none}
.fxmore summary::before{content:"+ ";color:var(--ink-3)}
.fxmore[open] summary::before{content:"− "}
.fuelfield .fbig{height:56px}
/* the currency mark belongs beside the box, not stacked above it */
.field .row{flex-direction:row;align-items:center;flex-wrap:nowrap}
.field .row>.inp{flex:1;min-width:0}
/* ============================================================
   MOBILE AUDIT FIXES

   Everything below was found by measurement during the mobile
   audit and each block says what it found and what the number
   was. They are kept verbatim through the command-centre
   redesign because the faults they fix are structural — safe
   areas, flex minimums, scroll locking — and none of them are
   opinions about how the app should look.
   ============================================================ */

/* ============================================================
   OVERLAYS ON A PHONE

   Three separate faults, one section, because they are all the
   same mistake: the glass layer restates desktop geometry after
   the phone rules have already changed it, and CSS gives the
   later rule the argument.
   ============================================================ */

/* ------------------------------------------------------------
   1. MODALS OPENING HALF A SCREEN TO THE LEFT

   On a phone a modal is a bottom sheet: it is pinned to the left
   edge, it is the full width of the screen, and it slides up. The
   glass layer then set `transform:translate(-50%,-50%)` on
   `.sheet.modal.in` to centre it the desktop way. Both selectors
   carry three classes, so the later one won — and a full-width
   sheet moved left by half its own width puts 196 of its 393
   pixels off the side of the screen. That is the "cut off on the
   left" fault, and it is measured: left:-196 at 393px wide.

   Only PLAIN modals were affected. `.sheet.modal.wide.in` has a
   fourth class, so the wide ones outranked the glass rule and
   landed correctly — which is why the fault looked intermittent.
   ------------------------------------------------------------ */
@media (max-width:720px){
  .sheet.modal,
  .sheet.modal.wide,
  .sheet.modal.xwide{
    top:auto;bottom:0;left:0;right:auto;
    width:100vw;max-width:100vw;
    max-height:calc(100dvh - var(--sat) - 8px);
    border-radius:var(--r5) var(--r5) 0 0;
    transform:translateY(100%)!important;
  }
  .sheet.modal.in,
  .sheet.modal.wide.in,
  .sheet.modal.xwide.in{transform:translateY(0)!important}

  /* the spring is vertical now, not a scale from the middle */
  /* V1: no overshoot. Nothing threw this sheet, so it settles
     like a critically damped spring and leaves the way it came. */
  .sheet.modal{transition:transform .38s var(--ease-out),opacity .22s var(--ease)}
  .sheet.modal:not(.in){transition:transform .26s var(--ease),opacity .16s var(--ease)}

  /* a full-width drawer has no left edge to round or line */
  .sheet.drawer,.sheet.drawer.wide{width:100vw;border-left:0;border-radius:0}
  .sheet.drawer .sheet-head{border-radius:0}
  .sheet-foot{border-radius:0}
}

/* ------------------------------------------------------------
   2. THE HEADING SITTING UNDER THE CLOCK

   A drawer is the full height of the screen, so its header starts
   at y=0 — underneath the status bar. The title was painted at
   18px and the close button at 16px on a handset whose top inset
   is 59px, which is why "Everything else" appeared to overlap the
   time and the close button sat among the status icons.

   The padding is added to whatever the header already has rather
   than replacing it, so the inset is clearance on top of the
   design's own spacing instead of a substitute for it.
   ------------------------------------------------------------ */
.sheet.drawer .sheet-head{padding-top:calc(var(--s5) + var(--sat))}
.sheet.drawer .steps{padding-top:calc(var(--s3) + var(--sat))}
.sheet.drawer .steps + .sheet-head{padding-top:var(--s5)}
@media (max-width:720px){
  .sheet.drawer .sheet-head{padding-top:calc(var(--s4) + var(--sat))}
  .sheet.drawer .steps{padding-top:calc(var(--s3) + var(--sat))}
  .sheet.drawer .steps + .sheet-head{padding-top:var(--s4)}

  /* A bottom sheet tall enough to reach the top needs the same
     clearance. It only reaches the top when its content is tall,
     so the max-height above leaves 8px of scrim showing and the
     header cannot arrive under the inset. */
  .sheet.modal{padding-top:0}
}

/* the close button is a 44px target with a 36px face */
.sheet-head .icobtn{min-width:44px;min-height:44px}

/* ------------------------------------------------------------
   3. THE BOTTOM BAR WITH NO CLEARANCE

   `.mbar` sets `padding-bottom:var(--sab)` and then, four lines
   later in the same block, `padding-bottom:0`. The second one
   wins, so on any handset with a home indicator the tab labels
   ran underneath it. It reads like a paste that brought a line
   with it; the intent is plainly the first declaration.
   ------------------------------------------------------------ */
.mbar{padding-bottom:var(--sab)}

/* ------------------------------------------------------------
   4. TAP TARGETS INSIDE SHEETS

   Fields inside a sheet were 40px. The brief is 44, and 44 is
   also what a gloved thumb on a site actually hits.
   ------------------------------------------------------------ */
@media (max-width:720px),(hover:none){
  .sheet .inp,.sheet .sel,.sheet select,.sheet input,.sheet textarea{min-height:44px}
  .sheet .btn,.sheet button{min-height:44px}
  .sheet .btn.btn-s.sm,.sheet .btn.sm{min-height:44px;padding-top:0;padding-bottom:0}
  /* except the ones that are deliberately inline in a line of text */
  .sheet .lnk,.sheet .btn.link{min-height:0}
}

/* nothing in a sheet may be wider than the sheet */
.sheet-body>*,.sheet-foot>*{min-width:0;max-width:100%}
.sheet-foot{flex-wrap:wrap}

/* ------------------------------------------------------------
   5. A CENTRED MODAL IS CENTRED IN THE SAFE AREA

   On a phone held sideways the sheet is a centred modal again —
   844px of width is plenty — but the screen is only 390px tall,
   and a modal centred in the VIEWPORT puts its header wherever
   the maths lands, which measured as a title at y=48 on a handset
   whose top inset is 59. The clock won.

   Centring in the safe area instead of the viewport fixes it
   without changing the design: the modal still looks centred,
   because the insets are chrome, not screen. The cap then makes
   it impossible for a tall one to grow back into them.
   ------------------------------------------------------------ */
@media (min-width:721px){
  .sheet.modal{
    top:calc(50% + (var(--sat) - var(--sab)) / 2);
    max-height:calc(100dvh - var(--sat) - var(--sab) - 32px);
  }
  /* a notch on the side in landscape takes width off both edges */
  .sheet.modal{max-width:calc(100vw - var(--sal) - var(--sar) - 32px)}
  .sheet.drawer{padding-right:var(--sar)}
}

/* ------------------------------------------------------------
   6. A PHONE ON ITS SIDE IS STILL A PHONE

   The mobile rules key off width alone, so a handset in landscape
   — 844 x 390 — was served the desktop sizes: 30px buttons, 35px
   chips, 40px fields. The height and the pointer both say
   otherwise. This adds the touch sizes back without moving the
   layout, which stays as designed.
   ------------------------------------------------------------ */
@media (max-height:520px) and (orientation:landscape),(hover:none) and (pointer:coarse){
  .btn,button.btn,.chip,.icobtn,.tabs button,.sheet .inp,.sheet .sel,
  .sheet select,.sheet input,.sheet textarea{min-height:44px}
  .btn.sm,.btn-s.sm,.btn-g.sm,.btn-p.sm{min-height:44px;padding-top:0;padding-bottom:0}
  .icobtn{min-width:44px}
  .chip{min-width:44px}
  .lnk,.btn.link,.tiny .lnk{min-height:0;min-width:0}
}

/* ------------------------------------------------------------
   7. THE ASSISTANT'S TYPING BOX

   The bar around it is 54px, but the input itself was 32 — so the
   thing that takes the keyboard was smaller than the thing that
   looked like it did. On a touch screen the field is the target.
   ------------------------------------------------------------ */
@media (max-height:520px) and (orientation:landscape),(hover:none) and (pointer:coarse),(max-width:720px){
  .ginput{min-height:44px}
  .gbar{padding:6px var(--s4)}
  .gmic{width:44px;height:44px}
}

/* ------------------------------------------------------------
   8. FLEX CHILDREN THAT REFUSE TO SHRINK

   A flex item's default minimum size is its content, not zero, so
   `flex:1` is a request the browser overrules the moment the
   content is wider than the share it was given. Both faults below
   are that rule:

   - the recovery chart: six `.b` columns, each carrying a month
     label. At 360px the labels needed more room than a sixth of
     the card, so the whole row grew past the screen edge.
   - the insight card: a headline next to a `flex:none` figure.
     The headline could not wrap narrow enough, so the figure was
     pushed off the right.

   `min-width:0` lets them shrink. The labels then need somewhere
   to put the characters that no longer fit.
   ------------------------------------------------------------ */
.bars .b{min-width:0}
.bars .b>s,.bars .b>u{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  display:block;max-width:100%}
.insight .spread>h4{min-width:0;overflow-wrap:anywhere}
.insight-stat{min-width:0}

/* the same guard everywhere it is cheap and cannot change a layout */
.spread>*,.row>*,.grow{min-width:0}

/* A tile with nowhere to go is now a div, so it needs the box
   rules a button gave it for nothing. The ones that do go
   somewhere keep the hover and the pointer; the ones that don't
   no longer pretend. */
.tile{display:block;text-align:left;width:100%;box-sizing:border-box}
.tile.go{cursor:pointer}
div.tile:hover{background:var(--surface)}

/* ------------------------------------------------------------
   9. THE INSIGHT FIGURE ON A 320px SCREEN

   The figure is `flex:none; white-space:nowrap`, which is right
   when it is "46.3%" and wrong when it is "60% under £10k · 43%
   over £20k" — 271px of unbreakable text in a 269px card. The
   heading beside it was crushed to nothing and the figure still
   ran off the edge.

   Under 400px the pair stacks: heading, then figure beneath it.
   Nothing is dropped, nothing is truncated, and above 400px the
   card is exactly as designed.
   ------------------------------------------------------------ */
@media (max-width:400px){
  .insight .spread{flex-wrap:wrap;align-items:flex-start;gap:2px}
  .insight .spread>h4{flex:1 1 100%}
  .insight-stat{flex:1 1 100%;white-space:normal;font-size:var(--t-lead);line-height:var(--lh-heading)}
}

/* ------------------------------------------------------------
   10. THE NOTCH ABOVE A WIZARD HEADER

   `.sheet-head` rounds its own top corners, which is right when
   it is the first thing in the sheet and wrong when a step rail
   sits above it: the white rail had square corners, the dark
   header beneath it had round ones, and the join showed two
   little white wedges. Whichever element is first owns the
   rounding.
   ------------------------------------------------------------ */
.sheet .steps:first-child{border-radius:var(--r5) var(--r5) 0 0;overflow:hidden}
.sheet .steps + .sheet-head{border-radius:0}
@media (max-width:720px){
  .sheet.modal .steps:first-child{border-radius:var(--r5) var(--r5) 0 0}
}

/* The drawer starts at y=0 and its header now carries the inset as
   padding, so the header's own chrome already paints the strip
   behind the status bar — checked on the screenshots rather than
   assumed. Nothing further is needed here. */

/* ============================================================
   THE PREVIEW BAR

   It sits above everything, including the phone's own chrome, and
   it carries the top safe area itself so the shell beneath it can
   go on assuming it starts at the top of the screen. Amber rather
   than the brand navy on purpose: this is a state, not a place,
   and it should not be mistaken for part of the design.
   ============================================================ */
.pvbar{
  position:sticky;top:0;z-index:70;
  display:flex;align-items:center;gap:var(--s3);
  padding:8px var(--s5);
  padding-top:calc(8px + var(--sat));
  padding-left:calc(var(--s5) + var(--sal));
  padding-right:calc(var(--s5) + var(--sar));
  background:linear-gradient(180deg,#B8791C,#8F5B12);
  color:#fff;
  border-bottom:1px solid rgba(0,0,0,.22);
  box-shadow:0 6px 18px rgba(10,8,58,.20);
}
html[data-theme="dark"] .pvbar{background:linear-gradient(180deg,#8A5A16,#5E3C0C)}
.pvdot{width:8px;height:8px;border-radius:var(--rp);background:#FFD98A;flex:none;
  box-shadow:0 0 0 3px rgba(255,217,138,.24)}
.pvtxt{min-width:0;display:flex;flex-direction:column;line-height:var(--lh-heading)}
.pvtxt b{font-size:var(--t-meta);font-weight:var(--w-semi)}
/* the explanation is the second line, not a colour: the bar has to
   be readable to somebody who cannot tell amber from navy */
.pvtxt i{font-style:normal;font-size:var(--t-tiny);color:rgba(255,255,255,.82)}
.pvbar .pvback{flex:none;background:rgba(255,255,255,.16);border-color:rgba(255,255,255,.32);color:#fff}
.pvbar .pvback:hover{background:rgba(255,255,255,.26)}

/* The bar has taken the inset, so every shell beneath it must stop
   adding it a second time — that is what left a band of empty navy
   between the bar and the White's mark in the field app. */
.pvbar + .shell .topbar,
.pvbar + .mshell .mtop{padding-top:0;height:56px}
.pvbar + .devwrap .phone.bare .mob-head,
.pvbar + .appshell .mob-head{padding-top:var(--s4)}
.pvbar + .mshell,.pvbar + .devwrap,.pvbar + .shell{min-height:calc(100dvh - 46px)}
.pvbar + .devwrap .phone.bare{min-height:calc(100dvh - 46px)}

@media (max-width:720px){
  .pvbar{padding:8px var(--s4);padding-top:calc(8px + var(--sat))}
  /* the name goes on the one line that is left, because "signed in
     as Lewis" is the whole point of the bar */
  .pvtxt i{display:none}
  .pvbar .pvback{min-height:36px;padding:0 12px;font-size:var(--t-small)}
}
/* On a phone the bar has one line and about two hundred pixels of
   it. "Previewing:" is the word the amber and the dot already say,
   so it goes; the view and the name — the two things that are not
   guessable — stay, and the button loses three words it did not
   need. */
.pvwho{display:none}
.pvshort{display:none}
@media (max-width:720px){
  .pvlead{display:none}
  .pvwho{display:inline;font-weight:var(--w-body);color:rgba(255,255,255,.86)}
  .pvlong{display:none}
  .pvshort{display:inline}
}


/* ============================================================
   iOS ZOOMS INTO A SMALL FIELD AND STAYS THERE

   Nobody reports this as zooming; they report the page jumping
   about when they tap a box. Safari magnifies any field whose text
   is under 16px on focus and does not go back — and on a form
   filled in on a wet morning that is worse than the double tap
   this was found alongside.

   It sits here, at the end of the cascade, because 15-command.css
   sets 14.5px on inputs and is concatenated after 13-devices.css,
   where this was first written and quietly lost.

   Phones only. Nothing above 720px moves.
   ============================================================ */
@media (max-width:720px){
  /* input.num, not .num (26 October): .num is also the class on every
     count in a chip and every figure in a row, and this was setting all
     of them to the field size, so "Today 11" set the 11 two sizes larger
     than "Today". A number FIELD is still caught, at a higher weight. */
  input,select,textarea,.inp,.sel,input.num{font-size:var(--t-input)}
  /* Except the keypad, whose digits are meant to be large, and the
     search field, which was already sized for a thumb. */
  .pinbody .inp,.pinbody .num,#pinentry{font-size:var(--t-figure)}
}

/* ============================================================
   A SEGMENTED CONTROL THAT WILL NOT FIT

   .seg carries overflow:hidden so its buttons share one rounded
   outline, which means a row too narrow for it does not spill — it
   CUTS, and the last option loses its edge with nothing to say so.
   Eight pixels at tablet width on the settings screen, which is
   enough to shave the end off "System".

   Two things, both narrow. The control keeps its shape and never
   shrinks below its content, and the ROW is allowed to wrap so it
   drops onto its own line instead of being squeezed. A label and a
   control on two lines is ordinary; a control with its last option
   sliced off is not.
   ============================================================ */
.seg{flex:none}
@media (max-width:1180px){
  .spread{flex-wrap:wrap}
  /* The label keeps the row; the control takes the next line whole. */
  .spread > .seg{margin-left:auto}
}

/* ============================================================
   UI AUDIT FIXES (uiaudit.mjs, October 2026)
   ============================================================ */
/* The yard's lorry lists sat flush against each other and against the
   task rows under them — every other card on that screen has 12px. */
[data-yardloads]{margin-bottom:var(--s3)}
/* A screen's eyebrow ("EMAIL", "JOB FLOW") was touching the title under it. */
.page-head .eyebrow+.page-title,.page-head .eyebrow+h1{margin-top:4px}
/* Customer cards: the four figures stay two-by-two on a phone instead of
   one tall column, and each label has room above its number. */
.figs{display:grid;grid-template-columns:1fr 1fr;gap:var(--s3) var(--s4)}
.figs .dsp{margin-top:2px}
/* Diary rows: on a phone the time-and-type tags were drawn over the entry's
   title, which had been squeezed to one word a line. The tags now take their
   own line under the title. */
.dy-row{display:grid;grid-template-columns:44px 15px minmax(0,1fr) auto;align-items:center;gap:12px;padding:8px 0;border-top:1px solid var(--border)}
@media (max-width:640px){.dy-row{grid-template-columns:44px 15px minmax(0,1fr);row-gap:8px}.dy-row .dy-tags{grid-column:3/-1;flex-wrap:wrap}}
/* Job cards on a phone: the status pill was touching the customer line above it. */
.jc-sub+*{margin-top:8px}
/* Planning → The day: label, figure and note were stacked with no air. */
.dsum b.fig{display:block;margin:4px 0 2px;line-height:var(--lh-solid)}
.dsum>div+div{margin-top:var(--s3)}
/* A two-column grid holding a table: the table was squeezed into half the
   width and its columns hidden off the side. It takes the full width until
   there is genuinely room for two. */
.g2:has(.tblwrap){grid-template-columns:repeat(auto-fit,minmax(min(100%,520px),1fr))}
/* Growth insights in a four-across row: the headline was crushed to one
   letter a line beside its figure. The figure drops under the headline
   whenever the two do not fit side by side. */
.insight .spread{flex-wrap:wrap;align-items:flex-start;gap:4px var(--s3)}
.insight .spread>h4{flex:1 1 200px;overflow-wrap:break-word}
.insight-stat{white-space:normal;flex:0 1 auto;max-width:100%}
.grid>.insight,.insight-grid>.insight{padding-right:var(--s4)}
/* Job flow strip on a tablet: nine stages in a 450px band left each label a
   letter or two wide ("Nee/ds/revi/ew"). Between phone and desk each group
   takes its own row and the stages wrap at a readable width. */
@media (min-width:721px) and (max-width:1600px){
  .jf-strip.jf-strip{grid-template-columns:3fr 4fr 1.4fr}
  .jf-strip .jf-grp:first-child{grid-column:1/-1;border-right:0;border-bottom:1px solid var(--rule)}
  .jf-strip .jf-grp:nth-child(2){padding-left:0}
  .jf-strip .jf-stages{grid-auto-flow:row;grid-template-columns:repeat(auto-fill,minmax(84px,1fr))}
  .jf-strip .jf-st span{overflow-wrap:normal;hyphens:manual}
}
/* Live operations map card: everything under the map (the routes button,
   "Needs attention", the driver note) ran flush to the card's left edge. */
.om-wrap>:not(.ops-head):not(.ops-map):not(.ops-note):not(.om-keys){padding-left:var(--s5);padding-right:var(--s5)}
.om-wrap .ops-note{padding-top:var(--s2)}
.figs.f3{grid-template-columns:repeat(3,1fr)}
/* A small label straight over a big number: give the number room. */
.tiny.muted+.dsp{margin-top:4px}
/* Home: Today's vehicles sat flush on top of the Live operations card. */
#mboard{margin-bottom:var(--s4)}
.jf-strip .jf-st span{overflow-wrap:normal;word-break:normal;hyphens:manual}
/* A side-by-side layout whose main column holds a wide table (six columns
   or more): on a laptop the money columns were hidden off the side of the
   table. Below a wide screen the side column moves underneath instead. */
@media (max-width:1500px){.split:has(> :first-child .tbl th:nth-child(6)){grid-template-columns:minmax(0,1fr)}}
.figs .dsp-lg,.figs .dsp-md{margin-top:6px;line-height:var(--lh-solid)}

/* ---- bringing the real work in (38p-bringin.js) ---- */
.sc-rows,.sz-rows{display:grid}
.sc-row,.sz-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--s3);padding:12px 0;border-top:1px solid var(--rule)}
.sc-row:first-child,.sz-row:first-child{border-top:0}
.sc-row .grow,.sz-row .grow{flex:1 1 240px}
.sc-row .inp{min-width:160px}
.sz-in{display:grid;grid-template-columns:72px 72px 80px minmax(110px,1fr) minmax(120px,1fr);gap:var(--s2);flex:1 1 500px}
@media (max-width:560px){.sz-in{grid-template-columns:1fr 1fr 1fr}}
.rs-steps{margin:var(--s3) 0 0;padding-left:20px;display:grid;gap:var(--s3)}
.rs-steps>li.ok>b{color:var(--ok)}

/* White's Scaffolding's own logo on the scafftag and the public tag page */
.ptag-logo{height:9mm;width:auto;display:block}
.tagpub-logo{height:52px;width:auto;max-width:80vw;display:block;margin:0 auto}

/* White's Scaffolding's logo across the system (see WLOGO in 20-util.js) */
.wlogo{display:inline-block;flex:none;background:currentColor;-webkit-mask:var(--wlogo) left center/contain no-repeat;mask:var(--wlogo) left center/contain no-repeat}
.mtop-mark{width:auto;padding-left:4px}
.shell.collapsed .logo .wlogo{width:34px!important;-webkit-mask-size:auto 100%;mask-size:auto 100%}

/* ============================================================
   WALKTHROUGHS AND "NOT READY YET" (31d-tours.js)
   ============================================================ */
.nr-chip{display:inline-flex;align-items:center;gap:4px;font-size:var(--t-micro);font-weight:var(--w-bold);letter-spacing:var(--ls-micro);line-height:var(--lh-solid);
  padding:2px 6px;border-radius:4px;background:#FCE7B2;color:#6B4A00;white-space:nowrap;margin-left:auto;flex:none}
.nav-item.nr-dim .nav-lbl,.nav-item.nr-dim .ic{opacity:.55}
.side .nav-item .nr-chip{margin-left:auto}
.side .nav-item:has(.nav-badge) .nr-chip{margin-left:6px}
.shell.collapsed .side .nr-chip{display:none}
.shell.collapsed .nav-item.nr-dim{opacity:.55}
.mbtab.nr-dim{opacity:.6}.mbtab .nr-chip{display:none}
.tabs button.nr-dim{opacity:.7}.tabs button .nr-chip{margin-left:6px;font-size:var(--t-micro);padding:2px 4px}
.nr-bubble{position:fixed;left:50%;bottom:22px;transform:translateX(-50%);z-index:45;display:flex;align-items:center;gap:12px;
  max-width:min(560px,calc(100vw - 32px));padding:12px 16px;border-radius:999px;background:#FFF4D6;color:#4A3300;
  border:1.5px solid #E8B53A;box-shadow:0 6px 22px rgba(15,12,91,.18);font-size:var(--t-meta);line-height:var(--lh-ui);animation:nrIn .25s ease}
.nr-bubble b{font-weight:var(--w-bold)}
.nr-dot{width:9px;height:9px;border-radius:50%;background:#E0A00F;flex:none;box-shadow:0 0 0 4px rgba(224,160,15,.22)}
@keyframes nrIn{from{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%,0)}}
@media (max-width:699px){.nr-bubble{bottom:calc(84px + var(--sab,0px));border-radius:14px;font-size:var(--t-small)}}
.tour-gbtn{position:fixed;left:16px;bottom:18px;z-index:44;display:flex;align-items:center;gap:6px;padding:8px 12px;border-radius:999px;
  background:var(--surface);color:var(--text);border:1px solid var(--rule-strong,var(--border));box-shadow:0 4px 14px rgba(15,12,91,.14);
  font:600 13px/1 inherit;cursor:pointer}
.tour-gbtn:hover{border-color:var(--text-3)}
@media (max-width:699px){.tour-gbtn{bottom:calc(84px + var(--sab,0px));left:12px;padding:6px 12px}.tour-gbtn span{display:none}}
/* html.tour-lock no longer hides overflow itself; tourWelcome() uses lockScroll() */
.tw-scrim{position:fixed;inset:0;z-index:170;background:rgba(15,12,91,.5)}
.tw-panel{position:fixed;z-index:171;left:50%;top:50%;transform:translate(-50%,-50%);width:min(560px,calc(100vw - 32px));max-height:calc(100dvh - 40px);
  overflow:auto;background:var(--surface);color:var(--text);border-radius:14px;padding:24px 24px 20px;box-shadow:0 24px 70px rgba(15,12,91,.35)}
.tw-head{display:flex;gap:12px;align-items:flex-start}
.tw-title{font-size:var(--t-page);line-height:var(--lh-heading);font-weight:var(--w-bold);margin-top:6px;letter-spacing:var(--ls-4)}
.tw-lede{margin-top:12px;color:var(--text-2);font-size:var(--t-body);line-height:var(--lh-ui)}
.tw-lede .nr-chip{margin:0 2px;vertical-align:1px}
.tw-list{margin-top:16px;border-top:1px solid var(--border)}
.tw-row{display:flex;align-items:center;gap:12px;width:100%;text-align:left;padding:12px 4px;border-bottom:1px solid var(--border);background:none;cursor:pointer;color:inherit}
.tw-row:hover{background:var(--surface-2)}
.tw-row b{display:block;font-size:var(--t-body)}.tw-row .grow span{display:block;font-size:var(--t-small);color:var(--text-2);margin-top:2px}
.tw-ic{width:34px;height:34px;flex:none;display:grid;place-items:center;color:#0F0C5B;border:1.5px solid #0F0C5B;border-radius:8px}
[data-theme=dark] .tw-ic{color:#fff;border-color:rgba(255,255,255,.6)}
.tw-go{font-size:var(--t-small);font-weight:var(--w-bold);color:var(--accent,#0F0C5B);white-space:nowrap;display:flex;align-items:center;gap:4px}
.tw-foot{display:flex;align-items:center;gap:12px;margin-top:16px;flex-wrap:wrap}
.tw-tiny{margin-top:12px;font-size:var(--t-small);color:var(--text-3)}
@media (max-width:699px){.tw-panel{top:auto;bottom:0;transform:translateX(-50%);width:100vw;border-radius:16px 16px 0 0;max-height:92dvh;padding:20px 16px calc(16px + var(--sab,0px))}
  .tw-go{font-size:0}.tw-go .ic{width:16px;height:16px}}
.tp-spot{position:fixed;z-index:160;border-radius:10px;pointer-events:none;box-shadow:0 0 0 3px #F2B705,0 0 0 9999px rgba(15,12,91,.45);transition:left .25s ease,top .25s ease,width .25s ease,height .25s ease}
.tp-dim{position:fixed;inset:0;z-index:160;background:rgba(15,12,91,.45);pointer-events:none}
.tp{position:fixed;z-index:161;width:min(360px,calc(100vw - 24px));background:var(--surface);color:var(--text);border-radius:12px;padding:16px 16px 12px;
  box-shadow:0 18px 50px rgba(15,12,91,.35);border-top:4px solid #F2B705}
.tp.tp-mid{left:50%;top:50%;transform:translate(-50%,-50%);width:min(420px,calc(100vw - 24px))}
.tp.tp-dock{left:12px!important;right:12px;top:auto!important;bottom:calc(12px + var(--sab,0px));width:auto;transform:none}
.tp-n{font-size:var(--t-micro);font-weight:var(--w-bold);letter-spacing:var(--ls-caps);text-transform:uppercase;color:var(--text-3)}
.tp-t{font-size:var(--t-lead);font-weight:var(--w-bold);margin-top:4px}
.tp-b{font-size:var(--t-body);line-height:var(--lh-ui);color:var(--text-2);margin-top:6px}
.tp-foot{display:flex;align-items:center;gap:8px;margin-top:12px}
.howto{margin:8px 0 0 0;padding-left:20px;display:grid;gap:6px}
.howto li{line-height:var(--lh-ui)}
.gans-d .howto{font-size:var(--t-meta)}
@media (min-width:700px){#tourgbtn .tour-gbtn{left:var(--tg-left,16px)}}
/* Room to scroll the last row out from under the not-ready bubble. */
html.nr-on .page{padding-bottom:calc(var(--s6,32px) + 72px)}
@media (max-width:699px){html.nr-on .mshell .page{padding-bottom:calc(84px + 96px + var(--sab,0px))}}

/* ============================================================
   NOTHING THE PHONE SCROLLS TO MAY LAND UNDER THE TAB BAR

   The bottom bar is fixed over the page. The page itself already
   had room at the end to scroll its last row clear of it, but
   whenever the BROWSER does the scrolling — the keyboard's "next"
   moving to the following box, a screen calling scrollIntoView to
   show the thing that just changed, a link to a section — it only
   brings the element to the bottom edge of the glass, which is
   exactly where the bar sits. The row looked on screen and a tap
   on it hit the bar instead (a lad tapping a scafftag due today
   opened a different tab). scroll-padding tells the browser that
   the bottom of the window is not visible, so it stops short of
   the bar everywhere at once rather than one row at a time.

   The root cause was worse than a missing margin. In the phone
   shell (.mshell — the field app and the client portal on a real
   phone) the bar was position:absolute, which the framed demo
   phone needs because the frame is its box. In .mshell there is no
   such box, so "bottom:0" meant the bottom of the FIRST screenful:
   scroll down and the bar rode up the page with the content and sat
   in the middle of the list, on top of the very rows he was reaching
   for. Only the tablet layout had ever pinned it. It is pinned to
   the glass on every phone now, as the tablet one always was.

   Two scrollers: the phone shell scrolls the whole document, the
   framed phone (and the bare one) scrolls .mob-body. The figure is
   the bar's height plus the gap it floats on, plus the home
   indicator.
   ============================================================ */
/* pinned, not carried along with the page */
.mshell > .mob-tabs{position:fixed}
html:has(.mshell .mob-tabs),html:has(.mshell .mbar){scroll-padding-bottom:calc(96px + var(--sab,0px))}
.mob-body{scroll-padding-bottom:calc(96px + var(--sab,0px))}
/* The "Not ready yet" note is a note, not a control. It floats over
   the foot of the screen, and it was swallowing the tap meant for
   whatever had scrolled beneath it — on Equipment that was the red
   overhire card's own button. It says its piece and lets taps through,
   and the browser stops short of it as it does the bar. */
.nr-bubble{pointer-events:none}
@media (max-width:699px){html.nr-on:has(.mshell){scroll-padding-bottom:calc(96px + 72px + var(--sab,0px))}}
/* ============================================================
   PICKING A CARD UP

   Two things, and they are not the same thing.

   A shadow says a card sits above the page. It is structural,
   it is always on, and it has to stay quiet — this trade's
   software should look like a control room, not a landing page,
   so the elevation is a hairline plus a soft ground shadow, not
   a drop shadow you can point at.

   The glow is the opposite: it is an EVENT. When you choose a
   card, a light runs once around its edge and settles into a
   held ring, so the answer to "which one did I just pick" is
   never in doubt on a scaffolder's phone in daylight. It is the
   accent blue already in the palette, not a rainbow — the
   travelling-gradient look, borrowed, but wearing this system's
   own colour.

   The ring is drawn in a pseudo-element masked to the border, so
   it cannot move the card, cannot change its size, and cannot
   affect anything laid out inside it.
   ============================================================ */

/* The angle is animated as a real custom property where the
   browser supports it. Without @property a conic gradient cannot
   be tweened, so older browsers get the held ring and no travel —
   which is the point being made, just more quietly. */
@property --cg-ang {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

.card { box-shadow: var(--lift), var(--sh1), 0 2px 10px -4px rgba(0,0,0,.35); }
.card.flat { box-shadow: none; }

.card.click, .card.hov { position: relative; }

/* ---- the ring ---- */
.card.is-picked::before,
.card.click:focus-visible::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.5px;                 /* the ring's thickness */
  pointer-events: none;
  z-index: 1;
  background: conic-gradient(from var(--cg-ang),
    transparent 0turn,
    transparent .55turn,
    color-mix(in srgb, var(--int) 35%, transparent) .70turn,
    var(--int-hi) .84turn,
    #fff .90turn,
    var(--int-hi) .96turn,
    transparent 1turn);
  /* keep only the border band */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  animation: cg-run 1.15s var(--ease, cubic-bezier(.22,.61,.36,1)) 1 both;
}

/* Once it has run, the card keeps a steady ring so the choice
   stays visible after the movement stops. A selection that only
   exists during an animation is not a selection. */
.card.is-picked {
  border-color: color-mix(in srgb, var(--int) 55%, var(--rule));
  box-shadow: var(--lift), var(--sh1),
    0 0 0 1px color-mix(in srgb, var(--int) 28%, transparent),
    0 6px 26px -10px color-mix(in srgb, var(--int) 60%, transparent);
}

@keyframes cg-run { to { --cg-ang: 1turn } }

/* A second pick on the same card should run the light again
   rather than sit there doing nothing. */
.card.is-picked.again::before { animation: none; }

/* A card that opens something never gets the held ring — it is
   gone before you could read it — so the press itself is what
   answers. One quick sweep of the same light, on the way out. */
.card.click:active::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.5px;
  pointer-events: none;
  z-index: 1;
  background: conic-gradient(from var(--cg-ang),
    transparent 0turn, transparent .6turn,
    var(--int-hi) .85turn, #fff .92turn, transparent 1turn);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  animation: cg-run .5s linear 1 both;
}

/* ---- the same idea, restrained, on hover ---- */
.card.click:hover { box-shadow: var(--lift-2), var(--sh2),
  0 0 0 1px color-mix(in srgb, var(--int) 14%, transparent); }

/* ---- and for anyone who has asked the system to stop moving ---- */
@media (prefers-reduced-motion: reduce) {
  .card.click:active::before { display: none; }
  .card.is-picked::before,
  .card.click:focus-visible::before { animation: none;
    background: color-mix(in srgb, var(--int) 45%, transparent); }
}

/* Light mode carries a real shadow rather than an inset highlight,
   so the ring needs less help to be seen. */
:root[data-theme="light"] .card.is-picked,
.light .card.is-picked { box-shadow: var(--sh1),
  0 0 0 1px color-mix(in srgb, var(--int) 30%, transparent),
  0 8px 24px -12px color-mix(in srgb, var(--int) 55%, transparent); }
/* ============================================================
   THE JOB FILE

   Rules and type, not boxes. The strip is one ruled band read
   left to right the way the job travels; a file is a column of
   labelled sections with a hairline between them, and the
   timeline runs down the side like the margin of a ledger.
   Nothing here introduces a colour — it is the app's own tokens.
   ============================================================ */

/* ---- the strip ---- */
.jf-strip{display:grid;grid-template-columns:9fr 3fr 4fr 1.3fr;gap:0;
  border-top:2px solid var(--ink);border-bottom:1px solid var(--rule);margin:var(--s4) 0 var(--s5)}
.jf-grp{padding:12px 12px 12px 0;border-right:1px solid var(--rule);min-width:0}
.jf-grp+.jf-grp{padding-left:16px}
.jf-grp:last-child{border-right:0}
.jf-gl{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);font-size:var(--t-micro);
  color:var(--ink-3);margin-bottom:6px}
.jf-stages{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);gap:2px}
.jf-st{display:flex;flex-direction:column;align-items:flex-start;gap:2px;padding:6px 6px 6px 6px;min-width:0;
  background:none;border:0;border-left:2px solid transparent;color:var(--ink);text-align:left;cursor:pointer;
  border-radius:0;transition:background var(--dur)}
.jf-st b{font-size:var(--t-page);line-height:var(--lh-solid);font-weight:var(--w-bold)}
.jf-st span{font-size:var(--t-tiny);color:var(--ink-2);line-height:var(--lh-heading);overflow-wrap:anywhere;hyphens:auto}
.jf-st:hover{background:var(--surface-2)}
.jf-st.on{border-left-color:var(--int);background:var(--surface-2)}
.jf-st.zero b{color:var(--ink-3);font-weight:var(--w-med)}
.jf-st.hot b{color:var(--risk)}

/* ---- tabs ---- */
.jf-tabs{display:flex;gap:20px;border-bottom:1px solid var(--rule);margin-bottom:var(--s4)}
.jf-tab{background:none;border:0;padding:8px 0 8px;color:var(--ink-2);font-weight:var(--w-semi);font-size:var(--t-meta);
  border-bottom:2px solid transparent;margin-bottom:-1px;cursor:pointer}
.jf-tab.on{color:var(--ink);border-bottom-color:var(--int)}
.jf-n{display:inline-block;min-width:18px;padding:0 4px;border-radius:var(--r-pill);background:var(--int);color:#fff;
  font-size:var(--t-micro);text-align:center;line-height:17px;margin-left:4px}

/* ---- action rows ---- */
.jf-cols{display:grid;grid-template-columns:minmax(0,1fr) 260px;gap:var(--s6)}
.jf-list{border-top:1px solid var(--rule)}
.jf-row{display:grid;grid-template-columns:104px minmax(0,1fr) auto auto;gap:12px;align-items:center;
  padding:12px 0;border-bottom:1px solid var(--rule)}
.jf-due{font-size:var(--t-tiny);font-weight:var(--w-semi);color:var(--ink-3);text-transform:uppercase;letter-spacing:var(--ls-caps)}
.jf-due.risk{color:var(--risk)} .jf-due.warn{color:var(--warn)}
.jf-main{display:flex;flex-direction:column;gap:1px;background:none;border:0;padding:0;text-align:left;
  color:var(--ink);cursor:pointer;min-width:0}
.jf-main b{font-size:var(--t-meta);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.jf-main:hover b{text-decoration:underline}
.jf-act{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);font-size:var(--t-micro);color:var(--int)}
.jf-who{font-size:var(--t-small);color:var(--ink-2);white-space:nowrap}
.jf-side{border-left:1px solid var(--rule);padding-left:var(--s4)}
.jf-people{display:flex;flex-direction:column;gap:6px;margin-top:8px;font-size:var(--t-meta)}
.jf-tbl tr.clk{cursor:pointer}
.jf-tbl tr.clk:hover td{background:var(--surface-2)}

/* ---- home ---- */
.jf-home{border-top:2px solid var(--ink);padding-top:12px;margin:var(--s5) 0}
.jf-mini{display:flex;flex-wrap:wrap;gap:0;border-bottom:1px solid var(--rule);margin-bottom:6px}
.jf-mst{display:flex;align-items:baseline;gap:6px;padding:6px 16px 8px 0;margin-right:16px;background:none;border:0;
  color:var(--ink);cursor:pointer;border-right:1px solid var(--rule)}
.jf-mst:last-child{border-right:0}
.jf-mst b{font-size:var(--t-lead)}
.jf-mst span{font-size:var(--t-tiny);color:var(--ink-2)}
.jf-mst.zero b{color:var(--ink-3);font-weight:var(--w-med)}
.jf-home .jf-row{grid-template-columns:96px minmax(0,1fr) auto}

/* ---- one job ---- */
.jf-head{display:flex;gap:var(--s4);align-items:flex-start;margin-bottom:var(--s4)}
.jf-next{display:flex;gap:var(--s4);align-items:center;flex-wrap:wrap;padding:16px 16px;
  border-left:3px solid var(--int);background:var(--surface-2);margin-bottom:var(--s4)}
.jf-next.late{border-left-color:var(--risk)}
.jf-next.done{border-left-color:var(--ok)}
.jf-next .jf-act{display:block;margin-bottom:2px}
.jf-next b{font-size:var(--t-lead)}
.jf-rail{list-style:none;display:grid;grid-template-columns:repeat(13,minmax(0,1fr));gap:0;padding:0;margin:0 0 var(--s5);
  border-top:1px solid var(--rule)}
.jf-rail li{position:relative;padding:12px 4px 0 0;font-size:var(--t-tiny);color:var(--ink-3);min-width:0}
.jf-rail .dot{position:absolute;top:-5px;left:0;width:9px;height:9px;border-radius:50%;background:var(--surface);
  border:1.5px solid var(--rule-strong)}
.jf-rail li.done .dot{background:var(--ink);border-color:var(--ink)}
.jf-rail li.done{color:var(--ink-2)}
.jf-rail li.now .dot{background:var(--int);border-color:var(--int);box-shadow:0 0 0 3px var(--surface-3)}
.jf-rail li.now{color:var(--ink);font-weight:var(--w-semi)}
.jf-rail li.skip{opacity:.55}
.jf-rail .l{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.jf-rail .t{display:block;font-size:var(--t-micro);color:var(--ink-3);margin-top:1px}
.jf-body{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:var(--s6);align-items:start}
.jf-sec{border-top:1px solid var(--rule);padding:16px 0 16px}
.jf-sec h2{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);font-size:var(--t-small);
  margin:0 0 8px;color:var(--ink-2)}
.jf-kv{display:grid;grid-template-columns:150px minmax(0,1fr);gap:12px;padding:4px 0;font-size:var(--t-meta)}
.jf-kv>span{color:var(--ink-3)}
.jf-chks{display:flex;flex-wrap:wrap;gap:6px 20px}
.jf-chk{display:flex;gap:6px;align-items:center;background:none;border:0;padding:4px 0;color:var(--ink-2);
  font-size:var(--t-meta);cursor:pointer}
.jf-chk>span{width:16px;text-align:center;font-weight:var(--w-bold);color:var(--ink-3)}
.jf-chk.ok>span{color:var(--ok)} .jf-chk.miss>span{color:var(--warn)} .jf-chk.miss{color:var(--ink)}
.jf-chk:hover{color:var(--ink);text-decoration:underline}
.jf-tl-col{position:sticky;top:12px;border-left:1px solid var(--rule);padding-left:var(--s4);max-height:calc(100vh - 40px);overflow:auto}
.jf-tl{list-style:none;padding:0;margin:8px 0 0}
.jf-tl li{display:grid;grid-template-columns:78px minmax(0,1fr);gap:8px;padding:6px 0;border-bottom:1px dashed var(--rule);
  font-size:var(--t-small)}
.jf-tl .t{color:var(--ink-3);font-variant-numeric:tabular-nums}
.jf-mail summary{cursor:pointer;padding:6px 0;font-size:var(--t-meta)}
.jf-pre{white-space:pre-wrap;font:var(--t-meta)/var(--lh-ui) ui-monospace,Menlo,Consolas,monospace;background:var(--surface-2);
  padding:12px 12px;max-height:340px;overflow:auto;margin:6px 0 12px}
.jf-link{display:block;width:100%;text-align:left;background:none;border:0;padding:2px 0;color:var(--ink);
  font-size:var(--t-meta);cursor:pointer}
.jf-link:hover{text-decoration:underline}
.jf-link .tiny{display:block}

/* ---- the day ---- */
.jf-day{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--s6)}
.jf-dayl{border-top:1px solid var(--rule);padding:8px 0 12px;font-size:var(--t-meta)}

/* ---- steps ---- */
.jf-avail{border-left:4px solid var(--ok);padding:12px 16px;background:var(--surface-2)}
.jf-avail.no{border-left-color:var(--risk)}
.jf-avail-h{font-family:var(--font-head);letter-spacing:var(--ls-caps);font-size:var(--t-small);font-weight:var(--w-bold);color:var(--ok)}
.jf-avail.no .jf-avail-h{color:var(--risk)}
.jf-rules{list-style:none;padding:0;margin:8px 0 0;font-size:var(--t-small)}
.jf-rules li{padding:2px 0;color:var(--ink-2)}
.jf-rules li>span{display:inline-block;width:14px;font-weight:var(--w-bold)}
.jf-rules li.ok>span{color:var(--ok)} .jf-rules li.no{color:var(--ink)} .jf-rules li.no>span{color:var(--risk)}
.jf-rules li.warn>span{color:var(--warn)}
.jf-bigprice{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
.jf-bigprice .num{font-size:var(--t-figure);font-weight:var(--w-bold)}
.jf-quote-scope{white-space:pre-wrap;font-size:var(--t-meta);border-left:2px solid var(--rule-strong);padding:4px 0 4px 12px}

/* ---- phone: its own layout, not the desk squeezed ---- */
@media (max-width:720px){
  .jf-strip{grid-template-columns:none;display:flex;flex-direction:column;border-bottom:0}
  .jf-grp,.jf-grp+.jf-grp{padding:8px 0;border-right:0;border-bottom:1px solid var(--rule)}
  .jf-stages{display:flex;flex-wrap:wrap;gap:0}
  .jf-st{flex:0 0 25%;padding:6px 8px 6px 0}
  /* On a phone the strip leads with what is actually there: empty
     stages step aside, and a group with nothing in it goes. */
  .jf-st.zero{display:none}
  .jf-grp[data-empty]{display:none}
  .jf-cols,.jf-body,.jf-day{grid-template-columns:1fr}
  .jf-side{border-left:0;padding-left:0;border-top:1px solid var(--rule);padding-top:var(--s3)}
  .jf-row{grid-template-columns:1fr auto;grid-template-areas:"due who" "main main" "btn btn";gap:4px 12px;padding:12px 0}
  .jf-row .jf-due{grid-area:due}
  .jf-row .jf-main{grid-area:main}
  .jf-row .jf-who{grid-area:who;text-align:right}
  .jf-row>.btn,.jf-row>span:last-child{grid-area:btn;justify-self:stretch;text-align:left}
  .jf-row>.btn{min-height:44px}
  .jf-home .jf-row{grid-template-columns:1fr auto}
  .jf-head{flex-direction:column}
  .jf-next{flex-direction:column;align-items:stretch}
  .jf-next .btn{min-height:44px}
  .jf-rail{grid-template-columns:none;display:flex;overflow-x:auto;scrollbar-width:none;padding-top:0}
  .jf-rail li{min-width:74px;flex:0 0 auto}
  .jf-kv{grid-template-columns:110px minmax(0,1fr)}
  .jf-tab{min-height:48px;padding:12px 4px}
  .jf-st,.jf-mst,.jf-chk,.jf-link{min-height:44px}
  .jf-tl-col{position:static;border-left:0;padding-left:0;border-top:2px solid var(--ink);padding-top:12px;max-height:none}
  .jf-mini{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none}
  .jf-mst{flex:0 0 auto}
}

/* ---- what the inbox brought in, on the chosen people's home ---- */
.jf-inbox{border-left:3px solid var(--warn);padding:8px 0 4px 12px;margin:8px 0 12px}
.jf-inbox>.spread b{display:inline-flex;gap:6px;align-items:center;font-size:var(--t-meta)}

/* ============================================================
   EMAIL — a ruled list, read like a ledger. No cards.
   ============================================================ */
.ml-boxes{display:flex;gap:0;border-top:2px solid var(--ink);border-bottom:1px solid var(--rule);margin:var(--s4) 0 0}
.ml-box{display:flex;flex-direction:column;align-items:flex-start;gap:2px;padding:12px 20px 12px 0;margin-right:20px;
  background:none;border:0;border-right:1px solid var(--rule);color:var(--ink-2);cursor:pointer;text-align:left;min-width:0}
.ml-box:last-child{border-right:0}
.ml-box b{font-size:var(--t-meta);font-weight:var(--w-semi)}
.ml-box span{font-size:var(--t-tiny);color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:260px}
.ml-box.on{color:var(--ink);box-shadow:inset 0 -2px 0 var(--int)}
.ml-acct{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;
  padding:12px 0;border-bottom:1px solid var(--rule);font-size:var(--t-meta)}
.ml-acct .ic{vertical-align:-2px;margin-right:4px;color:var(--ink-3)}
.ml-bar{display:flex;align-items:center;gap:16px;border-bottom:1px solid var(--rule);margin:var(--s3) 0 0;flex-wrap:wrap}
.ml-search{flex:1;min-width:180px;max-width:360px;margin-left:auto}
.ml-search .inp{height:34px}
.ml-list{border-top:0}
.ml-row{display:grid;grid-template-columns:34px 30px minmax(0,1fr);align-items:center;border-bottom:1px solid var(--rule)}
/* is-sel, not sel: sel is every select box's class, and a ticked row took
   a select's 40px height and chevron (27 October) */
.ml-row:hover,.ml-row.is-sel{background:var(--surface-2)}
.ml-open{display:grid;grid-template-columns:190px minmax(0,1fr) auto 76px;gap:16px;align-items:baseline;width:100%;min-width:0;
  padding:12px 0;border:0;background:none;color:var(--ink-2);text-align:left;cursor:pointer}
.ml-row.unread .ml-open{color:var(--ink)}
.ml-row.unread .ml-who,.ml-row.unread .ml-sub b{font-weight:var(--w-bold)}
.ml-chk{display:flex;align-items:center;justify-content:center;min-height:40px;cursor:pointer}
.ml-chk input{width:16px;height:16px;accent-color:var(--int);cursor:pointer}
.ml-star{display:flex;align-items:center;justify-content:center;width:30px;height:36px;background:none;border:0;color:var(--ink-3);cursor:pointer;padding:0}
.ml-star:hover{color:var(--ink)}
.ml-star.on{color:var(--warn)}
.ml-star.on svg{fill:currentColor}
.ml-star:disabled{opacity:.35;cursor:default}
.ml-who{font-size:var(--t-meta);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ml-c{font-style:normal;font-size:var(--t-micro);color:var(--ink-3);margin-left:4px}
.ml-sub{font-size:var(--t-meta);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.ml-sub b{font-weight:var(--w-med)}
.ml-file{font-family:var(--font-head);font-size:var(--t-micro);letter-spacing:var(--ls-caps);color:var(--int);white-space:nowrap}
.ml-at{font-size:var(--t-tiny);color:var(--ink-3);text-align:right;white-space:nowrap}
.ml-at .ic{vertical-align:-2px}
.ml-folders{display:flex;gap:16px;overflow-x:auto;scrollbar-width:none}
.ml-folders::-webkit-scrollbar{display:none}
.ml-folders .jf-tab{white-space:nowrap}
.ml-bulk{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-height:44px;border-bottom:1px solid var(--rule);padding:2px 0}
.ml-bulk.on{background:var(--surface-2)}
.ml-bulk .ml-chk{width:34px}
.ml-bn{font-weight:var(--w-semi);font-size:var(--t-meta);margin-right:6px}
.ml-note{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:12px 12px;margin:var(--s3) 0 0;
  border-left:3px solid var(--warn);background:var(--surface-2);font-size:var(--t-meta)}
.ml-note span{flex:1;min-width:200px}
.ml-files{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.ml-fbtn{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:1px solid var(--rule);background:var(--surface);
  color:var(--ink);font-size:var(--t-small);border-radius:var(--r-sm,4px);cursor:pointer;max-width:100%}
.ml-fbtn span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:240px}
.ml-fbtn i{font-style:normal;color:var(--ink-3);font-size:var(--t-tiny)}
.ml-fx{background:none;border:0;padding:2px;margin-left:2px;color:var(--ink-3);cursor:pointer;display:flex}
.ml-tools .btn.on svg{fill:currentColor}
.ml-empty{padding:28px 0;border-bottom:1px solid var(--rule);max-width:560px}
.ml-empty b{display:block;font-size:var(--t-lead);margin-bottom:6px}
.ml-empty p{margin-bottom:12px}
.ml-thead{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap;
  border-top:2px solid var(--ink);padding-top:12px;margin-bottom:6px}
.ml-subj{font-size:var(--t-h2);line-height:var(--lh-heading);margin:0;max-width:760px}
.ml-msgs{border-top:1px solid var(--rule)}
.ml-msg{padding:16px 0 16px;border-bottom:1px solid var(--rule);max-width:820px}
.ml-msg header{display:flex;justify-content:space-between;gap:12px;margin-bottom:12px}
.ml-text{white-space:pre-wrap;overflow-wrap:anywhere;font-size:var(--t-body);line-height:var(--lh-ui);color:var(--ink)}

@media (max-width:720px){
  .ml-boxes{overflow-x:auto}
  .ml-box{padding-right:12px;margin-right:12px}
  .ml-box span{max-width:150px}
  .ml-bar{gap:8px}
  .ml-search{order:3;flex-basis:100%;max-width:none;margin:0 0 12px}
  .ml-row{grid-template-columns:40px minmax(0,1fr) 40px;grid-template-areas:"chk open star"}
  .ml-row .ml-chk{grid-area:chk;min-height:56px}
  .ml-row .ml-star{grid-area:star;height:56px;width:40px}
  .ml-open{grid-area:open;grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"who at" "sub sub" "file file";gap:2px 12px;padding:12px 2px;min-height:64px}
  .ml-who{grid-area:who;font-size:var(--t-body)}
  .ml-at{grid-area:at}
  .ml-sub{grid-area:sub;white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
  .ml-file{grid-area:file;margin-top:2px}
  .ml-file:empty{display:none}
  .ml-bulk{position:sticky;top:0;z-index:5;background:var(--bg,var(--surface))}
  .ml-bulk .btn{min-height:38px}
  .ml-tools .btn{min-height:40px}
  .ml-msg header{flex-direction:column;gap:4px}
  .ml-acct .btn{min-height:40px}
}

/* ============================================================
   THE SCAFFOLD SUMMARY — read in seconds, ruled not boxed
   ============================================================ */
.sp-card{border-top:2px solid var(--ink);border-bottom:1px solid var(--rule);padding:12px 0 12px;margin:2px 0 12px}
.sp-card.empty{border-top-style:dashed;border-top-width:1px}
.sp-h{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);font-size:var(--t-micro);color:var(--ink-3)}
.sp-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:0;border-top:1px solid var(--rule)}
.sp-c{display:flex;flex-direction:column;gap:1px;padding:8px 12px 8px 0;border-bottom:1px solid var(--rule)}
.sp-c span,.sp-special span,.sp-price span{font-size:var(--t-tiny);color:var(--ink-3)}
.sp-c b{font-size:var(--t-body);font-weight:var(--w-semi);line-height:var(--lh-heading)}
.sp-special{display:grid;grid-template-columns:110px minmax(0,1fr);gap:12px;padding:8px 0;border-bottom:1px solid var(--rule)}
.sp-special b{font-weight:var(--w-semi);font-size:var(--t-meta)}
.sp-special .sp-n{font-weight:var(--w-body);white-space:pre-wrap}
.sp-price{display:flex;flex-wrap:wrap;gap:4px 28px;align-items:baseline;padding-top:12px}
.sp-price .sp-h{flex-basis:100%;margin-bottom:2px}
.sp-price div{display:flex;flex-direction:column}
.sp-price b{font-size:var(--t-lead);font-weight:var(--w-bold)}
.sp-price .tot b{font-size:var(--t-page);font-weight:var(--w-bold)}
.sp-card.compact .sp-grid{grid-template-columns:repeat(auto-fill,minmax(110px,1fr))}
.sp-form .chips{flex-wrap:wrap}
.sp-yn{display:flex;align-items:center;gap:8px;padding:8px 0;border-top:1px solid var(--rule)}
.sp-yn span{font-weight:var(--w-semi);font-size:var(--t-meta);min-width:150px}
.sp-sub{padding:2px 0 8px 16px;border-left:2px solid var(--int);margin-left:2px}
.jf-price{display:grid;grid-template-columns:1fr 1fr 1.2fr;gap:16px;align-items:end;padding:12px 0;border-top:1px solid var(--rule);border-bottom:1px solid var(--rule)}
.jf-price .inp{font-size:var(--t-title);height:48px}
.jf-ptotal{display:flex;flex-direction:column;gap:2px}
.jf-ptotal b{font-size:var(--t-page);font-weight:var(--w-bold);line-height:var(--lh-display)}
@media (max-width:720px){
  .sp-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .sp-special{grid-template-columns:1fr;gap:2px}
  .jf-price{grid-template-columns:1fr 1fr}
  .jf-ptotal{grid-column:span 2}
  .sp-yn{flex-wrap:wrap}
  .sp-yn .chip,.sp-form .chip{min-height:40px}
}

/* ============================================================
   THE OPERATIONS MAP
   ============================================================ */
.om-wrap{--om-over:var(--risk);--om-insp:var(--warn);--om-today:var(--int);--om-live:var(--ok)}
.om-keys{display:flex;flex-wrap:wrap;gap:0;border-top:1px solid var(--rule);border-bottom:1px solid var(--rule);margin:12px 0 12px}
.om-key{display:flex;align-items:baseline;gap:8px;padding:8px 20px 12px 0;margin-right:20px;background:none;border:0;border-right:1px solid var(--rule);
  color:var(--ink);cursor:pointer;min-height:44px}
.om-key:last-child{border-right:0}
.om-key i{width:12px;height:12px;border-radius:50%;align-self:center;flex:none}
.om-key b{font-size:var(--t-page);font-weight:var(--w-bold);line-height:var(--lh-solid)}
.om-key span{font-size:var(--t-meta);color:var(--ink-2)}
.om-key:not(.on){opacity:.42}
.om-key:not(.on) i{background:none!important;box-shadow:inset 0 0 0 2px currentColor}
.k-over i{background:var(--om-over)}.k-insp i{background:var(--om-insp)}.k-today i{background:var(--om-today)}.k-live i{background:var(--om-live)}
.om{position:relative;outline:none;border-radius:var(--r-sm,4px);overflow:hidden;
  background:var(--surface-2) repeating-linear-gradient(0deg,transparent 0 49px,rgba(127,127,127,.09) 49px 50px),
  repeating-linear-gradient(90deg,transparent 0 49px,rgba(127,127,127,.09) 49px 50px)}
.om:focus-visible{box-shadow:0 0 0 2px var(--int)}
.om-stage{position:relative;overflow:hidden;touch-action:none;cursor:grab;user-select:none;-webkit-user-select:none}
.om-stage:active{cursor:grabbing}
.om-layer{position:absolute;inset:0;will-change:transform}
.om-t{position:absolute;width:256px;height:256px;max-width:none;pointer-events:none}
html[data-theme="dark"] .om-t{filter:brightness(.82) contrast(1.05) saturate(.85)}
.om-svg{position:absolute;left:0;top:0;pointer-events:none;overflow:visible}
.om-svg .livepin{pointer-events:auto}
.om-yard rect{fill:var(--paper,#fff);stroke:var(--ink-2);stroke-width:1.5}.om-yard path{fill:none;stroke:var(--ink);stroke-width:1.3}
.om-m{position:absolute;width:44px;height:44px;margin:-24px 0 0 -24px;padding:0;border:0;background:none;cursor:pointer;z-index:2}
.om-m i{position:absolute;inset:11px;border-radius:50%;border:3px solid #fff;box-shadow:0 1px 4px rgba(0,0,0,.45);background:var(--om-live)}
.om-m.s-over i{background:var(--om-over)}.om-m.s-insp i{background:var(--om-insp)}.om-m.s-today i{background:var(--om-today)}
.om-m.approx i{border-style:dashed}
.om-m s{position:absolute;width:11px;height:11px;border-radius:50%;border:2px solid #fff;top:-1px;right:-1px;text-decoration:none;top:6px;right:6px}
.om-m s+s{right:auto;left:6px}
.om-m s.f-over{background:var(--om-over)}.om-m s.f-insp{background:var(--om-insp)}.om-m s.f-today{background:var(--om-today)}
.om-m.on{z-index:3}.om-m.on i{transform:scale(1.3);box-shadow:0 0 0 4px rgba(0,0,0,.18),0 2px 8px rgba(0,0,0,.5)}
.om-m:focus-visible i{outline:3px solid var(--int);outline-offset:2px}
.om-ctl{position:absolute;right:10px;top:10px;display:flex;flex-direction:column;gap:6px;z-index:4}
.om-b{width:40px;height:40px;border-radius:6px;border:1px solid var(--rule);background:var(--surface);color:var(--ink);
  font-size:var(--t-page);font-weight:var(--w-semi);line-height:var(--lh-solid);cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 1px 3px rgba(0,0,0,.18)}
.om-b:disabled{opacity:.4;cursor:default}
.om-legend{position:absolute;left:10px;bottom:10px;z-index:4;display:flex;flex-wrap:wrap;gap:4px 12px;max-width:calc(100% - 80px);
  padding:6px 12px;background:color-mix(in srgb,var(--surface) 90%,transparent);border:1px solid var(--rule);border-radius:6px;font-size:var(--t-tiny);color:var(--ink-2)}
.om-legend span{display:flex;align-items:center;gap:4px}
.om-legend i{width:10px;height:10px;border-radius:50%;flex:none}
.om-legend .k-more i{background:var(--surface);box-shadow:inset 0 0 0 2px var(--ink-3);width:8px;height:8px}
.om-nomap{position:absolute;left:50%;top:10px;transform:translateX(-50%);z-index:4;padding:4px 12px;background:var(--surface);border:1px solid var(--rule);border-radius:12px}
.om-pop{position:absolute;right:58px;top:10px;z-index:5;max-width:min(360px,calc(100% - 76px))}
.om-pop:empty{display:none}
.om-card{position:relative;background:var(--surface);border:1px solid var(--rule);border-top:3px solid var(--ink);border-radius:6px;
  padding:12px 16px 16px;box-shadow:0 6px 24px rgba(0,0,0,.25);max-height:calc(100% - 20px);overflow:auto}
.om-card h4{font-size:var(--t-lead);margin:6px 28px 0 0;line-height:var(--lh-heading)}
.om-x{position:absolute;right:6px;top:6px}
.om-flags{display:flex;flex-wrap:wrap;gap:4px;margin-right:28px}
.om-flag{font-family:var(--font-head);text-transform:uppercase;letter-spacing:var(--ls-caps);font-size:var(--t-micro);padding:2px 6px;border-radius:2px;color:#fff;background:var(--om-live);white-space:nowrap}
.om-flag.f-over{background:var(--om-over)}.om-flag.f-insp{background:var(--om-insp);color:#1b1300}.om-flag.f-today{background:var(--om-today)}
.om-kv{display:grid;grid-template-columns:auto minmax(0,1fr);gap:4px 12px;margin:12px 0 12px;font-size:var(--t-meta)}
.om-kv span{color:var(--ink-3);font-size:var(--t-small)}
.om-kv b{font-weight:var(--w-semi)}
.om-kv i{font-style:normal;font-weight:var(--w-body);font-size:var(--t-tiny)}
.om-acts{display:flex;gap:8px;flex-wrap:wrap}
.om-acts .btn{min-height:44px;flex:1}
.om-under{display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px;padding:12px 0;font-size:var(--t-meta)}
.om-runs{display:inline-flex;align-items:center;gap:8px;min-height:44px;padding:0 12px;border:1px solid var(--rule);border-radius:6px;background:var(--surface);color:var(--ink);cursor:pointer;font-size:var(--t-meta)}
.om-runs[aria-pressed="true"]{border-color:var(--int);color:var(--int)}
.om-att{border-top:2px solid var(--ink);padding-top:8px;margin-bottom:12px}
.om-row{display:grid;grid-template-columns:auto minmax(0,1fr) minmax(0,1.2fr);gap:12px;align-items:center;width:100%;padding:12px 0;
  border:0;border-bottom:1px solid var(--rule);background:none;color:var(--ink);text-align:left;cursor:pointer;min-height:48px}
.om-row:hover{background:var(--surface-2)}
.om-row b{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:var(--t-meta)}
.om-row .tiny{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@media (max-width:720px){
  .om-keys{display:grid;grid-template-columns:1fr 1fr;border-bottom:0}
  .om-key{margin:0;padding:12px 8px 12px 0;border-right:0;border-bottom:1px solid var(--rule)}
  .om-stage{height:62vh!important;min-height:360px}
  .om-legend{display:none}
  .om-pop{left:0;right:0;top:auto;bottom:0;max-width:none}
  .om-card{border-radius:12px 12px 0 0;max-height:70vh;padding-bottom:20px}
  .om-row{grid-template-columns:auto minmax(0,1fr);}
  .om-row .tiny{grid-column:span 2}
  .om-b{width:44px;height:44px}
}

/* ===== Handovers — erect and dismantle ===== */
.req{font-size:var(--t-tiny);font-weight:var(--w-bold);color:var(--risk);text-transform:uppercase;letter-spacing:var(--ls-caps);margin-left:4px}
.ho-head{border:1px solid var(--border);border-radius:var(--r3);padding:16px;background:var(--surface-2)}
.ho-head dl{display:grid;grid-template-columns:1fr 1fr;gap:12px 16px;margin:12px 0 8px}
.ho-head dl div.wide{grid-column:1/-1}
.ho-head dt{font-size:var(--t-tiny);color:var(--text-3);text-transform:uppercase;letter-spacing:var(--ls-caps)}
.ho-head dd{margin:2px 0 0;font-weight:var(--w-semi);font-size:var(--t-meta);overflow-wrap:anywhere}
.ho-phase{display:inline-block;font-family:var(--font-head);font-weight:var(--w-bold);letter-spacing:var(--ls-caps);font-size:var(--t-tiny);
  padding:4px 12px;border-radius:999px;background:var(--navy-700);color:#fff}
.ho-phase.dismantle{background:var(--warn);color:#111}
.ho-cats{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.ho-cat{border:1px solid var(--border);border-radius:var(--r2);padding:12px;background:var(--surface-2);display:flex;flex-direction:column;gap:8px;min-width:0}
.ho-cat.need{border-color:var(--warn-line,var(--warn))}
.ho-cat.ok{border-color:var(--ok);background:var(--ok-bg)}
.ho-cat.fail{border-color:var(--risk);background:var(--risk-bg)}
.ho-cat-h{display:flex;justify-content:space-between;gap:6px;align-items:baseline;font-size:var(--t-meta)}
.ho-ths{display:flex;gap:6px;flex-wrap:wrap}
.ho-th{position:relative;width:72px;height:72px;border-radius:8px;overflow:hidden;background:var(--surface-3)}
.ho-th img{width:100%;height:100%;object-fit:cover;display:block}
.ho-th i{position:absolute;left:0;right:0;bottom:0;font-style:normal;font-size:var(--t-micro);font-weight:var(--w-bold);text-align:center;
  background:rgba(0,0,0,.6);color:#fff;padding:2px}
.ho-th.fail i{background:var(--risk)}
.ho-err{color:var(--risk);font-weight:var(--w-semi)}
.ho-take{min-height:44px;margin-top:auto}
.ho-check{width:100%;min-height:48px;display:flex;justify-content:space-between;align-items:center;gap:12px;text-align:left;
  padding:12px 12px;border-radius:var(--r2);background:var(--surface-2);border:1px solid var(--border);font-size:var(--t-meta);font-weight:var(--w-semi)}
.ho-check.on{background:var(--ok-bg);border-color:var(--ok);color:var(--ok)}
.ho-duty{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.ho-yn{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.ho-opt{min-height:48px;padding:8px 12px;border-radius:var(--r2);background:var(--surface-2);border:1px solid var(--border);
  text-align:left;display:flex;flex-direction:column;gap:2px;font-size:var(--t-meta)}
.ho-opt.on{background:var(--ok-bg);border-color:var(--ok)}
.ho-opt.on.warn{background:var(--warn-bg);border-color:var(--warn)}
.ho-confirm{display:flex;gap:12px;align-items:flex-start;padding:12px;border-radius:var(--r2);border:1px solid var(--border);
  background:var(--surface-2);font-size:var(--t-meta);font-weight:var(--w-semi);min-height:48px;cursor:pointer}
.ho-confirm input{width:22px;height:22px;flex:none;margin-top:1px}
.ho-miss{margin:4px 0 0;padding-left:20px;font-size:var(--t-meta);font-weight:var(--w-semi)}
.ho-miss li{margin:2px 0}
#ho_submit.dim{opacity:.6}
.ho-chips{display:flex;flex-wrap:wrap;gap:6px}
.ho-chips.sm{margin-top:6px}
.ho-chip{display:inline-flex;align-items:center;gap:4px;font-size:var(--t-tiny);font-weight:var(--w-bold);padding:4px 8px;border-radius:999px;
  background:var(--surface-3);border:1px solid var(--border)}
.ho-chip.ok{background:var(--ok-bg);border-color:var(--ok);color:var(--ok)}
.ho-chip.warn{background:var(--warn-bg);border-color:var(--warn);color:var(--warn)}
.ho-chip.risk{background:var(--risk-bg);border-color:var(--risk);color:var(--risk)}
.hocam{position:fixed;inset:0;z-index:9999;background:#05070D;color:#fff;display:flex;flex-direction:column;
  padding:env(safe-area-inset-top) 0 env(safe-area-inset-bottom)}
.hocam-top{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;font-size:var(--t-body)}
.hocam-x{width:44px;height:44px;border-radius:50%;background:rgba(255,255,255,.12);color:#fff;display:grid;place-items:center}
.hocam-mid{flex:1;display:grid;place-items:center;min-height:0;padding:0 12px}
.hocam-mid img{max-width:100%;max-height:100%;object-fit:contain;border-radius:8px}
.hocam-empty{text-align:center;opacity:.7}
.hocam-foot{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:16px 16px 20px}
.hocam-foot .block{grid-column:1/-1;min-height:56px}
.hocam-foot .lnk{grid-column:1/-1;color:rgba(255,255,255,.8);text-align:center;min-height:44px;text-decoration:underline}
.hocam-foot .btn.lg{min-height:56px;font-weight:var(--w-bold)}
.ph-rail{display:flex;flex-direction:column;gap:2px}
.ph-row{display:flex;gap:12px;align-items:flex-start;padding:8px 0;border-left:2px solid var(--border);margin-left:6px;padding-left:12px;position:relative}
.ph-dot{position:absolute;left:-7px;top:12px;width:12px;height:12px;border-radius:50%;background:var(--surface-3);border:2px solid var(--border)}
.ph-row.completed .ph-dot,.ph-row.mid.on .ph-dot{background:var(--ok);border-color:var(--ok)}
.ph-row.awaiting_handover .ph-dot,.ph-row.in_progress .ph-dot{background:var(--warn);border-color:var(--warn)}
.ph-acts{flex:none}
.tag.ph-completed{background:var(--ok-bg);color:var(--ok)}
.tag.ph-awaiting_handover,.tag.ph-in_progress{background:var(--warn-bg);color:var(--warn)}
.ph-pills{display:flex;gap:6px;flex-wrap:wrap}
img.wosfile-loading{background:var(--surface-3)}
@media (max-width:420px){ .ho-cats,.ho-duty{grid-template-columns:1fr} }

.om-row.on{background:var(--surface-3);box-shadow:inset 3px 0 0 var(--brand)}
.perm-tbl th.c,.perm-tbl td.c{text-align:center;width:72px}
.perm-tbl input[type=checkbox]{width:22px;height:22px}
.tag.needs{background:var(--warn-bg);color:var(--warn)}

/* ===== The morning: vehicle check, load list, board ===== */
.morning{border-left:var(--bar) solid var(--brand)}
.mo-today{display:grid;grid-template-columns:1fr 1fr;gap:12px 16px;margin:12px 0 12px}
.mo-today>div{display:flex;flex-direction:column;gap:2px;min-width:0}
.mo-today .wide{grid-column:1/-1}
.mo-today b{font-size:var(--t-meta);overflow-wrap:anywhere}
.mo-steps{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}
.mo-btn{width:100%;min-height:60px;display:flex;align-items:center;gap:12px;padding:12px 12px;border-radius:var(--r2);
  background:var(--surface-2);border:1px solid var(--border);text-align:left}
.mo-btn b{display:block;font-size:var(--t-body)}
.mo-btn .tiny{display:block;opacity:.8}
.mo-n{width:34px;height:34px;flex:none;border-radius:50%;display:grid;place-items:center;font-weight:var(--w-bold);background:var(--surface-3)}
.mo-step.ok .mo-n{background:var(--ok);color:#fff}
.mo-step.warn .mo-n{background:var(--warn);color:#111}
.mo-step.next .mo-btn{border-color:var(--brand);box-shadow:inset 3px 0 0 var(--brand)}
.mo-step.na{opacity:.6}
.fvc-row{display:grid;grid-template-columns:minmax(0,1fr) repeat(3,auto);gap:8px;align-items:center;padding:12px;border-radius:var(--r2);
  border:1px solid var(--border);background:var(--surface-2)}
.fvc-row .grow{display:flex;flex-direction:column;gap:2px;min-width:0}
.fvc-row.ok{border-color:var(--ok)}
.fvc-row.fault{border-color:var(--risk);background:var(--risk-bg)}
.fvc-b{height:52px;width:56px;border-radius:var(--r2);border:1px solid var(--border);background:var(--surface-3);display:grid;place-items:center}
.fvc-b.ok.on{background:var(--ok);color:#fff;border-color:var(--ok)}
.fvc-b.bad.on{background:var(--risk);color:#fff;border-color:var(--risk)}
.fvc-b.na{width:48px;font-size:12px;font-weight:700;color:var(--text-2)}
.fvc-b.na.on{background:var(--text-2);color:var(--surface);border-color:var(--text-2)}
.fvs{display:flex;flex-direction:column;border:1px solid var(--border);border-radius:var(--r2);overflow:hidden}
.fvs-row{display:grid;grid-template-columns:28px minmax(0,1fr) auto;gap:10px;align-items:center;padding:9px 12px;border-top:1px solid var(--border);font-size:15px}
.fvs-row:first-child{border-top:0}
.fvc-fault{grid-column:1/-1;display:flex;flex-direction:column;gap:8px}
.ll-head{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;padding:12px;border:1px solid var(--border);border-radius:var(--r3);background:var(--surface-2)}
.ll-head>div{display:flex;flex-direction:column;gap:2px;min-width:0}
.ll-head .wide{grid-column:1/-1}
.ll-group{display:flex;flex-direction:column;gap:6px}
.ll-gh{font-family:var(--font-head);font-weight:var(--w-bold);letter-spacing:var(--ls-caps);text-transform:uppercase;font-size:var(--t-tiny);color:var(--text-3);margin-top:6px}
.ll-row{display:flex;align-items:center;gap:12px;padding:8px 12px;border-radius:var(--r2);border:1px solid var(--border);background:var(--surface-2)}
.ll-row.ok{border-color:var(--ok)}
.ll-row.short{border-color:var(--warn);background:var(--warn-bg)}
.ll-item{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.ll-item b{font-size:var(--t-body)}
.ll-warn{color:var(--warn);font-weight:var(--w-bold)}
.ll-ok{color:var(--ok);font-weight:var(--w-bold)}
.ll-q{display:flex;align-items:center;gap:6px;flex:none}
.ll-q.ro{font-weight:var(--w-bold);font-size:var(--t-body);min-width:44px;text-align:right}
.ll-b,.ll-all{width:48px;height:48px;border-radius:var(--r2);border:1px solid var(--border);background:var(--surface-3);font-size:var(--t-page);font-weight:var(--w-bold);display:grid;place-items:center}
.ll-all{background:var(--ok-bg);color:var(--ok);border-color:var(--ok)}
.ll-n{width:76px;height:48px;text-align:center;font-size:var(--t-title);font-weight:var(--w-bold);border-radius:var(--r2);border:1px solid var(--border);background:var(--surface);color:var(--text)}
.ll-by{display:flex;justify-content:space-between;align-items:baseline;padding:12px 12px;border-top:2px solid var(--text)}
.mb-list{display:flex;flex-direction:column;margin-top:8px}
.mb-row{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr);gap:12px;padding:12px 0;border-bottom:1px solid var(--border)}
.mb-v{display:flex;flex-direction:column;gap:2px}
.mb-st{display:flex;flex-direction:column;gap:2px}
.mb-s{font-size:var(--t-meta);font-weight:var(--w-semi)}
.mb-s.ok{color:var(--ok)} .mb-s.warn{color:var(--warn)} .mb-s.todo{color:var(--text-3)}
@media (max-width:480px){
  .ll-head{grid-template-columns:1fr 1fr}
  .ll-b,.ll-all{width:44px;height:48px}
  .ll-n{width:68px}
  .mb-row{grid-template-columns:1fr}
}

/* ===== RAMS ===== */
.ra-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px}
.ra-grid .field.wide{grid-column:1/-1}
.ra-sec{border-top:1px solid var(--border);padding-top:12px;margin-top:6px;display:flex;flex-direction:column;gap:8px}
.ra-sh{font-weight:var(--w-bold);font-size:var(--t-body)}
.ra-std{font-size:var(--t-meta);color:var(--text-2);background:var(--surface-2);border-radius:var(--r2);padding:12px 12px}
.ra-std ol,.ra-std ul{margin:6px 0;padding-left:20px}
.ra-std li{margin:2px 0}
.ra-std mark{background:var(--warn-bg);color:var(--warn);padding:0 2px;border-radius:2px}
.ra-risk{border:1px solid var(--border);border-radius:var(--r2);padding:12px 12px;margin-bottom:8px;display:flex;flex-direction:column;gap:6px}
.ra-risk.off{opacity:.55}
.ra-scores{display:flex;flex-wrap:wrap;gap:12px}
.ra-scores>div{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.ra-legend{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
.ra-signs{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.tag.ok{background:var(--ok-bg);color:var(--ok)}
.tag.risk{background:var(--risk-bg);color:var(--risk)}
.tag.warn{background:var(--warn-bg);color:var(--warn)}
.ra-list{display:flex;flex-direction:column}
.ra-row{display:flex;align-items:center;gap:12px;padding:12px 4px;border-bottom:1px solid var(--border);text-align:left;width:100%;min-height:48px}
.ra-row .grow{display:flex;flex-direction:column;gap:2px;min-width:0}
.ra-ver{border-bottom:1px solid var(--border);padding:8px 0;display:flex;flex-direction:column;gap:4px}
.rfj-key{font-size:var(--t-meta);line-height:var(--lh-ui);padding:12px 12px;border-left:3px solid var(--brand);background:var(--surface-2);border-radius:var(--r2)}
.ra-scores select{width:auto;min-width:170px;max-width:100%}

/* ---------- site evidence (41j-evidence.js) ---------- */
.ev-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
@media (min-width:560px){.ev-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}
.ev-cat{position:relative;display:flex;flex-direction:column;align-items:flex-start;gap:6px;min-height:74px;padding:12px;border-radius:var(--r2);
  background:var(--surface-2);border:1px solid var(--border);text-align:left;font-size:var(--t-meta);color:var(--text)}
.ev-cat.on{background:var(--ok-bg);border-color:var(--ok)}
.ev-cat.dmg{border-color:var(--warn)}
.ev-cat .ev-n{position:absolute;top:8px;right:9px;min-width:22px;height:22px;border-radius:var(--r-pill);background:var(--ok);color:#fff;
  font-size:var(--t-small);font-weight:var(--w-bold);display:flex;align-items:center;justify-content:center;padding:0 6px}
.ev-shots{display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:8px}
.ev-shot{position:relative;display:flex;flex-direction:column;gap:4px;font-size:var(--t-micro)}
.ev-shot img,.ev-shot .ev-vid{width:100%;aspect-ratio:4/3;object-fit:cover;border-radius:var(--r1);background:var(--surface-2);
  display:flex;align-items:center;justify-content:center;flex-direction:column;gap:2px;color:var(--text-2)}
.ev-shot.sending img{opacity:.55}
.ev-shot.failed img{outline:2px solid var(--risk)}
.ev-shot .ev-x{position:absolute;top:4px;right:4px;width:26px;height:26px;border-radius:var(--r-pill);background:rgba(0,0,0,.55);color:#fff;
  display:flex;align-items:center;justify-content:center}
.ev-cap{color:var(--text-2);line-height:var(--lh-heading)}
.ev-row{margin-top:12px}
.ev-tl{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin-top:6px}
.ev-st{display:flex;flex-direction:column;gap:4px;align-items:flex-start}

/* ---------- the brief (38l-brief.js) ---------- */
.brief-items{display:flex;flex-direction:column}
.brief-item{display:flex;align-items:center;gap:8px;width:100%;min-height:44px;padding:8px 2px;text-align:left;
  border-top:1px solid var(--border);color:var(--text);font-size:var(--t-meta);background:none}
.brief-item:first-child{border-top:0}
.brief-item .grow{flex:1;min-width:0}
/* Photos in emails and on requests (36i-mailphotos.js) */
.ml-thumbs,.jf-photos{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.ml-thumb{width:112px;height:112px;padding:0;border:1px solid var(--rule);border-radius:8px;overflow:hidden;background:var(--surface-2);cursor:zoom-in}
.ml-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.ml-thumb img:not([src]){opacity:0}
.ml-thumb.bad{cursor:default;opacity:.5}
.ml-big{display:grid;place-items:center;min-height:200px}
.ml-big img{max-width:100%;max-height:70vh;border-radius:6px}
@media (max-width:699px){.ml-thumb{width:calc(33.33% - 6px);height:auto;aspect-ratio:1}}
.kit-line .fvc-b{width:auto;min-width:56px;padding:0 10px;height:40px;font-size:13px;font-weight:700}
/* ============================================================
   THE WEEK GRID (34h-planweek-ui.js)

   Gangs down the side, days across, a card where a gang is on a job.
   The density of Lewis's mock-up, in this system's own tokens: the
   job's kind is its colour, carried as a left rule and a faint wash
   so it still reads in sunlight and to a colour-blind eye the kind is
   also written on the card.
   ============================================================ */
.pw-head{flex-wrap:wrap;justify-content:flex-end}
.pw-range{font-weight:var(--w-semi);font-size:var(--t-meta);min-width:150px;text-align:center}
.pw-actions{flex-wrap:wrap;gap:var(--s2);margin:calc(var(--s3)*-1) 0 var(--s4)}
.pw-tools{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3);margin-bottom:var(--s4)}
.pw-tools .sel{width:auto;min-width:130px;height:38px}
.pw-search{position:relative;flex:1 1 240px;max-width:420px;display:flex;align-items:center}
.pw-search .ic{position:absolute;left:12px;color:var(--ink-3);pointer-events:none}
.pw-search .inp{padding-left:36px;height:38px;width:100%}

.pw{display:grid;grid-template-columns:220px minmax(0,1fr);gap:var(--s4);align-items:start}
.pw-side,.pw-main,.pw-lorries{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r3)}
.pw-side{padding:var(--s3);position:sticky;top:calc(var(--topbar) + var(--s3))}
.pw-sidetabs{display:flex;width:100%;margin-bottom:var(--s3)}
.pw-sidetabs button{flex:1}
.pw-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}
.pw-gang{display:flex;align-items:center;gap:4px;border-radius:var(--r2);border:1px solid var(--rule)}
.pw-gang[draggable="true"]{cursor:grab}
.pw-gang.pw-dragging{opacity:.45}
.pw-grip{color:var(--ink-3);padding-left:6px;display:grid;place-items:center}
.pw-gang>button{flex:1;display:grid;grid-template-columns:auto 1fr auto;grid-template-rows:auto auto;
  column-gap:8px;align-items:center;text-align:left;padding:8px 12px;min-height:44px;color:var(--ink)}
.pw-gang>button .ic{grid-row:1/3;color:var(--ink-2)}
.pw-gang>button b{font-size:var(--t-meta)}
.pw-gang>button .tiny{grid-column:2/4}
.pw-gang>button:hover{background:var(--surface-3)}
.pw-dot{width:8px;height:8px;border-radius:50%;background:var(--ok)}
.pw-dot.warn{background:var(--gold)}
.pw-hint{margin-top:var(--s3)}

.pw-main{min-width:0;overflow:hidden}
.pw-scroll{overflow-x:auto}
.pw-grid{display:grid;width:100%}
.pw-grid>div{border-bottom:1px solid var(--rule);border-right:1px solid var(--rule)}
.pw-corner,.pw-dh{background:var(--surface-2);position:sticky;top:0;z-index:1}
.pw-dh{padding:12px 8px;text-align:center;display:flex;flex-direction:column;gap:2px}
.pw-dh b{font-size:var(--t-meta)}
.pw-dh.today b{color:var(--int)}
.pw-dh.wkend{background:var(--surface-3)}
.pw-rh{padding:12px;display:flex;flex-direction:column;justify-content:center;gap:2px;background:var(--surface-2)}
.pw-rh b{font-size:var(--t-meta)}
.pw-cell{padding:6px;display:flex;flex-direction:column;gap:6px;min-height:64px;transition:background var(--dur),box-shadow var(--dur)}
.pw-cell.pw-over{background:var(--int-soft);box-shadow:inset 0 0 0 2px var(--int)}
.pw-dragmode .pw-cell.empty{background:color-mix(in srgb,var(--int) 5%,transparent)}
.pw-add{flex:1;min-height:44px;border:1px dashed var(--rule-strong);border-radius:var(--r2);color:var(--ink-3);
  font-size:var(--t-small);font-weight:var(--w-semi);display:flex;align-items:center;justify-content:center;gap:6px}
.pw-add:hover,.pw-add:focus-visible{color:var(--int);border-color:var(--int);background:var(--int-soft)}
.pw-free{font-size:var(--t-small);color:var(--ink-3);margin:auto}

.pw-card{--c:var(--ink-3);border-radius:var(--r2);border:1px solid color-mix(in srgb,var(--c) 40%,var(--rule));
  border-left:4px solid var(--c);background:color-mix(in srgb,var(--c) 18%,var(--surface-2));transition:transform var(--dur),opacity var(--dur)}
.pw-card[draggable="true"]{cursor:grab}
.pw-card.pw-dragging{opacity:.35;transform:scale(.97)}
.pw-card.pw-over{box-shadow:0 0 0 2px var(--int)}
.pw-card.pw-hide{display:none}
.pw-card-b{display:flex;flex-direction:column;gap:4px;width:100%;text-align:left;padding:6px 8px;color:var(--ink)}
.pw-card-k{font-style:normal;font-size:var(--t-micro);font-weight:var(--w-semi);letter-spacing:var(--ls-caps);text-transform:uppercase;color:var(--ink-2)}
.pw-card-t{font-size:var(--t-small);font-weight:var(--w-bold);line-height:var(--lh-heading);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.pw-card-m{display:flex;flex-direction:column;align-items:flex-start;gap:2px;font-size:var(--t-micro);color:var(--ink-2)}
.pw-card-m span{display:inline-flex;align-items:center;gap:2px;min-width:0;max-width:100%;white-space:nowrap}
/* 27 October: a day column is too narrow for the town and the lorry side by
   side at every desk width up to 1512 and on an iPad, so the two were
   squeezed into each other, icons gone and "BournemouthLorry 3" touching.
   On the board the lorry goes under the town on every card, so the cards
   in a column read alike; the phone's wide card keeps them on one line. */
.pw-card-m svg{flex:none}
/* the postcode never cut off (Waj, 3 Nov 2026: "PO20 8Q" on a narrow card) */
.pw-card-m .pw-loc{white-space:normal;align-items:flex-start}
.pw-card-m .pw-loc>span{display:inline;white-space:normal}
.pw-card-m .pw-pc{white-space:nowrap;font-weight:var(--w-bold);color:var(--ink)}
.pw-ghost{position:fixed;top:-500px;left:-500px;transform:rotate(-2deg);box-shadow:0 10px 24px rgba(0,0,0,.35);
  background:color-mix(in srgb,var(--c) 22%,var(--surface-2))}
.pw-warn{color:var(--warn)}
html[data-theme="dark"] .pw-warn{color:var(--gold)}

.pw-lorries{margin-top:var(--s4);padding:var(--s3) var(--s4)}
.pw-lorries .sec-title{margin-bottom:var(--s3)}
.pw-lstrip{display:grid;grid-template-columns:repeat(auto-fit,minmax(165px,1fr));gap:var(--s3)}
.pw-lorry{display:flex;gap:12px;align-items:center;text-align:left;padding:12px 12px;border-radius:var(--r2);
  border:1px solid var(--rule);background:var(--surface-2);color:var(--ink);min-height:56px}
.pw-lorry:hover,.pw-lorry:focus-visible{border-color:var(--int-line);background:var(--surface-3)}
.pw-lorry-i{color:var(--ink-2);display:grid;place-items:center;width:36px}
.pw-lorry-t{display:flex;flex-direction:column;gap:1px;min-width:0}
.pw-lorry-t b{font-size:var(--t-meta)}
.pw-st{display:inline-flex;align-items:center;gap:6px;font-size:var(--t-small);font-weight:var(--w-semi);color:var(--ok)}
.pw-st i{width:7px;height:7px;border-radius:50%;background:currentColor}
.pw-st.warn{color:var(--warn)} .pw-st.risk{color:var(--risk)}
html[data-theme="dark"] .pw-st.ok{color:#3FC88E}
html[data-theme="dark"] .pw-st.warn{color:var(--gold)}

/* the sheets */
.pw-ctx{display:grid;grid-template-columns:1fr auto;gap:var(--s4);padding:var(--s3) 0;border-top:1px solid var(--rule);border-bottom:1px solid var(--rule)}
.pw-ctx>div{display:flex;flex-direction:column;gap:2px;min-width:0}
.pw-recs{display:flex;flex-direction:column}
.pw-rec{display:flex;gap:var(--s3);align-items:center;padding:var(--s3) 0 var(--s3) var(--s3);
  border-left:3px solid var(--c);border-bottom:1px solid var(--rule)}
.pw-rec-t{display:flex;flex-wrap:wrap;gap:6px 8px;align-items:baseline}
.pw-rec-t b{font-size:var(--t-body)}
.pw-why{font-size:var(--t-small);color:var(--ink-2);margin-top:4px}
.pw-book{flex:none;min-width:72px}
.pw-not summary{cursor:pointer;font-size:var(--t-small);color:var(--ink-2);padding:6px 0}
.pw-not ul{margin:6px 0 0;padding-left:20px;font-size:var(--t-small);display:flex;flex-direction:column;gap:4px}
.pw-days{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
.pw-daybtn{display:flex;flex-direction:column;align-items:center;padding:6px 12px;min-width:64px;min-height:44px;
  border-radius:var(--r2);border:1px solid var(--rule);color:var(--ink-3);font-size:var(--t-small)}
.pw-daybtn.free{color:var(--ink);border-color:var(--rule-strong)}
.pw-daybtn.on{border-color:var(--int);background:var(--int-soft);color:var(--ink)}
.pw-daybtn[disabled]{opacity:.45}
.pw-daybtn span{font-size:var(--t-micro)}
.pw-team{padding:var(--s3) 0;border-top:1px solid var(--rule);border-bottom:1px solid var(--rule)}
.pw-size{display:inline-flex;align-items:center;gap:8px}
.pw-size .btn{min-width:38px}
.pw-men{list-style:none;margin:var(--s3) 0;padding:0;display:flex;flex-direction:column;gap:6px}
.pw-man{display:flex;align-items:center;gap:12px}
.pw-man b{display:block;font-size:var(--t-meta)}
.pw-rate{font-size:var(--t-small);font-weight:var(--w-bold);padding:2px 8px;border-radius:var(--r1);background:var(--int-soft);color:var(--ink)}
.pw-rate.un{background:var(--surface-3);color:var(--ink-2);font-weight:var(--w-semi)}
.pw-notes{margin:var(--s2) 0 0;padding-left:20px;font-size:var(--t-small);color:var(--ink-2);display:flex;flex-direction:column;gap:2px}
.pw-refuse ul{margin:6px 0 0;padding-left:20px}

/* ---------- the phone ---------- */
.pwm{display:flex;flex-direction:column;gap:var(--s3);margin-top:var(--s3)}
.pwm-day{font-size:var(--t-lead);font-weight:var(--w-bold)}
.pwm-gang{display:flex;flex-direction:column;gap:8px;padding:var(--s3);border:1px solid var(--rule);border-radius:var(--r3);background:var(--surface)}
.pwm-gang header{display:flex;flex-direction:column;gap:2px}
.pwm-gang .pw-card-b{min-height:52px;padding:12px 12px}
.pwm-gang .pw-card-t{font-size:var(--t-meta)}
.pwm-gang .pw-card-m{font-size:var(--t-small);flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;column-gap:6px}
.pwm-add{min-height:48px;border:1px dashed var(--rule-strong);border-radius:var(--r2);color:var(--ink-2);
  font-weight:var(--w-semi);font-size:var(--t-meta);display:flex;align-items:center;justify-content:center;gap:8px;width:100%}
.pwm-add:active{background:var(--int-soft);color:var(--int)}
.pwm-lh{margin-top:var(--s3)}
.pwm-lorries{display:flex;flex-direction:column;gap:6px}
.pwm-lorry{display:flex;align-items:center;gap:12px;min-height:52px;padding:8px 12px;border:1px solid var(--rule);
  border-radius:var(--r2);background:var(--surface);color:var(--ink);text-align:left;width:100%}
.pwm-lorry b{display:block;font-size:var(--t-meta)}

@media (max-width:1100px){
  .pw{grid-template-columns:1fr}
  .pw-side{position:static}
  .pw-list{flex-direction:row;flex-wrap:wrap}
  .pw-gang{flex:1 1 180px}
}
@media (max-width:640px){
  .pw-ctx{grid-template-columns:1fr}
  .pw-rec{flex-wrap:wrap}
  .pw-book{width:100%;min-height:44px}
  .pw-actions{margin-top:0}
  .pw-daybtn{flex:1 1 56px}
}

/* A dismantle: the job type's own colour, a broken edge and a lighter
   fill — the same scaffold, coming down rather than going up. */
.pw-card.pw-down{border-left-style:dashed;background:color-mix(in srgb,var(--c) 7%,var(--surface-2))}
.pw-card.pw-down .pw-card-k{color:var(--ink)}
.jt{display:inline-flex;align-items:center;gap:3px;flex-wrap:wrap}
.jt svg{flex:none}
/* ============================================================
   V1 FOUNDATION (17 October 2026)

   One layer, loaded last, that sets the design language for V1.
   It exists because the rules it replaces are spread over ten
   files that each won at a different time. Everything here is a
   decision, not a nudge; if one of these looks wrong somewhere,
   fix it here rather than adding another override elsewhere.

   Selectors carry `html:root` so this layer wins on specificity
   as well as order, without !important.

   1. TYPE. Titles are the system face, set tight. Oswald is for
      the figure that is the point of a panel and nothing else.
   2. CAPITALS. Sentence case everywhere, except the small label
      printed above a figure on the day board and on a tile.
   3. THE RAIL. The current item is a tint, not a solid block.
   4. NOT READY YET. A quiet dot on the rail, not an amber badge.
   5. THE DAY BOARD. However many cells, no empty panel.
   6. SCROLLERS. A strip that scrolls sideways shows that it does.
   7. SHEETS. A grabber on the phone sheet (behaviour in 51-motion.js).
   8. CONTRAST. prefers-contrast: more gets solid surfaces.
   ============================================================ */

/* ---------- 0. ZOOM ----------
   Pinch-zoom is allowed (the viewport no longer locks the scale).
   Double-tap zoom, which made the page jump, is stopped here. */
html{touch-action:manipulation}

/* ---------- 1. TYPE ---------- */
html:root :is(.page-title,.dsp-lg,h1.dsp){
  font-family:var(--font);text-transform:none;
  font-weight:var(--w-bold);letter-spacing:var(--ls-8);line-height:var(--lh-display);
}
html:root :is(.dsp-md,.sec-title,.mtop-title,.jf-sec h2,.frtitle){
  font-family:var(--font);text-transform:none;
  font-weight:var(--w-semi);letter-spacing:var(--ls-2);line-height:var(--lh-heading);
}
html:root :is(.dsp,.dsp-sm){font-family:var(--font);text-transform:none;letter-spacing:var(--ls-0)}

/* the figures keep the company's face */
html:root :is(.fig,.today-v,.money,.tile .v,.hs-score,.gauge-mv,.gauge-of,.gauge-fig b,
  .factor .fv,.dispatch-n,.lt-n,.cs-n,.yl-n,.frn,.capd .p,.jf-bigprice .num,
  .daychip span,.step .n,.pinkey,.pinentry,[data-gl-code],.wordmark b,.wordmark i){
  font-family:var(--font-fig);font-variant-numeric:tabular-nums;
}
/* a sentence that happens to carry the .fig class is still a sentence */
html:root :is(.insight-stat.fig,.jc-when .fig){font-family:var(--font);letter-spacing:var(--ls-fig)}
html:root .jc-when .fig{font-weight:var(--w-semi)}
/* large figures want negative tracking, never positive */
html:root .today-v{letter-spacing:var(--ls-fig)}

/* ---------- 2. CAPITALS ---------- */
html:root :is(.eyebrow,.stencil,.nav-label,.pill,.lbl,.tbl th,.att-k,.kv-k,.capd .d,
  .bars .b>s,.lifestep .p,.daychip b,.ptag-tt,.ghead-t,.glist-k,.gconf-h,.gsug-h,
  .warnfix,.contentflag,.yardbig span,.demoway-r,.rotabox .rb-d,.appwho,.plan-w,
  .gperson em,.waitw em,.fperson em,.frun span,.tp-n,.jf-gl,.jf-due,.jf-act,.sp-h,
  .om-flag,.req,.ho-head dt,.ll-gh,.pw-card-k){
  text-transform:none;letter-spacing:0;font-family:var(--font);
}
html:root .tbl td::before{text-transform:none;letter-spacing:var(--ls-0);font-family:var(--font)}
/* sentence case at 11px is too faint to carry a heading, so the
   labels that sit above content step up one size */
html:root :is(.eyebrow,.nav-label,.pill,.lbl,.tbl th,.att-k,.kv-k,.sp-h,.jf-gl,.om-flag,.demoway-r){
  font-size:var(--t-small);font-weight:var(--w-semi);
}
html:root .eyebrow{color:var(--ink-2)}
html:root .nav-label{color:var(--ink-3)}
html:root .pill{height:auto;min-height:22px;padding:2px 8px}
/* the one place capitals stay: the label printed above a figure */
html:root :is(.today-k,.tile .k){
  font-family:var(--font);text-transform:uppercase;letter-spacing:var(--ls-caps);
  font-size:var(--t-tiny);font-weight:var(--w-semi);color:var(--ink-3);
}

/* ---------- 3. THE RAIL ---------- */
html:root .nav-item.on{
  background:var(--int-soft);border-color:var(--int-line);
  color:var(--ink);font-weight:var(--w-semi);box-shadow:none;
}
html:root .nav-item.on .ic{color:var(--int-hi);opacity:1}
html:root[data-theme="light"] .nav-item.on{background:rgba(255,255,255,.14);border-color:rgba(255,255,255,.28);color:#fff}
html:root[data-theme="light"] .nav-item.on .ic{color:#fff}

/* ---------- 4. NOT READY YET ---------- */
/* The text stays in the element for screen readers; on screen it is
   a dot. The bubble on the screen itself still says it in words. */
html:root .side .nav-item .nr-chip{
  font-size:0;line-height:0;padding:0;gap:0;
  width:6px;height:6px;min-width:6px;border-radius:50%;
  background:var(--ink-3);box-shadow:none;border:0;
}
html:root .nav-item.nr-dim .nav-lbl,html:root .nav-item.nr-dim .ic{opacity:.62}

/* ---------- 5. THE DAY BOARD ---------- */
@media (min-width:1081px){
  html:root .today:has(> .today-c:nth-child(7):last-child){grid-template-columns:repeat(7,minmax(0,1fr))}
  html:root .today:has(> .today-c:nth-child(8):last-child){grid-template-columns:repeat(4,minmax(0,1fr))}
}
@media (min-width:601px) and (max-width:1080px){
  html:root .today > .today-c:last-child:nth-child(3n+1){grid-column:span 3}
  html:root .today > .today-c:last-child:nth-child(3n+2){grid-column:span 2}
}

/* ---------- 6. SCROLLERS ---------- */
/* 51-motion.js sets data-more on any sideways scroller that has
   more content to its right; the edge fades so the cut-off reads
   as "there is more", not as a fault. */
html:root [data-more="r"]{
  -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 40px),transparent);
          mask-image:linear-gradient(90deg,#000 calc(100% - 40px),transparent);
}
html:root [data-more="lr"]{
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 40px,#000 calc(100% - 40px),transparent);
          mask-image:linear-gradient(90deg,transparent,#000 40px,#000 calc(100% - 40px),transparent);
}
html:root [data-more="l"]{
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 40px);
          mask-image:linear-gradient(90deg,transparent,#000 40px);
}

/* ---------- 7. SHEETS ---------- */
.sheet-grab{display:none}
@media (max-width:720px){
  html:root .sheet.modal .sheet-grab{
    display:block;position:absolute;top:0;left:0;right:0;height:22px;z-index:2;
    touch-action:none;cursor:grab;
  }
  html:root .sheet.modal .sheet-grab::before{
    content:"";position:absolute;top:8px;left:50%;width:38px;height:5px;margin-left:-20px;
    border-radius:var(--rp);background:var(--rule-strong);
  }
  html:root .sheet.modal .sheet-head{touch-action:none;padding-top:calc(var(--s4) + 8px)}
  html:root .sheet.modal.dragging{transition:none!important}
  html:root .sheet.modal.dragging .sheet-grab{cursor:grabbing}
}

/* ---------- 8. CONTRAST ---------- */
/* Glass and hairlines are lovely indoors and gone in a yard at noon.
   A phone set to more contrast gets solid surfaces and real edges. */
@media (prefers-contrast:more){
  html:root[data-theme="dark"]{
    --ink-2:#C4CBD6;--ink-3:#B3BBC8;
    --rule:rgba(255,255,255,.22);--rule-strong:rgba(255,255,255,.4);
    --glass:#0D111C;--scrim:rgba(3,5,11,.88);
  }
  html:root[data-theme="light"]{
    --ink-2:#2B3050;--ink-3:#3A4060;
    --rule:#9AA2B8;--rule-strong:#5E6680;
  }
  html:root :is(.side,.topbar,.mtop,.mbar,.mob-tabs,.sheet,.toast,.gpanel){
    -webkit-backdrop-filter:none!important;backdrop-filter:none!important;
  }
  html:root :is(.mbar,.mob-tabs,.topbar,.mtop){background:var(--surface)!important}
  html:root :is(.card,.today,.btn,.chip,.pill,.sheet){border-color:var(--rule-strong)}
  html:root :is(a,.nav-item.on,.btn-p){text-decoration-thickness:2px}
}
/* ============================================================
   V2: NAVY GLASS (17 October 2026)

   The design language for V2, chosen by Ahmed from three rendered
   directions. White's Navy (#0E0B5C, from the brand guidelines) is
   the ground itself, lit from the top like a sky at dusk. Content
   sits on frosted panels with large radii; size says importance,
   the way an iOS home screen does. White is the primary action.
   Oswald stays for the figure that is the point of a panel.

   HOW THIS LAYER WINS. Every selector starts with `html:not(#v2)`.
   The :not(#id) adds ID-level specificity, so this layer beats the
   ten older stylesheets without !important, and anything that does
   need to beat it (prefers-contrast at the bottom) uses the same
   prefix and comes later. If something looks wrong, fix it HERE.

   Sections
     0  Tokens and ground
     1  Shell: rail, top bar, page header
     2  Type
     3  Panels: card, tile, attention row, job card, money cell
     4  Controls: buttons, icon buttons, chips, segments, tabs, inputs, switch
     5  Data: tables, pills, tags, meters
     6  The day board as widgets
     7  Overlays: sheet, drawer, scrim, toast, not-ready note
     8  Phone: top bar, tab bar, floating buttons
     9  Light theme
    10  Motion
    11  Contrast and transparency
   ============================================================ */

/* ---------- 0. TOKENS AND GROUND ---------- */
html[data-theme="dark"]:not(#v2){
  --navy-brand:#0E0B5C;
  --paper:#07052A; --paper-2:#0A0840;
  /* surfaces are solid steps of navy, not translucent: panels stack
     inside panels all over this app, and glass on glass collapses */
  --surface:#15125A; --surface-2:#1C1966; --surface-3:#242072; --surface-4:#2C287E;
  --rule:rgba(255,255,255,.10); --rule-strong:rgba(255,255,255,.18);
  --ink:#FFFFFF; --ink-2:#C9C8E6; --ink-3:#A3A1CC;
  --chrome:transparent; --chrome-text:#FFFFFF; --chrome-2:rgba(255,255,255,.72);
  --chrome-rule:rgba(255,255,255,.10); --chrome-hover:rgba(255,255,255,.08); --chrome-active:rgba(255,255,255,.16);
  --int:#6CB8FF; --int-hi:#8FCBFF; --int-lo:#4A9FF0;
  --int-soft:rgba(108,184,255,.16); --int-line:rgba(108,184,255,.40); --int-glow:rgba(108,184,255,.30);
  --gold:#FFC24D; --gold-hi:#FFD27A;
  --ok:#5BE3B0; --ok-bg:rgba(91,227,176,.14); --ok-line:rgba(91,227,176,.34);
  --warn:#FFC24D; --warn-bg:rgba(255,194,77,.14); --warn-line:rgba(255,194,77,.36);
  --risk:#FF7A66; --risk-bg:rgba(255,122,102,.15); --risk-line:rgba(255,122,102,.38);
  --info:#6CB8FF; --info-bg:rgba(108,184,255,.14); --info-line:rgba(108,184,255,.36);
  --lift:inset 0 1px 0 rgba(255,255,255,.10); --lift-2:inset 0 1px 0 rgba(255,255,255,.16);
  --sh1:0 1px 2px rgba(3,2,30,.4); --sh2:0 14px 34px rgba(3,2,30,.38); --sh3:0 30px 80px rgba(3,2,30,.55);
  --scrim:rgba(4,3,26,.62);
  --pane:linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,0) 60%);
  --pane-hero:linear-gradient(150deg,#211D78 0%,#16135C 55%,#110E4E 100%);
  --glass:rgba(22,19,88,.62);
  --glass-edge:rgba(255,255,255,.14);
  --glass-hi:inset 0 1px 0 rgba(255,255,255,.16);
  --ground:radial-gradient(1400px 820px at 28% -12%,#2A24A8 0%,#17128A 26%,#0E0B5C 50%,#07052A 100%);
  --r-panel:24px; --r-ctl:14px;
}
html:not(#v2){ --font-display:-apple-system,BlinkMacSystemFont,"SF Pro Display","Inter",var(--font); }
html[data-theme="dark"]:not(#v2) body{background-color:#07052A;background-image:var(--ground);background-attachment:fixed;background-repeat:no-repeat;color:var(--ink)}
html:not(#v2) #app,html:not(#v2) .shell,html:not(#v2) .main,html:not(#v2) .mshell{background:transparent}
/* the older layers painted scaffold line-art and a grid over the
   ground; on navy glass that is noise behind every panel */
html[data-theme="dark"]:not(#v2) .page::before,html[data-theme="dark"]:not(#v2) .main::before,
html[data-theme="dark"]:not(#v2) .shell::before{display:none}

/* ---------- 1. SHELL ---------- */
@media (min-width:901px){
  html:not(#v2) .shell{grid-template-columns:calc(var(--sidebar) + 16px) 1fr}
  html:not(#v2) .side{
    margin:12px 0 12px 12px;height:calc(100dvh - 24px);top:12px;border-radius:28px;
    background:var(--glass);border:1px solid var(--glass-edge);box-shadow:var(--glass-hi),var(--sh2);
    -webkit-backdrop-filter:blur(26px) saturate(150%);backdrop-filter:blur(26px) saturate(150%);
  }
  html:not(#v2) .side::after{display:none}
  html:not(#v2) .side-top{border-bottom:0;padding:20px 20px 12px}
  html:not(#v2) .side-foot{border-top:0;padding:12px 12px 16px}
  html:not(#v2) .userchip{border:0;background:rgba(255,255,255,.06);border-radius:16px;padding:12px 12px}
}
html:not(#v2) .nav-item{border-radius:12px;padding:12px 12px;font-size:var(--t-body);font-weight:var(--w-med);color:var(--chrome-2);border:0}
html:not(#v2) .nav-item:hover{background:rgba(255,255,255,.07);color:#fff}
html:not(#v2) .nav-item.on{background:rgba(255,255,255,.15);color:#fff;font-weight:var(--w-semi);box-shadow:inset 0 1px 0 rgba(255,255,255,.12)}
html:not(#v2) .nav-item.on .ic{color:#fff}
html:not(#v2) .nav-item.on::before{display:none}
html:not(#v2) .nav-label{color:rgba(255,255,255,.5);padding:0 12px 6px}
html:not(#v2) .nav-badge{border-radius:99px;min-width:22px;height:22px;padding:0 6px;font-size:var(--t-tiny)}
html:not(#v2) .nav-badge.amber{background:var(--gold);color:#2A1B00}
html:not(#v2) .side .nav-item .nr-chip{background:rgba(255,255,255,.32)}

html:not(#v2) .topbar{
  background:transparent;border-bottom:0;height:72px;padding:0 28px;
}
html.scrolled:not(#v2) .topbar{
  background:rgba(10,8,64,.55);-webkit-backdrop-filter:blur(22px) saturate(150%);backdrop-filter:blur(22px) saturate(150%);
  box-shadow:0 1px 0 rgba(255,255,255,.08);
}
html:not(#v2) .searchbar{
  height:44px;border-radius:14px;background:rgba(255,255,255,.08);border:1px solid var(--glass-edge);
  color:var(--ink-2);box-shadow:none;
}
html:not(#v2) .searchbar .kbd,html:not(#v2) .kbd{background:rgba(255,255,255,.08);border:0;border-radius:6px;color:var(--ink-3)}
html:not(#v2) .page{padding:8px 32px 96px;max-width:1520px}
html:not(#v2) .page-head{padding-bottom:0;margin-bottom:28px;align-items:flex-end}
html:not(#v2) .page-head::after{display:none}

/* ---------- 2. TYPE ---------- */
html:not(#v2) :is(.page-title,.dsp-lg,h1.dsp){
  font-family:var(--font-display);font-size:var(--t-h1);
  font-weight:var(--w-bold);letter-spacing:var(--ls-8);line-height:var(--lh-display);
}
html:not(#v2) :is(.sec-title,.dsp-md){font-family:var(--font-display);font-size:var(--t-title);font-weight:var(--w-semi);letter-spacing:var(--ls-2)}
html:not(#v2) .lede{color:var(--ink-2);font-size:var(--t-body);line-height:var(--lh-ui);max-width:68ch}
html:not(#v2) .eyebrow{color:var(--ink-2)}
html:not(#v2) .muted{color:var(--ink-3)}
html:not(#v2) .page-head .lede{margin-top:8px}

/* ---------- 3. PANELS ---------- */
html:not(#v2) .card{
  background:var(--surface);background-image:var(--pane);
  border:1px solid var(--glass-edge);border-radius:var(--r-panel);
  box-shadow:var(--glass-hi),var(--sh2);
}
html:not(#v2) .card.pad{padding:24px}
html:not(#v2) .card.tight{padding:20px}
html:not(#v2) .card.flat{box-shadow:var(--glass-hi);background-image:none}
html:not(#v2) :is(.card.click,.card.hov):hover{border-color:rgba(255,255,255,.24);transform:translateY(-2px);
  box-shadow:var(--glass-hi),0 20px 44px rgba(3,2,30,.48)}
/* status is a soft wash from the leading edge, not a hard bar */
html:not(#v2) .card.led{border-left:1px solid var(--glass-edge);
  background-image:linear-gradient(90deg,var(--led,rgba(255,255,255,0)) 0,rgba(255,255,255,0) 38%),var(--pane)}
html:not(#v2) .card.led.risk{--led:rgba(255,122,102,.16)} html:not(#v2) .card.led.warn{--led:rgba(255,194,77,.14)}
html:not(#v2) .card.led.ok{--led:rgba(91,227,176,.12)} html:not(#v2) .card.led.info{--led:rgba(108,184,255,.14)}
html:not(#v2) .tile{background:var(--surface);border:1px solid var(--glass-edge);border-radius:20px;box-shadow:var(--glass-hi);padding:20px 20px}
html:not(#v2) .tile .v{font-size:var(--t-figure);line-height:var(--lh-solid)}
html:not(#v2) .att{
  border-radius:20px;border:1px solid var(--glass-edge);background:var(--surface);box-shadow:var(--glass-hi);
  padding:20px 20px;
}
html:not(#v2) .jcard{
  border-radius:24px;border:1px solid var(--glass-edge);background:var(--surface);background-image:var(--pane);
  box-shadow:var(--glass-hi),var(--sh1);
}
html:not(#v2) .jcard:hover{border-color:rgba(255,255,255,.24)}
html:not(#v2) .jcard.led,html:not(#v2) .jcard[class*="led"]{border-left-width:1px}
html:not(#v2) .moneyc{border-radius:20px;background:var(--surface);border:1px solid var(--glass-edge)}
html:not(#v2) .sec-head{margin-bottom:16px}
html:not(#v2) hr,html:not(#v2) .hr{border-color:var(--rule)}

/* ---------- 4. CONTROLS ---------- */
html:not(#v2) .btn{
  height:42px;padding:0 20px;border-radius:var(--r-ctl);font-weight:var(--w-semi);font-size:var(--t-meta);
  transition:transform 140ms cubic-bezier(.2,.9,.3,1),background-color 160ms var(--ease),border-color 160ms var(--ease),box-shadow 160ms var(--ease),color 160ms var(--ease);
}
html:not(#v2) .btn.sm{height:34px;padding:0 12px;border-radius:12px;font-size:var(--t-small)}
html:not(#v2) .btn.lg{height:52px;padding:0 24px;border-radius:16px;font-size:var(--t-body)}
/* white is the primary action on navy */
html[data-theme="dark"]:not(#v2) .btn-p{background:#FFFFFF;color:var(--navy-brand);border-color:#FFFFFF;box-shadow:0 8px 22px rgba(3,2,30,.35)}
html[data-theme="dark"]:not(#v2) .btn-p:hover{background:#EEF0FF;border-color:#EEF0FF}
html[data-theme="dark"]:not(#v2) .btn-p:active{background:#DCDFFB}
html[data-theme="dark"]:not(#v2) .btn-p .ic{color:var(--navy-brand)}
html:not(#v2) .btn-s{background:rgba(255,255,255,.09);border:1px solid var(--glass-edge);color:var(--ink);box-shadow:var(--glass-hi)}
html:not(#v2) .btn-s:hover{background:rgba(255,255,255,.15);border-color:rgba(255,255,255,.24)}
html:not(#v2) .btn-g{color:var(--ink-2)} html:not(#v2) .btn-g:hover{background:rgba(255,255,255,.08);color:#fff}
html:not(#v2) .btn-a{background:var(--int);border-color:var(--int);color:#06204A}
html:not(#v2) .icobtn{border-radius:12px;width:40px;height:40px;background:rgba(255,255,255,.07);border:1px solid var(--glass-edge)}
html:not(#v2) .icobtn:hover{background:rgba(255,255,255,.14)}
html:not(#v2) .chip{height:36px;padding:0 16px;display:inline-flex;align-items:center;border-radius:99px;
  background:rgba(255,255,255,.07);border:1px solid var(--glass-edge);color:var(--ink-2);font-weight:var(--w-semi)}
html:not(#v2) .chip:hover{background:rgba(255,255,255,.13);color:#fff}
html:not(#v2) .chip.on{background:#FFFFFF;border-color:#FFFFFF;color:var(--navy-brand,#0E0B5C)}
html:not(#v2) .seg{border:1px solid var(--glass-edge);border-radius:14px;background:rgba(255,255,255,.07);padding:4px;gap:2px}
html:not(#v2) .seg button{border:0;border-radius:10px;padding:8px 16px;color:var(--ink-2)}
html:not(#v2) .seg button.on{background:#FFFFFF;color:var(--navy-brand,#0E0B5C);box-shadow:0 4px 12px rgba(3,2,30,.3)}
html:not(#v2) .tabs{border-bottom:1px solid var(--rule);gap:4px}
html:not(#v2) .tabs button{border-radius:12px 12px 0 0}
html:not(#v2) :is(.inp,input[type="text"],input[type="search"],input[type="email"],input[type="tel"],input[type="number"],input[type="date"],input[type="time"],select,textarea){
  border-radius:14px;background:rgba(255,255,255,.07);border:1px solid var(--glass-edge);color:var(--ink);
}
html:not(#v2) :is(.inp,input,select,textarea):focus{border-color:var(--int);box-shadow:0 0 0 4px var(--int-soft);outline:none}
html:not(#v2) .switch{border-radius:99px}
html:not(#v2) :focus-visible{outline:2px solid var(--int-hi);outline-offset:2px}

/* ---------- 5. DATA ---------- */
html:not(#v2) .tblwrap{border-radius:var(--r-panel);border:1px solid var(--glass-edge);background:var(--surface);box-shadow:var(--glass-hi);overflow:hidden}
html:not(#v2) .tbl th{background:transparent;color:var(--ink-3);border-bottom:1px solid var(--rule);height:44px}
html:not(#v2) .tbl td{border-bottom:1px solid var(--rule);height:56px}
html:not(#v2) .tbl tr:last-child td{border-bottom:0}
html:not(#v2) .tbl tbody tr:hover td{background:rgba(255,255,255,.04)}
html:not(#v2) .pill,html:not(#v2) .tag{border-radius:99px;border:0;font-weight:var(--w-semi)}
html:not(#v2) .pill.ok,html:not(#v2) .tag-ok{background:var(--ok-bg);color:var(--ok)}
html:not(#v2) .pill.warn{background:var(--warn-bg);color:var(--warn)}
html:not(#v2) .pill.risk{background:var(--risk-bg);color:var(--risk)}
html:not(#v2) .pill.info{background:var(--info-bg);color:var(--info)}
html:not(#v2) .pill.grey,html:not(#v2) .tag-off{background:rgba(255,255,255,.09);color:var(--ink-2)}
html:not(#v2) .meter{border-radius:99px;background:rgba(255,255,255,.10)}

/* ---------- 6. THE DAY BOARD AS WIDGETS ---------- */
/* One panel cut into seven equal cells becomes a set of widgets
   whose size says what matters: the money at risk is the big one. */
html:not(#v2) .today{
  background:transparent;border:0;border-radius:0;gap:16px;overflow:visible;
  grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-flow:row dense;
}
html:not(#v2) .today::after{display:none}
html:not(#v2) .today > .today-c{
  background:var(--surface);background-image:var(--pane);border:1px solid var(--glass-edge);border-radius:var(--r-panel);
  box-shadow:var(--glass-hi),var(--sh2);padding:20px 24px;min-height:132px;display:flex;flex-direction:column;
}
html:not(#v2) .today > .today-c:hover{background-color:var(--surface-2)}
html:not(#v2) .today-k{order:0}
html:not(#v2) .today-v{order:1;margin-top:auto;font-size:var(--t-hero)}
html:not(#v2) .today-s{order:2;color:var(--ink-2)}
html:not(#v2) .today-art{opacity:.35}
/* the first flagged figure (work done, not billed) is the hero */
html:not(#v2) .today > .today-c.flag:not(.today-c.flag ~ .today-c.flag){
  grid-column:span 2;grid-row:span 2;
  background-image:linear-gradient(150deg,rgba(255,194,77,.22),rgba(255,255,255,.04) 62%);
}
html:not(#v2) .today > .today-c.flag:not(.today-c.flag ~ .today-c.flag) .today-v{font-size:var(--t-giant);color:var(--gold)}
html:not(#v2) .today > .today-c.flag ~ .today-c.flag{background-image:linear-gradient(150deg,rgba(255,122,102,.24),rgba(255,255,255,.04) 70%)}
html:not(#v2) .today > .today-c.flag ~ .today-c.flag .today-v{color:var(--risk)}
html:not(#v2) .today > .today-c.flag{border-left:1px solid var(--glass-edge)}
html:not(#v2) .today > .today-c:nth-last-child(-n+2):not(.flag){grid-column:span 2}
html:not(#v2) .today .today-k{color:var(--ink-2)}
@media (min-width:601px) and (max-width:1080px){
  html:not(#v2) .today{grid-template-columns:repeat(2,minmax(0,1fr))}
  html:not(#v2) .today > .today-c:last-child{grid-column:auto}
  html:not(#v2) .today > .today-c:nth-last-child(-n+2):not(.flag){grid-column:span 1}
}
@media (max-width:600px){
  html:not(#v2) .today{display:grid;grid-template-columns:1fr 1fr;overflow:visible;-webkit-mask-image:none;mask-image:none}
  html:not(#v2) .today > .today-c{min-width:0;min-height:118px;padding:16px}
  html:not(#v2) .today > .today-c.flag:not(.today-c.flag ~ .today-c.flag){grid-column:1/-1;grid-row:auto}
  html:not(#v2) .today > .today-c:nth-last-child(-n+2):not(.flag){grid-column:span 1}
}

/* ---------- 7. OVERLAYS ---------- */
html:not(#v2) .scrim{background:var(--scrim);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}
html[data-theme="dark"]:not(#v2) .sheet{
  background:rgba(24,21,94,.86);border:1px solid var(--glass-edge);
  -webkit-backdrop-filter:blur(30px) saturate(160%);backdrop-filter:blur(30px) saturate(160%);
  box-shadow:var(--glass-hi),var(--sh3);
}
html:not(#v2) .sheet.modal{border-radius:28px}
html:not(#v2) .sheet-head{border-bottom:1px solid var(--rule);background:transparent}
html:not(#v2) .sheet-foot{border-top:1px solid var(--rule);background:transparent}
html:not(#v2) .sheet.drawer{border-radius:28px 0 0 28px}
html[data-theme="dark"]:not(#v2) .toast{
  background:rgba(28,25,102,.88);border:1px solid var(--glass-edge);border-left:1px solid var(--glass-edge);border-radius:20px;
  -webkit-backdrop-filter:blur(24px);backdrop-filter:blur(24px);box-shadow:var(--glass-hi),var(--sh3);padding:12px 20px;
}
html:not(#v2) .toast .ic{color:var(--int)} html:not(#v2) .toast.ok .ic{color:var(--ok)}
html:not(#v2) .toast.risk .ic{color:var(--risk)} html:not(#v2) .toast.warn .ic{color:var(--gold)}
/* the not-ready note: a small glass chip, never a cream slab over data */
html:not(#v2) .nr-bubble{
  background:rgba(22,19,88,.82);color:var(--ink-2);border:1px solid var(--glass-edge);border-radius:99px;
  -webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);box-shadow:var(--sh2);
  font-size:var(--t-small);padding:8px 16px;max-width:min(440px,calc(100vw - 32px));
}
html:not(#v2) .nr-bubble b{color:#fff}
html:not(#v2) .nr-dot{background:var(--gold);box-shadow:0 0 0 4px rgba(255,194,77,.2)}

/* ---------- 8. PHONE ---------- */
@media (max-width:720px){
  html:not(#v2) .page{padding:4px 20px 120px}
  html:not(#v2) .mtop{background:transparent;border-bottom:0}
  html.scrolled:not(#v2) .mtop{background:rgba(10,8,64,.78);-webkit-backdrop-filter:blur(22px) saturate(150%);backdrop-filter:blur(22px) saturate(150%);box-shadow:0 1px 0 rgba(255,255,255,.08)}
  html:not(#v2) .mbar,html:not(#v2) .mob-tabs{
    left:14px;right:14px;bottom:calc(12px + var(--sab));height:68px;border-radius:var(--r-pill);
    background:rgba(22,19,88,.72);border:1px solid var(--glass-edge);
    -webkit-backdrop-filter:blur(26px) saturate(160%);backdrop-filter:blur(26px) saturate(160%);
    box-shadow:var(--glass-hi),0 18px 44px rgba(3,2,30,.5);
  }
  html:not(#v2) .mbtab{color:var(--ink-3)}
  html:not(#v2) .mbtab.on{color:#fff}
  html:not(#v2) .mbtab.on::before,html:not(#v2) .mbtab.on::after{background:transparent;box-shadow:none}
  html:not(#v2) #toasts{bottom:calc(96px + var(--sab))}
  html:not(#v2) .sheet.modal{border-radius:28px 28px 0 0}
  html:not(#v2) .nr-bubble{bottom:calc(92px + var(--sab))}
}
html:not(#v2) .fab{background:#FFFFFF;color:#0E0B5C;border-radius:20px;box-shadow:0 14px 34px rgba(3,2,30,.45)}
html:not(#v2) .fab .ic{color:#0E0B5C}
html:not(#v2) .tour-gbtn{background:rgba(22,19,88,.78);border:1px solid var(--glass-edge);color:#fff;
  -webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);border-radius:99px}

/* ---------- 9. LIGHT THEME ---------- */
/* The same system in daylight: a pale lavender ground, white panels,
   navy ink and navy as the primary action. */
html[data-theme="light"]:not(#v2){
  --paper:#EEF0FA; --surface:#FFFFFF; --surface-2:#F5F6FC; --surface-3:#E8EAF6;
  --rule:#E0E2F0; --rule-strong:#C9CCE2; --ink:#0B0A33; --ink-2:#44476E; --ink-3:#5D6086;
  --int:#2A3FD6; --int-soft:rgba(42,63,214,.10); --int-line:rgba(42,63,214,.30);
  --glass:rgba(255,255,255,.78); --glass-edge:rgba(14,11,92,.10); --glass-hi:inset 0 1px 0 rgba(255,255,255,.9);
  --sh2:0 12px 30px rgba(14,11,92,.08); --sh3:0 30px 70px rgba(14,11,92,.18);
  --pane:none; --r-panel:24px; --r-ctl:14px; --scrim:rgba(14,11,92,.28);
  --ground:radial-gradient(1400px 820px at 28% -12%,#DCDDFB 0%,#EEF0FA 46%,#F4F5FB 100%);
}
html[data-theme="light"]:not(#v2) body{background-color:#EEF0FA;background-image:var(--ground);background-attachment:fixed;background-repeat:no-repeat}
/* the rail in daylight is frosted white glass over a softly coloured
   ground, with navy type: the same material as the dark theme, lit */
html[data-theme="light"]:not(#v2){
  --ground:radial-gradient(620px 520px at 2% 22%,rgba(92,86,255,.55),transparent 64%),
    radial-gradient(560px 520px at 6% 88%,rgba(64,168,255,.50),transparent 64%),
    radial-gradient(520px 420px at 24% 54%,rgba(190,120,255,.28),transparent 66%),
    radial-gradient(1400px 820px at 40% -12%,#DCDDFB 0%,#EEF0FA 46%,#F4F5FB 100%);
}
@media (min-width:901px){
  html[data-theme="light"]:not(#v2) .side{
    background:linear-gradient(180deg,rgba(255,255,255,.44),rgba(255,255,255,.26));
    border:1px solid rgba(255,255,255,.85);
    -webkit-backdrop-filter:blur(28px) saturate(170%);backdrop-filter:blur(28px) saturate(170%);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.95),inset 0 0 0 1px rgba(14,11,92,.04),0 18px 44px rgba(14,11,92,.12);
    color:var(--ink);
  }
}
html[data-theme="light"]:not(#v2) .side{--chrome-text:#0B0A33;--chrome-2:#44476E;--chrome-rule:rgba(14,11,92,.08)}
html[data-theme="light"]:not(#v2) .side .wlogo,html[data-theme="light"]:not(#v2) .side .logo{color:#0E0B5C}
html[data-theme="light"]:not(#v2) .side .nav-item{color:#44476E}
html[data-theme="light"]:not(#v2) .side .nav-item .ic{color:#0E0B5C;opacity:.75}
html[data-theme="light"]:not(#v2) .side .nav-item:hover,html[data-theme="light"]:not(#v2) .side .nav-item.on,
html[data-theme="light"]:not(#v2) .side-nav.has-glide .nav-item:hover{color:#0E0B5C}
html[data-theme="light"]:not(#v2) .side .nav-item.on .ic{opacity:1}
html[data-theme="light"]:not(#v2) .side .nav-label{color:#6A6D92}
html[data-theme="light"]:not(#v2) .side .nr-chip{background:rgba(14,11,92,.28)!important}
html[data-theme="light"]:not(#v2) .side :is(.nav-glide,.nav-glide-was){background:rgba(255,255,255,.85);border-color:rgba(14,11,92,.08);
  box-shadow:inset 0 1px 0 #fff,0 6px 18px rgba(14,11,92,.12)}
html[data-theme="light"]:not(#v2) .side .userchip{background:rgba(255,255,255,.6);color:var(--ink)}
html[data-theme="light"]:not(#v2) .side .userchip *:not(.avatar){color:#0B0A33!important}
html[data-theme="light"]:not(#v2) .side .userchip i{color:#44476E!important}
html[data-theme="light"]:not(#v2) .side .side-foot .icobtn,html[data-theme="light"]:not(#v2) .side .userchip .ic{color:#44476E}
html[data-theme="light"]:not(#v2) .shell.collapsed .side .nav-badge{box-shadow:0 0 0 2px #fff}
html[data-theme="light"]:not(#v2) .shell.collapsed .nav-label{background:linear-gradient(rgba(14,11,92,.12),rgba(14,11,92,.12)) 50% 0/22px 1px no-repeat}
html[data-theme="light"]:not(#v2) .btn-p{background:#0E0B5C;border-color:#0E0B5C;color:#fff}
html[data-theme="light"]:not(#v2) :is(.chip.on,.seg button.on){background:#0E0B5C;border-color:#0E0B5C;color:#fff}
html[data-theme="light"]:not(#v2) :is(.btn-s,.chip,.seg,.icobtn,.searchbar,.inp,input,select,textarea){background:#FFFFFF;border-color:var(--rule-strong)}
html[data-theme="light"]:not(#v2) .sheet{background:rgba(255,255,255,.94)}
html[data-theme="light"]:not(#v2) .toast{background:#0E0B5C;color:#fff}
html[data-theme="light"]:not(#v2) .nr-bubble{background:#fff;color:var(--ink-2)}
html[data-theme="light"]:not(#v2) .nr-bubble b{color:var(--ink)}
html[data-theme="light"]:not(#v2) .today > .today-c.flag:not(.today-c.flag ~ .today-c.flag) .today-v{color:#9A6400}
html[data-theme="light"]:not(#v2) .today > .today-c.flag:not(.today-c.flag ~ .today-c.flag){background-image:linear-gradient(150deg,#FFF1CC,#FFFFFF 62%)}
/* The desk's top bar once the page scrolls (28 October, Ahmed in Safari).
   Its scrolled glass was the dark theme's navy at 55 percent with nothing
   for daylight, so on a light page it turned into a slate band and every
   card passing under it looked cut off at the top. In daylight it is the
   rail's material: frosted white, the page soft behind it, a navy
   hairline. (The phone's bar is navy in daylight on purpose, at rest and
   scrolled, and is not touched.) */
html.scrolled[data-theme="light"]:not(#v2) .topbar{
  background:rgba(255,255,255,.72);
  -webkit-backdrop-filter:blur(22px) saturate(170%);backdrop-filter:blur(22px) saturate(170%);
  box-shadow:0 1px 0 rgba(14,11,92,.08);
}
/* with transparency turned down, the scrolled bar is solid in either theme
   rather than a tint with the page legible through it */
@media (prefers-reduced-transparency:reduce){
  html.scrolled[data-theme="dark"]:not(#v2) .topbar{background:#15125A}
  html.scrolled[data-theme="light"]:not(#v2) .topbar{background:#FFFFFF}
}
@media (max-width:720px){
  html[data-theme="light"]:not(#v2) :is(.mbar,.mob-tabs){background:rgba(255,255,255,.86)}
  html[data-theme="light"]:not(#v2) .mbtab.on{color:#0E0B5C}
}

/* ---------- 10. MOTION ---------- */
/* Press lands on touch-down (the .pressing class is set on
   pointerdown in 51-motion.js) and releases on a short spring-like
   curve. Nothing animates layout. */
html:not(#v2) :is(.btn,.chip,.seg button,.icobtn,.card.click,.jcard,.today-c,.mbtab,.fab,.nav-item).pressing,
html:not(#v2) :is(.btn,.chip,.icobtn,.fab):active{transform:scale(.97);transition-duration:80ms}
html:not(#v2) :is(.card.click,.jcard,.today-c).pressing{transform:scale(.985)}
@media (prefers-reduced-motion:reduce){
  html:not(#v2) :is(.btn,.chip,.seg button,.icobtn,.card.click,.jcard,.today-c,.mbtab,.fab,.nav-item){transform:none!important;transition:background-color 120ms,color 120ms,opacity 120ms!important}
}

/* ---------- 12. REFINEMENTS FROM THE FIRST RENDER ---------- */
/* the hero widget goes first, so the money is top left */
html:not(#v2) .today > .today-c.flag:not(.today-c.flag ~ .today-c.flag){order:-1}
html:not(#v2) .today > .today-c{min-height:136px}
/* widget labels in sentence case, as on an iOS widget */
html:not(#v2) :is(.today-k,.tile .k){text-transform:none;letter-spacing:var(--ls-0);font-size:var(--t-meta);font-weight:var(--w-semi);color:var(--ink-2)}
/* the gold hazard stripe under the hero band is the old direction */
html:not(#v2) .hero::after{display:none}
/* job flow on home becomes a panel instead of a hard white rule */
html:not(#v2) .jf-home{border-top:0;background:var(--surface);background-image:var(--pane);border:1px solid var(--glass-edge);
  border-radius:var(--r-panel);box-shadow:var(--glass-hi),var(--sh2);padding:20px 24px}
html[data-theme="light"]:not(#v2) .jf-home{border-color:var(--rule)}
/* chip counts sit apart from the label */
html:not(#v2) .chip{gap:6px}
html:not(#v2) .chip .num{opacity:.6}
/* a search field inside a search wrapper draws one edge, not two */
html:not(#v2) .jsearch{border-radius:14px;background:rgba(255,255,255,.07);border:1px solid var(--glass-edge);height:46px}
html:not(#v2) .jsearch .inp{background:transparent;border:0;box-shadow:none}
html[data-theme="light"]:not(#v2) .jsearch{background:#fff;border-color:var(--rule-strong)}

/* the not-ready note sits at the top right, under the bar, where it
   never covers data; on a phone it is one line under the top bar */
html:not(#v2) .nr-bubble{top:84px;right:28px;left:auto;bottom:auto;transform:none;animation:fadein .3s var(--ease-out);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@media (max-width:720px){
  html:not(#v2) .nr-bubble{top:auto;bottom:calc(90px + var(--sab));right:16px;left:16px;max-width:none;justify-content:center;font-size:var(--t-small);padding:6px 12px}
}

/* light theme on a phone: the top bar keeps the navy brand slab, so
   the white logo and icons stay visible */
@media (max-width:720px){
  html[data-theme="light"]:not(#v2) .mtop{background:#0E0B5C!important;-webkit-backdrop-filter:none;backdrop-filter:none}
  html[data-theme="light"]:not(#v2) .mtop :is(.icobtn,.mtop-btn,button){background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.18);color:#fff}
  html[data-theme="light"]:not(#v2) .mtop .mtop-title{color:#fff}
}

/* ---------- 13. THE REACTIVE SIDEBAR (51b-sidebar.js) ---------- */
/* Full width or a 76px rail, springing between the two. On a tablet
   the rail is the default; the old tablet rules that forced the rail
   on every shell are undone for a shell the user has opened. */
@media (min-width:901px){
  html:not(#v2) .shell{--sidebar:250px;transition:grid-template-columns .52s cubic-bezier(.32,.72,0,1)}
  html:not(#v2) .shell.collapsed{--sidebar:76px}
  html:not(#v2) .shell:not(.collapsed){grid-template-columns:calc(var(--sidebar) + 16px) 1fr}
  html:not(#v2) .shell.collapsed{grid-template-columns:calc(var(--sidebar) + 16px) 1fr}
  html:not(#v2) .side{overflow:visible}
  html:not(#v2) .side-nav{position:relative;overflow-x:hidden;padding:8px 8px 16px}
  html:not(#v2) .nav-item{overflow:hidden;white-space:nowrap;justify-content:flex-start;
    transition:padding .52s cubic-bezier(.32,.72,0,1),color .16s var(--ease)}
  html:not(#v2) .nav-lbl{transition:opacity .22s var(--ease)}
  html:not(#v2) .nav-label{transition:opacity .22s var(--ease);white-space:nowrap;overflow:hidden}
  /* undo the forced tablet rail for an opened shell */
  html:not(#v2) .shell:not(.collapsed) :is(.nav-lbl,.nav-label,.wordmark,.side-foot .who){display:revert}
  html:not(#v2) .shell:not(.collapsed) .nav-item{justify-content:flex-start;padding:12px 12px}
  /* margin-block: a count no longer makes its row 1.7px taller than the
     others, so the rows sit where they sit on the rail (8 November) */
  html:not(#v2) .shell:not(.collapsed) .side .nav-badge{position:static;box-shadow:none;margin-left:auto;margin-block:-1px}
  /* the rail */
  html:not(#v2) .shell.collapsed .nav-item{padding:12px 0 12px 20px;justify-content:flex-start}
  html:not(#v2) .shell.collapsed :is(.nav-lbl,.nav-label){display:revert;opacity:0;pointer-events:none}
  /* .side scopes these to the rail: the planner tabs and the people screen
     reuse .nav-badge for their counts, and the rail was shrinking those to dots */
  html:not(#v2) .shell.collapsed .side .nav-badge{position:absolute;left:33px;top:6px;min-width:9px;width:9px;height:9px;padding:0;
    font-size:0;border-radius:50%;box-shadow:0 0 0 2px #15125A}
  html:not(#v2) .shell.collapsed .side .nr-chip{display:none}
  html:not(#v2) .shell.collapsed .side-top{justify-content:center;padding:20px 0 12px}
  html:not(#v2) .shell.collapsed .userchip{justify-content:center;padding:12px 0}
  html:not(#v2) .shell.collapsed .userchip > :not(.avatar){display:none}
  html:not(#v2) .shell.collapsed .side-foot{padding:12px 8px 16px}
  html:not(#v2) .shell.collapsed .side .nav-badge{right:auto}
  html:not(#v2) .shell.collapsed :is(.logo,.side-top > a,.side-top > div){margin-left:auto;margin-right:auto}
  html:not(#v2) .shell.collapsed .side-top{display:flex;justify-content:center}
  html:not(#v2) .shell.collapsed .side-top > .grow{display:none}
  /* group headings become a short hairline on the rail, not a gap. Since
     8 November it is drawn at the top of the space the heading takes when
     open, and the groups keep their spacing: the items keep their places
     as the rail opens and shuts, where the whole list used to move up or
     down by as much as 91px in the middle of the motion */
  html:not(#v2) .shell.collapsed .nav-label{opacity:1;overflow:hidden;color:transparent;
    background:linear-gradient(rgba(255,255,255,.14),rgba(255,255,255,.14)) 50% 0/22px 1px no-repeat}
  /* the rail's choreography (51b-sidebar.js, 8 November).
     SHUTTING: .side-narrow moves the rail on the press (its column, the
     icons, the logo, the footer, the button), while the names, counts,
     headings and the name at the bottom fade (.side-hush, 0.12s); the
     rail's own form (.collapsed: the dots, the hairlines) is taken once
     the rail has arrived, unseen, and fades in. OPENING: the open form is
     taken on the press, hidden at once (.side-opening), and fades in when
     there is room for it. .side-narrow sets the column itself, not
     --sidebar: a custom property is inherited, so changing it restyles
     every element on the screen (about 1,500 on Home), and that cost
     landed in the middle of the motion. The hidden parts are named by
     class for the same reason. */
  html:not(#v2) .shell.side-narrow{grid-template-columns:calc(76px + 16px) 1fr}
  html:not(#v2) .shell.side-narrow .nav-item{padding:12px 0 12px 20px}
  html:not(#v2) .shell.side-narrow .nav-item.nr-dim{opacity:.55}
  html:not(#v2) .shell.side-narrow .logo .wlogo{width:34px!important}
  html:not(#v2) .shell.side-narrow .side-foot{padding:12px 8px 16px}
  /* the person's picture slides to where the rail centres it */
  html:not(#v2) .shell.side-narrow .userchip{padding:12px 0 12px 16px}
  html:not(#v2) .userchip{transition:padding .52s cubic-bezier(.32,.72,0,1)}
  html:not(#v2) .shell.side-narrow .side-toggle{transform:rotate(180deg)}
  html:not(#v2) .shell.side-narrow .side-toggle:active{transform:rotate(180deg) scale(.9)}
  /* the Guides button narrows with its icon where the rail will centre it
     (12px in either way), its word fading; centring it now, with the
     word still taking room, pushed the icon out of the button */
  html:not(#v2) #tourgbtn.tour-narrow .tour-gbtn{width:40px;left:30px}
  /* and the word gives way to the icon, which a flex row would otherwise
     shrink to nothing first */
  html:not(#v2) #tourgbtn.tour-narrow .tour-gbtn > svg{flex:none}
  html:not(#v2) #tourgbtn.tour-narrow .tour-gbtn > span{min-width:0;overflow:hidden}
  /* the footer's height follows the rail's curve instead of jumping, so
     the foot of the list does not jump either */
  html:not(#v2) .side-foot{transition:padding .52s cubic-bezier(.32,.72,0,1)}
  html:not(#v2) .side :is(.nav-badge,.nr-chip,.who){transition:opacity .2s var(--ease)}
  html:not(#v2) #tourgbtn .tour-gbtn span{transition:opacity .2s var(--ease)}
  html:not(#v2) .shell.side-hush .side :is(.nav-lbl,.nav-label,.nav-badge,.nr-chip,.who),
  html:not(#v2) #tourgbtn.tour-hush .tour-gbtn span{opacity:0!important;transition:opacity .12s var(--ease)!important}
  html:not(#v2) .shell.side-hush.side-opening .side :is(.nav-lbl,.nav-label,.nav-badge,.nr-chip,.who),
  html:not(#v2) #tourgbtn.tour-hush.tour-now .tour-gbtn span{transition:none!important}
}
/* the round button on the rail's edge */
html:not(#v2) .side-toggle{display:none}
@media (min-width:901px){
  html:not(#v2) .side-toggle{
    display:grid;place-items:center;position:absolute;top:64px;right:-14px;z-index:5;
    width:28px;height:28px;border-radius:50%;color:#fff;cursor:pointer;
    background:rgba(30,26,110,.9);border:1px solid rgba(255,255,255,.22);
    -webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);
    box-shadow:var(--glass-hi),0 6px 16px rgba(3,2,30,.45);
    transition:transform .52s cubic-bezier(.32,.72,0,1),background-color .16s var(--ease);
  }
  html:not(#v2) .side-toggle:hover{background:rgba(48,42,150,.95)}
  html:not(#v2) .side-toggle:active{transform:scale(.9)}
  html:not(#v2) .shell.collapsed .side-toggle{transform:rotate(180deg)}
  html:not(#v2) .shell.collapsed .side-toggle:active{transform:rotate(180deg) scale(.9)}
  html[data-theme="light"]:not(#v2) .side-toggle{background:#fff;color:#0E0B5C;border-color:rgba(14,11,92,.14)}
}
/* the open page's glass pill (51b-sidebar.js, 8 November: it stays under
   the open page, and when the page changes it fades across, the old one
   fading where it was as .nav-glide-was) */
html:not(#v2) :is(.nav-glide,.nav-glide-was){
  position:absolute;left:8px;right:8px;top:0;height:40px;z-index:0;pointer-events:none;border-radius:12px;
  background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.16);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.18),0 6px 18px rgba(3,2,30,.25);
  transition:opacity .2s var(--ease);will-change:transform;
  /* drawn out by its speed as a scale about its top edge (51b-sidebar.js,
     8 November): its place and height are the browser's to play now */
  transform-origin:50% 0;
}
/* the pointer's pill: fainter glass under whatever is pointed at, which
   glides from item to item and fades out where it is when the pointer
   leaves the list, so nothing ever crosses the list on its own */
html:not(#v2) .nav-hover{
  position:absolute;left:8px;right:8px;top:0;height:40px;z-index:0;pointer-events:none;border-radius:12px;
  background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.09);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08);
  opacity:0;transition:opacity .18s var(--ease);will-change:transform;transform-origin:50% 0;
}
html[data-theme="light"]:not(#v2) .side .nav-hover{background:rgba(14,11,92,.05);border-color:rgba(14,11,92,.05);box-shadow:none}
html:not(#v2) .side-nav.has-glide .nav-item{position:relative;z-index:1}
html:not(#v2) .side-nav.has-glide .nav-item,html:not(#v2) .side-nav.has-glide .nav-item:hover,
html:not(#v2) .side-nav.has-glide .nav-item.on{background:transparent;box-shadow:none}
html:not(#v2) .side-nav.has-glide .nav-item:hover{color:#fff}
@media (prefers-reduced-transparency:reduce){html:not(#v2) :is(.nav-glide,.nav-glide-was){background:rgba(255,255,255,.2)}html:not(#v2) .nav-hover{background:rgba(255,255,255,.12)}}
@media (prefers-reduced-motion:reduce){
  html:not(#v2) .shell,html:not(#v2) .nav-item,html:not(#v2) .side-toggle,html:not(#v2) .side-foot,html:not(#v2) .userchip{transition:none}
}

/* the rail's glass label (51b-sidebar.js) */
.rail-tip{position:fixed;z-index:90;left:0;top:0;transform:translate(-4px,-50%);opacity:0;pointer-events:none;
  padding:6px 12px;border-radius:10px;font:var(--w-semi) var(--t-meta)/var(--lh-heading) var(--font);white-space:nowrap;
  color:#fff;background:rgba(22,19,88,.86);border:1px solid rgba(255,255,255,.16);
  -webkit-backdrop-filter:blur(18px) saturate(160%);backdrop-filter:blur(18px) saturate(160%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.14),0 10px 26px rgba(3,2,30,.35);
  transition:opacity .14s var(--ease),transform .2s cubic-bezier(.32,.72,0,1)}
.rail-tip.on{opacity:1;transform:translate(0,-50%)}
html[data-theme="light"] .rail-tip{color:#0E0B5C;background:rgba(255,255,255,.88);border-color:rgba(14,11,92,.10);
  box-shadow:inset 0 1px 0 #fff,0 10px 26px rgba(14,11,92,.14)}
@media (prefers-reduced-motion:reduce){.rail-tip{transition:none}}

/* ---------- 14. QA PASS (17 October, evening) ---------- */
/* the notification dot on the phone's bell hung 2px off the screen edge
   and gave every phone page a sideways scroll */
html:not(#v2) .mtop .dotbadge{right:0}
html:not(#v2) .mtop{overflow:hidden}

/* The Guides button floated over the content column and sat on top of
   whatever was at the bottom of the page. On desktop it now docks to the
   bottom-left corner of the content area with a clear gutter, level with
   the + button, and on the phone it sits inside the tab bar's clearance. */
@media (min-width:901px){
  /* on desktop the Guides button lives in the sidebar's footer, above the
     profile chip, so it never sits over the page */
  html:not(#v2) #tourgbtn .tour-gbtn{left:24px;bottom:92px;height:40px;padding:0 16px 0 12px;font-weight:var(--w-semi);width:calc(var(--sidebar) - 16px);
    justify-content:flex-start;border-radius:12px;background:rgba(255,255,255,.07);box-shadow:none;
    transition:left .52s cubic-bezier(.32,.72,0,1),width .52s cubic-bezier(.32,.72,0,1)}
  html:not(#v2) #tourgbtn .tour-gbtn:hover{background:rgba(255,255,255,.13)}
  html:not(#v2) .shell.collapsed ~ #tourgbtn .tour-gbtn,html:not(#v2) body:has(.shell.collapsed) #tourgbtn .tour-gbtn{width:40px;padding:0;justify-content:center;left:30px}
  html:not(#v2) body:has(.shell.collapsed) #tourgbtn .tour-gbtn span{display:none}
  html:not(#v2) .side-foot{padding-top:56px}
  html[data-theme="light"]:not(#v2) #tourgbtn .tour-gbtn{background:rgba(255,255,255,.6);color:#0E0B5C;border-color:rgba(14,11,92,.1)}
}
@media (max-width:720px){
  html:not(#v2) .tour-gbtn{bottom:calc(96px + var(--sab,0px));left:16px;width:40px;height:40px;padding:0;justify-content:center;border-radius:50%}
}
html:not(#v2) .fab{bottom:24px;right:24px}
/* the page keeps enough room at the bottom that nothing hides under the
   floating buttons */
html:not(#v2) .page{padding-bottom:112px}

/* when the page is locked under a sheet, keep the space the scrollbar
   took, so nothing shifts sideways. lockScroll() measures that space as
   the width #app actually gains when the root's overflow is hidden.
   scrollbar-gutter:stable is gone: Safari reserved an 11px gutter for an
   overlay scrollbar that takes no space, then dropped it under a sheet,
   so every pop-up moved the right-hand controls by 11px. */
html.locked:not(#v2) body{padding-right:var(--sbw,0px)}
html.locked:not(#v2) :is(#toasts,.fab,.nr-bubble){margin-right:var(--sbw,0px)}

/* the logo mask narrows to the W on the same curve as the rail, instead
   of snapping to 34px and sliding back */
@media (min-width:901px){
  /* the mark is held at its own height whatever the width (8 November):
     at "contain" the whole wordmark shrank to fit the narrowing box, so
     opening the rail drew a tiny WHITE'S that grew, instead of the name
     coming out from behind the W */
  html:not(#v2) .side-top .logo .wlogo{transition:width .52s cubic-bezier(.32,.72,0,1);-webkit-mask-position:left center;mask-position:left center;-webkit-mask-size:auto 100%;mask-size:auto 100%}
  html:not(#v2) .shell.collapsed .side-top{justify-content:flex-start;padding-left:20px}
  html:not(#v2) .shell.collapsed :is(.logo,.side-top > a,.side-top > div){margin-left:0;margin-right:0}
}

/* ---------- 11. CONTRAST AND TRANSPARENCY ---------- */
@media (prefers-reduced-transparency:reduce){
  html:not(#v2) :is(.side,.sheet,.toast,.mbar,.mob-tabs,.topbar,.mtop,.nr-bubble,.tour-gbtn,.scrim){
    -webkit-backdrop-filter:none;backdrop-filter:none}
  html[data-theme="dark"]:not(#v2) :is(.side,.sheet,.toast,.mbar,.mob-tabs,.nr-bubble){background:#15125A}
}
@media (prefers-contrast:more){
  html[data-theme="dark"]:not(#v2){--ink-2:#E4E3F5;--ink-3:#CFCDEA;--rule:rgba(255,255,255,.28);--rule-strong:rgba(255,255,255,.45);--glass-edge:rgba(255,255,255,.4)}
  html[data-theme="dark"]:not(#v2) body{background:#07052A}
  html:not(#v2) :is(.side,.sheet,.toast,.mbar,.mob-tabs,.topbar,.mtop){-webkit-backdrop-filter:none;backdrop-filter:none}
  html[data-theme="dark"]:not(#v2) :is(.side,.sheet,.toast,.mbar,.mob-tabs){background:#15125A}
}

/* ---------- 15. TYPE SYSTEM PASS (18 October) ---------- */
/* The nav list is longer than the sidebar. Its bottom edge used to be a
   hard cut through whatever item happened to be there; it now fades out
   over the last 28px, the way a list does in iOS, so a half item reads
   as "more below" rather than as a mistake. */
html:not(#v2) .side-nav{
  -webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 28px),transparent 100%);
  mask-image:linear-gradient(to bottom,#000 calc(100% - 28px),transparent 100%);
  padding-bottom:28px;
}
/* A reference never breaks across lines: WS-00479 is one word. If the
   status pill beside it has no room, the pill drops to the next line. */
html:not(#v2) .card > .spread:first-child{flex-wrap:wrap;row-gap:6px}
html:not(#v2) .card > .spread:first-child > .dsp-md{white-space:nowrap}

/* ---------- 16. JOB FLOW AND THE DAY BOARD (20 October) ---------- */
/* The stage strip as one glass panel of equal tiles. Every tile is the
   same height: the figure in the condensed face on one line, the label in
   a box that is always two lines tall whether it needs one or two. Figure
   and label share a left edge. A group is a heading, its total, and its
   tiles; "Winning it" takes a row of its own below 1900px because nine
   tiles need the width, and the other three groups share the next row. */
html:not(#v2) .jf-strip{
  display:grid;grid-template-columns:repeat(17,minmax(0,1fr));gap:var(--s5) var(--s4);
  margin:var(--s4) 0 var(--s6);padding:var(--s5) var(--s5) var(--s4);border:1px solid var(--glass-edge);
  border-top:1px solid var(--glass-edge);border-bottom:1px solid var(--glass-edge);border-radius:var(--r-panel);
  background:var(--surface);background-image:var(--pane);box-shadow:var(--glass-hi),var(--sh2);
}
html:not(#v2) .jf-grp{padding:0;border:0;min-width:0;grid-column:span 1}
html:not(#v2) .jf-grp[data-n="9"]{grid-column:span 9}
html:not(#v2) .jf-grp[data-n="3"]{grid-column:span 3}
html:not(#v2) .jf-grp[data-n="4"]{grid-column:span 4}
html:not(#v2) .jf-grp+.jf-grp{padding-left:0}
html:not(#v2) .jf-gl{
  display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin:0 0 12px;
  font-family:var(--font);text-transform:none;letter-spacing:var(--ls-0);font-size:var(--t-meta);
  font-weight:var(--w-semi);color:var(--ink-2);
}
html:not(#v2) .jf-gl b{font-family:var(--font-fig);font-weight:var(--w-semi);font-size:var(--t-meta);color:var(--ink-3);letter-spacing:var(--ls-fig)}
html:not(#v2) .jf-stages{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);grid-template-columns:none;gap:8px}
html:not(#v2) .jf-st{
  display:flex;flex-direction:column;align-items:flex-start;gap:6px;min-width:0;width:100%;justify-self:stretch;
  padding:12px 12px 12px;border:1px solid rgba(255,255,255,.07);border-radius:var(--r-ctl);
  background:rgba(255,255,255,.035);color:var(--ink);text-align:left;cursor:pointer;
  transition:var(--tr-ui);
}
html:not(#v2) .jf-st b{font-family:var(--font-fig);font-size:var(--t-figure-sm);font-weight:var(--w-semi);
  letter-spacing:var(--ls-fig);line-height:var(--lh-solid);font-variant-numeric:tabular-nums}
html:not(#v2) .jf-st span{
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;
  font-size:var(--t-micro);line-height:var(--lh-ui);height:calc(2 * var(--t-micro) * var(--lh-ui));
  color:var(--ink-2);letter-spacing:var(--ls-micro);overflow-wrap:normal;word-break:normal;hyphens:manual;
}
html:not(#v2) .jf-st:hover{background:rgba(255,255,255,.09);border-color:rgba(255,255,255,.16)}
html:not(#v2) .jf-st.on{background:var(--int-soft);border-color:var(--int-line);box-shadow:var(--glass-hi)}
html:not(#v2) .jf-st.on b{color:var(--int-hi)}
html:not(#v2) .jf-st.on span{color:var(--ink)}
html:not(#v2) .jf-st.zero b{color:var(--ink-3);font-weight:var(--w-med)}
html:not(#v2) .jf-st.hot b{color:var(--risk)}
html:not(#v2) .jf-st.pressing,html:not(#v2) .jf-st:active{transform:scale(.97);transition-duration:80ms}
html:not(#v2) .jf-late .jf-st{border-color:rgba(255,122,102,.28);background:rgba(255,122,102,.08)}
html:not(#v2) .jf-late .jf-st:hover{background:rgba(255,122,102,.14)}
@media (max-width:1900px){
  html:not(#v2) .jf-strip{grid-template-columns:repeat(8,minmax(0,1fr))}
  html:not(#v2) .jf-grp[data-n="9"]{grid-column:1 / -1}
  html:not(#v2) .jf-grp[data-n="3"]{grid-column:span 3}
  html:not(#v2) .jf-grp[data-n="4"]{grid-column:span 4}
  html:not(#v2) .jf-grp[data-n="1"]{grid-column:span 1}
}
@media (max-width:1180px){
  html:not(#v2) .jf-strip{grid-template-columns:repeat(4,minmax(0,1fr))}
  html:not(#v2) .jf-grp[data-n="9"]{grid-column:1 / -1}
  html:not(#v2) .jf-grp[data-n="9"] .jf-stages{grid-auto-flow:row;grid-template-columns:repeat(5,minmax(0,1fr))}
  html:not(#v2) .jf-grp[data-n="3"]{grid-column:span 2}
  html:not(#v2) .jf-grp[data-n="4"]{grid-column:span 2}
  html:not(#v2) .jf-grp[data-n="4"] .jf-stages{grid-auto-flow:row;grid-template-columns:repeat(2,minmax(0,1fr))}
  html:not(#v2) .jf-grp[data-n="3"] .jf-stages{grid-auto-flow:row;grid-template-columns:repeat(2,minmax(0,1fr))}
  html:not(#v2) .jf-grp[data-n="1"]{grid-column:span 4}
  html:not(#v2) .jf-grp[data-n="1"] .jf-stages{grid-template-columns:minmax(0,1fr)}
}
@media (max-width:720px){
  html:not(#v2) .jf-strip{display:grid;grid-template-columns:1fr;gap:var(--s4);padding:var(--s4);border-bottom:1px solid var(--glass-edge);flex-direction:column}
  html:not(#v2) .jf-grp,html:not(#v2) .jf-grp+.jf-grp{grid-column:1 / -1;padding:0;border:0}
  html:not(#v2) .jf-grp[data-n] .jf-stages{display:grid;grid-auto-flow:row;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
  html:not(#v2) .jf-st{flex:none;padding:12px 12px 8px;min-height:0}
  html:not(#v2) .jf-st.zero{display:none}
  html:not(#v2) .jf-grp[data-empty]{display:none}
}

/* tabs: the app's own tab component, not a second one */
html:not(#v2) .jf-tabs{margin:0 0 var(--s4)}
html:not(#v2) .jf-tabs .jf-tab{display:inline-flex;align-items:center;gap:6px;background:none;border:0;cursor:pointer;
  padding:12px 16px;font-size:var(--t-body);font-weight:var(--w-semi);color:var(--ink-3);position:relative;border-radius:12px 12px 0 0;
  margin:0;border-bottom:0;transition:var(--tr-ui)}
html:not(#v2) .jf-tabs .jf-tab:hover{color:var(--ink);background:rgba(255,255,255,.05)}
html:not(#v2) .jf-tabs .jf-tab.on{color:var(--ink)}
html:not(#v2) .jf-tabs .jf-tab.on::after{content:"";position:absolute;left:10px;right:10px;bottom:-1px;height:2px;border-radius:2px;background:var(--int)}
html:not(#v2) .jf-n{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;
  border-radius:99px;background:var(--int);color:#fff;font-family:var(--font-fig);font-size:var(--t-micro);line-height:1;margin:0}

/* the list and the aside, as two glass panels */
html:not(#v2) .jf-cols{grid-template-columns:minmax(0,1fr) 300px;gap:var(--s5);align-items:start}
html:not(#v2) .jf-filters{margin-bottom:var(--s4)}
html:not(#v2) .jf-list{border:1px solid var(--glass-edge);border-radius:var(--r-panel);background:var(--surface);box-shadow:var(--glass-hi);
  padding:4px var(--s5);border-top:1px solid var(--glass-edge)}
html:not(#v2) .jf-row{grid-template-columns:112px minmax(0,1fr) auto auto;gap:var(--s4);padding:16px 0;border-bottom:1px solid var(--rule)}
html:not(#v2) .jf-list .jf-row:last-child{border-bottom:0}
html:not(#v2) .jf-main b{font-size:var(--t-body);font-weight:var(--w-semi)}
html:not(#v2) .jf-main:hover b{text-decoration:none;color:var(--int-hi)}
html:not(#v2) .jf-act{font-family:var(--font);text-transform:none;letter-spacing:var(--ls-0);font-size:var(--t-micro);font-weight:var(--w-semi);color:var(--int-hi)}
html:not(#v2) .jf-due{font-family:var(--font);text-transform:none;letter-spacing:var(--ls-0);font-size:var(--t-meta);font-weight:var(--w-med)}
html:not(#v2) .jf-who{font-size:var(--t-meta)}
html:not(#v2) .jf-empty{display:flex;flex-direction:column;align-items:flex-start;gap:6px;padding:var(--s6) var(--s5);
  border:1px dashed var(--glass-edge);border-radius:var(--r-panel);color:var(--ink-2)}
html:not(#v2) .jf-empty svg{color:var(--ok);margin-bottom:4px}
html:not(#v2) .jf-empty b{font-size:var(--t-lead);font-weight:var(--w-semi);color:var(--ink);letter-spacing:var(--ls-1)}
html:not(#v2) .jf-empty span{font-size:var(--t-meta)}
html:not(#v2) .jf-side{border:1px solid var(--glass-edge);border-radius:var(--r-panel);background:var(--surface);background-image:var(--pane);
  box-shadow:var(--glass-hi),var(--sh2);padding:var(--s5);border-left:1px solid var(--glass-edge);position:sticky;top:88px}
html:not(#v2) .jf-side-h{font-size:var(--t-lead);font-weight:var(--w-semi);letter-spacing:var(--ls-1);line-height:var(--lh-heading);margin-bottom:6px}
html:not(#v2) .jf-people{gap:0;margin-top:4px;font-size:var(--t-meta)}
html:not(#v2) .jf-person{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:8px 0;border-bottom:1px solid var(--rule)}
html:not(#v2) .jf-person:last-child{border-bottom:0}
html:not(#v2) .jf-person b{font-family:var(--font-fig);font-weight:var(--w-semi);font-size:var(--t-lead);letter-spacing:var(--ls-fig);color:var(--ink)}
html:not(#v2) .jf-person b i{font-style:normal;font-family:var(--font);font-size:var(--t-micro);font-weight:var(--w-semi);color:var(--risk);margin-left:6px}
html:not(#v2) .jf-side-note{font-size:var(--t-micro);color:var(--ink-3);line-height:var(--lh-ui);margin:var(--s4) 0 0}
@media (max-width:900px){
  html:not(#v2) .jf-cols{grid-template-columns:minmax(0,1fr)}
  html:not(#v2) .jf-side{position:static;border-top:1px solid var(--glass-edge);padding:var(--s4)}
  html:not(#v2) .jf-list{padding:2px var(--s4)}
  html:not(#v2) .jf-row{grid-template-columns:1fr auto;padding:12px 0}
}

/* The day board tiles, in line with the rest of the design: a plain glass
   tile, the label on top, the figure straight under it, the caption under
   that. No gradient, no amber rule down the left, no drawing behind the
   number. The money at risk is still the hero: twice the width, a bigger
   figure, in gold. */
html:not(#v2) .today > .today-c{min-height:132px;padding:20px 24px;justify-content:flex-start}
html:not(#v2) .today-v{margin-top:12px}
html:not(#v2) .today-s{margin-top:6px}
html:not(#v2) .today-art,html:not(#v2) .today-c svg.art,html:not(#v2) .today-c > svg{display:none}
html:not(#v2) .today-c.flag::before{display:none}
html:not(#v2) .today > .today-c.flag:not(.today-c.flag ~ .today-c.flag){
  grid-column:span 2;grid-row:auto;background-image:var(--pane);
}
html:not(#v2) .today > .today-c.flag:not(.today-c.flag ~ .today-c.flag) .today-v{font-size:var(--t-hero-lg);color:var(--gold)}
html:not(#v2) .today > .today-c.flag ~ .today-c.flag{background-image:var(--pane)}
html:not(#v2) .today > .today-c:nth-last-child(-n+2):not(.flag){grid-column:span 1}
html[data-theme="light"]:not(#v2) .today > .today-c.flag:not(.today-c.flag ~ .today-c.flag){background-image:none}
@media (min-width:601px) and (max-width:1080px){
  html:not(#v2) .today > .today-c.flag:not(.today-c.flag ~ .today-c.flag){grid-column:1 / -1}
}

/* The job flow strip on the home screen: nine stages on ONE line. Smaller
   type and tighter gaps so it fits a laptop; on anything narrower it slides
   sideways under a fade rather than wrapping "Payment outstanding" onto a
   second line. */
html:not(#v2) .jf-mini{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;gap:0;border-bottom:1px solid var(--rule);margin-bottom:8px}
html:not(#v2) .jf-mini::-webkit-scrollbar{display:none}
html:not(#v2) .jf-mst{flex:0 0 auto;gap:4px;padding:6px 12px 8px 0;margin-right:12px;white-space:nowrap}
html:not(#v2) .jf-mst b{font-family:var(--font-fig);font-size:var(--t-body);font-weight:var(--w-semi);letter-spacing:var(--ls-fig)}
html:not(#v2) .jf-mst span{font-size:var(--t-micro);color:var(--ink-2);letter-spacing:var(--ls-micro)}

/* ---------- 17. THE POLISH LAYER: spacing, radius, controls, alignment (20 October) ---------- */
/* Every gap, padding and margin in the app is now on the 4px grid (tokens in
   10-tokens.css; 1,338 declarations were snapped). This section sets the
   things the grid alone does not: one height per control size, one padding
   per panel kind, one radius per size, and the edges that must line up. */

/* controls: three heights, one row shares one */
html:not(#v2) .btn{height:var(--h-md);padding:0 20px;border-radius:var(--r-md)}
html:not(#v2) .btn.sm{height:var(--h-sm);padding:0 12px;border-radius:var(--r-sm);font-size:var(--t-small)}
html:not(#v2) .btn.lg{height:var(--h-lg);padding:0 24px;border-radius:var(--r-lg);font-size:var(--t-body)}
html:not(#v2) .icobtn{width:var(--h-md);height:var(--h-md);border-radius:var(--r-md)}
html:not(#v2) .chip{height:var(--h-sm);padding:0 14px;border-radius:var(--r-pill)}
html:not(#v2) .chips{gap:var(--gap-row)}
html:not(#v2) .seg{padding:3px;border-radius:var(--r-md);gap:2px}
html:not(#v2) .seg button{height:calc(var(--h-sm) - 6px);padding:0 14px;border-radius:var(--r-sm);display:inline-flex;align-items:center}
html:not(#v2) :is(.inp,input[type="text"],input[type="search"],input[type="email"],input[type="tel"],input[type="number"],input[type="date"],input[type="time"],select){
  height:var(--h-md);padding:0 14px;border-radius:var(--r-md)}
html:not(#v2) textarea{border-radius:var(--r-md);padding:10px 14px}
html:not(#v2) .searchbar{height:var(--h-md);border-radius:var(--r-md);padding:0 14px;gap:var(--gap-row)}
html:not(#v2) .btn svg,html:not(#v2) .chip svg{flex:none}
html:not(#v2) .btn{gap:var(--gap-row)}

/* panels: one padding per kind */
html:not(#v2) .card.pad{padding:var(--sp-panel)}
html:not(#v2) .card.tight{padding:var(--sp-tight)}
html:not(#v2) .tile{padding:var(--sp-tile);border-radius:var(--r-lg)}
html:not(#v2) .att{padding:var(--sp-tile);border-radius:var(--r-lg)}
html:not(#v2) .jcard{padding:var(--sp-tile) var(--sp-panel);border-radius:var(--r-xl)}
html:not(#v2) .card.stat{padding:var(--sp-tile)}
html:not(#v2) .insight{padding:var(--sp-tile)}
html:not(#v2) .today > .today-c{padding:var(--sp-tile) var(--sp-tile)}
html:not(#v2) .jf-strip{padding:var(--sp-panel) var(--sp-panel) var(--sp-tile)}
html:not(#v2) .jf-side{padding:var(--sp-panel)}
html:not(#v2) .jf-st{padding:12px}
html:not(#v2) .tblwrap{border-radius:var(--r-xl)}

/* the figure rail: separate tiles with a gap, like the day board, not four
   tiles crammed into one bordered strip that clips their corners and
   leaves them 1px inside everything else */
html:not(#v2) .rail{border:0;background:transparent;box-shadow:none;overflow:visible;border-radius:0;gap:var(--gap-panels)}
html:not(#v2) .rail > .tile{border-right:1px solid var(--glass-edge)}
html:not(#v2) .rail.wrap{gap:var(--s4)}
html:not(#v2) .figs{gap:var(--s4)}

/* vertical rhythm: 24 between panels, 32 from the page head to the content
   and between sections */
html:not(#v2) .page-head{margin-bottom:var(--gap-section);gap:var(--s4)}
html:not(#v2) .page > .card + .card,html:not(#v2) .page > .card + .grid,html:not(#v2) .page > .grid + .card,
html:not(#v2) .page > .tblwrap + .card,html:not(#v2) .page > .card + .tblwrap{margin-top:var(--gap-panels)}
html:not(#v2) .grid{gap:var(--gap-panels)}
html:not(#v2) .grid.g2,html:not(#v2) .grid.g3,html:not(#v2) .grid.g4{gap:var(--gap-panels)}
html:not(#v2) .today{gap:var(--s4)}
html:not(#v2) .sec-head{margin-bottom:var(--s3);align-items:baseline;gap:var(--s3)}
html:not(#v2) .sec-head .btn,html:not(#v2) .sec-head .chip{align-self:center}
html:not(#v2) .row{gap:var(--gap-row)}
html:not(#v2) .spread{gap:var(--s2) var(--s4)}
html:not(#v2) .stack{gap:var(--s4)}
html:not(#v2) .stack.sm{gap:var(--s2)}
html:not(#v2) .stack.md{gap:var(--s3)}
html:not(#v2) .jf-home{margin:var(--gap-panels) 0}
html:not(#v2) .page{padding:8px 32px 112px}

/* the same gutter on every phone screen */
@media (max-width:900px){
  html:not(#v2) .page{padding:4px var(--s4) 120px}
  html:not(#v2) .mob-body,html:not(#v2) .appmain .page{padding-left:var(--s4);padding-right:var(--s4)}
  html:not(#v2) .card.pad{padding:var(--sp-tile)}
  html:not(#v2) .jcard{padding:var(--sp-tight)}
  html:not(#v2) .tile,html:not(#v2) .att{padding:var(--sp-tight)}
  html:not(#v2) .today > .today-c{padding:var(--sp-tight)}
  html:not(#v2) .grid,html:not(#v2) .grid.g2,html:not(#v2) .grid.g3,html:not(#v2) .grid.g4,html:not(#v2) .rail,html:not(#v2) .rail.wrap,html:not(#v2) .today{gap:var(--s3)}
  html:not(#v2) .page-head{margin-bottom:var(--s5)}
}
/* a table that is the only thing in an unpadded card was drawn with two
   borders, the card's and its own, 1px apart; the card steps aside */
html:not(#v2) .card:not(.pad):not(.tight):has(> .tblwrap:only-child){border:0;background:none;box-shadow:none;border-radius:0;padding:0}
/* The Home hero band: it still bled out to the page edges with its own
   paper gradient and a scaffold drawing behind the greeting, which in V2
   read as a pale block with a hard edge that lined up with nothing. The
   greeting and the day board now sit on the page like everything else. */
html:not(#v2) .hero{margin:0;padding:0;overflow:visible;isolation:auto;background:none}
html:not(#v2) .hero::before,html:not(#v2) .hero::after,html:not(#v2) .hero-art{display:none}
html:not(#v2) .hero .page-head{margin-bottom:var(--gap-section)}
@media (max-width:900px){
  html:not(#v2) .hero{margin:0;padding:0}
  html:not(#v2) .hero .page-head{margin-bottom:var(--s5)}
}
/* one rhythm down every page: 24px between panels, 32px after a page head
   (the two collapse to the larger, so nothing doubles) */
html:not(#v2) :is(.page,.hero) > * + *{margin-top:var(--gap-panels)}
html:not(#v2) :is(.page,.hero) > .page-head{margin-bottom:var(--gap-section)}
html:not(#v2) .page > .hero{margin-top:0}
html:not(#v2) :is(.page,.hero) > .page-head + *{margin-top:0}
html:not(#v2) .jf-home{margin:0}
html:not(#v2) .page > .jf-home + *,html:not(#v2) .page > * + .jf-home{margin-top:var(--gap-panels)}
html:not(#v2) #mboard{margin-bottom:0}

/* ---------- 18. STEADY: nothing moves unless somebody asked it to ----------
   Measured with qa/shiftprobe.mjs and qa/togglerec.mjs, 21 October 2026.
   The findings this answers: every press replayed the page's entrance fade
   (the page went to opacity 0 and back on each click); a page head that fit
   beside its buttons at one sidebar width wrapped under them at the other,
   so collapsing the rail moved everything below it by 58px; the job board's
   filter chips re-wrapped between two and three rows as the rail moved; a
   filter token or a week label changed width and pushed the button beside
   it; a lone card stretched to the full width when its neighbours were
   filtered away; table columns re-measured themselves on every filter. */

/* a same-screen re-render keeps the page where it is: no entrance fade.
   51-motion.js marks those renders with html.rerender; the rule that was
   meant to catch them only covered the page's children, not the page. */
html.rerender .page.fade,html.rerender .page.fade > *{animation:none!important}


/* figures that change with a press keep their width to the digit */
html:not(#v2) :is(.pw-range,.dsp,.fig,.num,.money,.jf-gl .num,.chip .num,.tag,.pill){font-variant-numeric:tabular-nums}

@media (min-width:901px){
  /* A page head lays out for the narrower of the two sidebar widths, so
     the sidebar never changes its shape. 51d-steady.js measures the head
     against the open-sidebar width and sets .two-line when the title and the
     buttons would not share a line there; otherwise the head never wraps,
     at either width. */
  html:not(#v2) .page-head{flex-wrap:nowrap}
  html:not(#v2) .page-head > :first-child{min-width:0;flex:1 1 auto}
  html:not(#v2) .page-head > .row{flex:0 0 auto;flex-wrap:nowrap;max-width:100%}
  html:not(#v2) .page-head.measure{flex-wrap:wrap}
  html:not(#v2) .page-head.measure > *{flex:0 0 auto;max-width:100%}
  html:not(#v2) .page-head.two-line{flex-wrap:wrap}
  html:not(#v2) .page-head.two-line > :first-child{flex-basis:100%}
  html:not(#v2) .page-head.two-line > .row{flex-basis:100%;justify-content:flex-start;flex-wrap:wrap}
  /* a section head never drops its button under its title */
  html:not(#v2) .sec-head{flex-wrap:nowrap}
  html:not(#v2) .sec-head > :first-child{min-width:0}
  html:not(#v2) .sec-head > .row,html:not(#v2) .sec-head > .btn,html:not(#v2) .sec-head > .chips{flex:0 0 auto}

  /* The job board bar has one shape: the view filters on their own line,
     the work filters and the search on the next. It was a flex row that
     let the search climb up beside the chips when the rail gave it room,
     squeezing the chips to three rows. */
  html:not(#v2) .board-bar{display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"views views" "work search";
    gap:var(--s3) var(--s4);align-items:center}
  html:not(#v2) .board-bar > .chips:first-child{grid-area:views}
  html:not(#v2) .board-bar > .chips:nth-child(2){grid-area:work}
  html:not(#v2) .board-bar > .jsearch{grid-area:search;flex:none;width:340px;justify-self:end}
}

/* A filter strip is one line. It never re-wraps as the page changes
   width; when it is longer than the page it scrolls sideways, with the
   same fade the tables use to say there is more. 51d-steady.js marks the
   strips (a chip row with a selected chip, sitting in a head, a board
   bar or straight on the page) and scrollHints() keeps the fade honest. */
html:not(#v2) .chips.strip{flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none;
  -webkit-overflow-scrolling:touch;overscroll-behavior-x:contain;min-width:0;max-width:calc(100% + 4px);padding:2px;margin-left:-2px;margin-right:-2px}
html:not(#v2) .chips.strip::-webkit-scrollbar{display:none}
html:not(#v2) .chips.strip > *{flex:0 0 auto}
html:not(#v2) .chips.strip.more{-webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 56px),transparent);
  mask-image:linear-gradient(90deg,#000 calc(100% - 56px),transparent)}
html:not(#v2) .chips.strip.more.at-end{-webkit-mask-image:none;mask-image:none}

/* a stage filter on the job files is a token with its own close, so the
   row holds one thing whose width changes and nothing sits to its right */
html:not(#v2) .pill.token{height:28px;padding:0 6px 0 12px;gap:6px;border-radius:var(--r-pill);text-transform:none;letter-spacing:0;
  font-size:var(--t-meta);font-family:var(--font)}
html:not(#v2) .pill.token .pill-x{display:inline-grid;place-items:center;width:18px;height:18px;border-radius:50%;border:0;padding:0;
  background:rgba(255,255,255,.18);color:inherit;cursor:pointer;font:inherit;line-height:1}
html:not(#v2) .pill.token .pill-x:hover{background:rgba(255,255,255,.3)}
html[data-theme="light"]:not(#v2) .pill.token .pill-x{background:rgba(14,11,92,.12)}
html[data-theme="light"]:not(#v2) .pill.token .pill-x:hover{background:rgba(14,11,92,.2)}

/* the top bar's search is one width; it was flex:1 beside a flex:1 spacer,
   so it took half of whatever the sidebar gave back and grew by 87px on
   every collapse */
@media (min-width:901px){ html:not(#v2) .searchbar{flex:0 1 440px;width:440px;max-width:440px} }

/* the planner's search keeps room for its icon; the control rule in section
   17 set every input's padding to 14px and the icon sat on the placeholder */
html:not(#v2) .pw-search .inp{padding-left:40px}
html:not(#v2) .pw-search .ic{left:14px}

/* the week label on the planner keeps the arrows still whatever the dates say */
html:not(#v2) .pw-range{display:inline-block;min-width:21ch;text-align:center}

/* A grid has a fixed number of columns for its tier, so a card keeps its
   size when its neighbours are filtered away and no card changes row as
   the sidebar moves. auto-fit let a lone card stretch to the full width
   and let the column count follow the sidebar. */
@media (min-width:1181px){
  html:not(#v2) :is(.page,.hero) > .grid.g2{grid-template-columns:repeat(2,minmax(0,1fr))}
  html:not(#v2) :is(.page,.hero) > .grid.g3{grid-template-columns:repeat(3,minmax(0,1fr))}
  html:not(#v2) :is(.page,.hero) > .grid.g4{grid-template-columns:repeat(4,minmax(0,1fr))}
}
@media (min-width:901px) and (max-width:1180px){
  html:not(#v2) :is(.page,.hero) > :is(.grid.g2,.grid.g3,.grid.g4){grid-template-columns:repeat(2,minmax(0,1fr))}
}
/* a grid inside a card keeps auto-fit: its columns depend on the card */
/* a held column width (51d-steady.js) is a floor, never a clip */
html:not(#v2) .tbl th[data-held]{box-sizing:border-box}

/* ---------- 19. THE CODE SCREEN (49-portal.js gateHTML, both gates) ----------
   From the reference Ahmed sent on 21 October: boxes that fill as you
   type, a ring that closes with them, a button whose fill is the same
   progress, a spinner while checking, a green cascade and a morphing
   title on a right code, a red shake and a reason on a wrong one. */
html:not(#v2) .pinbody{position:relative;padding-top:var(--s5)}
/* THE SCREEN FITS THE WINDOW (22 October). The head and the card are one
   column with one rhythm; what gives as the window gets shorter is the
   air above the logo, then the keys. Measured with qa/_fit.mjs: a 1440
   by 900 laptop shows the whole screen, foot and demonstration line
   included; at 820 the card still ends above the fold. */
html:not(#v2) .pinhead{padding:calc(var(--s6) + var(--sat)) var(--s5) var(--s4)}
@media (min-width:721px){
  html:not(#v2) .pinwrap{padding:var(--s5) var(--s6)}
  html:not(#v2) .pinhead{padding:var(--s6) var(--s6) var(--s4)}
  html:not(#v2) .pinbody{padding:var(--s5) var(--s6) var(--s5)}
}
/* the badge: a lock in a glass disc, a ring that closes digit by digit */
html:not(#v2) .pinbadge{position:relative;width:60px;height:60px;margin:0 auto var(--s3)}
html:not(#v2) .pinring{position:absolute;inset:0;width:60px;height:60px;transform:rotate(-90deg);overflow:visible}
html:not(#v2) .pinring circle{fill:none;stroke-width:3;stroke-linecap:round}
html:not(#v2) .pinring-bg{stroke:var(--rule)}
html:not(#v2) .pinring-fg{stroke:var(--int);transition:stroke-dashoffset .42s cubic-bezier(.32,.72,0,1),stroke .3s var(--ease)}
html:not(#v2) .pinlock,html:not(#v2) .pintick{position:absolute;inset:8px;border-radius:50%;display:grid;place-items:center;
  background:var(--surface-2);color:var(--int);border:1px solid var(--rule);
  transition:opacity .28s var(--ease),transform .42s cubic-bezier(.3,1.3,.42,1),background-color .3s var(--ease),color .3s var(--ease),border-color .3s var(--ease)}
html:not(#v2) .pintick{opacity:0;transform:scale(.55);background:var(--ok);color:#fff;border-color:transparent}
html:not(#v2) .pinbadge::after{content:"";position:absolute;inset:-5px;border-radius:50%;border:2px solid var(--ok);opacity:0;pointer-events:none}
/* the boxes */
html:not(#v2) .pinboxes{display:flex;gap:10px;justify-content:center;margin:0 0 var(--s3);width:100%}
html:not(#v2) .pinbox{position:relative;width:50px;height:54px;border-radius:var(--r-md);display:grid;place-items:center;
  background:var(--surface-2);border:1px solid var(--rule);
  transition:border-color .18s var(--ease),background-color .25s var(--ease),box-shadow .25s var(--ease),transform .25s var(--ease)}
html:not(#v2) .pinbox b{font-family:var(--font-head);font-weight:var(--w-semi);font-size:var(--t-figure-sm);line-height:1;letter-spacing:var(--ls-fig);color:var(--ink);
  font-variant-numeric:tabular-nums;transition:color .25s var(--ease)}
html:not(#v2) .pinbox i{position:absolute;left:50%;bottom:7px;width:14px;height:2px;margin-left:-7px;border-radius:2px;background:var(--rule-strong);
  transition:background-color .25s var(--ease),opacity .25s var(--ease)}
html:not(#v2) .pinbox.on{border-color:rgba(22,139,255,.55)}
html:not(#v2) .pinbox.on i{background:var(--int)}
html:not(#v2) .pinbox.cur{border-color:var(--int);box-shadow:0 0 0 3px var(--int-glow)}
html:not(#v2) .pinbox.cur i{background:var(--int);animation:pinblink 1s steps(2,start) infinite}
@keyframes pinblink{to{opacity:0}}
html:not(#v2) .pinbox.pop b{animation:pinpop .34s cubic-bezier(.3,1.3,.42,1)}
@keyframes pinpop{0%{transform:translateY(-10px) scale(.86);opacity:0}100%{transform:none;opacity:1}}
html:not(#v2) .pinboxes.shake{animation:pinshake .4s cubic-bezier(.36,.07,.19,.97)}
/* the message line sits between the boxes and the keys */
html:not(#v2) .pinmsg{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;max-width:36ch;margin-bottom:var(--s3)}
html:not(#v2) .pinmsg .ic{flex:none}
html:not(#v2) .pinmsg.bad{color:var(--risk);font-weight:var(--w-semi)}
/* the keys: the tick key has gone, the button below took its job */
html:not(#v2) .pinpad{gap:10px;max-width:330px}
html:not(#v2) .pinkey{height:52px;border-radius:var(--r-lg)}   /* the large control, as on the ladder */
/* short windows, a cursor: the air goes first, then the keys, then the logo */
@media (max-height:820px){
  html:not(#v2) .pinwrap{padding-top:var(--s3);padding-bottom:var(--s3)}
  html:not(#v2) .pinkey{height:48px}
  html:not(#v2) .pinhead{padding-top:var(--s4);padding-bottom:var(--s3)}
  html:not(#v2) .pinbody{padding-top:var(--s4);padding-bottom:var(--s4)}
  html:not(#v2) .pinmsg{min-height:28px}
  html:not(#v2) .pinfoot{margin-top:var(--s3)}
}
@media (max-height:800px){
  html:not(#v2) .pinkey{height:44px} html:not(#v2) .pinverify{height:44px;margin-top:var(--s2)}
  html:not(#v2) .pinhead .logo.lg .wlogo{height:48px!important;width:181px!important}   /* the mark is sized inline (20-util.js LOGO) */
  html:not(#v2) .pinbadge,html:not(#v2) .pinring{width:52px;height:52px}
  html:not(#v2) .pinbadge{margin-bottom:var(--s2)}
}
/* a glove, not a cursor: the keys stay big on a phone whatever its height */
@media (pointer:coarse){html:not(#v2) .pinkey{height:64px}}
@media (pointer:coarse) and (max-height:840px){html:not(#v2) .pinkey{height:56px}}
@media (pointer:coarse) and (max-height:720px){html:not(#v2) .pinkey{height:52px}}
html:not(#v2) .pinfoot{margin-top:var(--s4)}
/* on a narrow phone the two foot links stack, each on its own line, with
   no dot left dangling at the end of the first */
@media (max-width:420px){
  html:not(#v2) .pinfoot{display:flex;flex-direction:column;align-items:center;gap:6px}
  html:not(#v2) .pinfoot > span.tiny{display:none}
}
/* the demonstration's way in is a line of text under the foot links, not a
   boxed button: it is an aside on a sign-in screen, and the box was 71px of
   the window on a laptop. The list it opens is unchanged. */
html:not(#v2) .demoway{margin-top:var(--s3);max-width:none;width:auto;text-align:center}
html:not(#v2) .demoway-t{width:auto;display:inline-flex;justify-content:center;padding:4px 8px;margin:0 auto;
  background:none;border:0;border-radius:var(--r-sm);color:rgba(255,255,255,.62);font-size:var(--t-meta);gap:6px}
html:not(#v2) .demoway-t:hover{background:none;color:rgba(255,255,255,.9)}
html:not(#v2) .demoway-t:focus-visible{outline:2px solid var(--int);outline-offset:2px}
html:not(#v2) .demoway-t span:first-of-type{flex:none;text-align:center}
html:not(#v2) .demoway-c{font-size:var(--t-body);line-height:1}
html:not(#v2) .demoway-b{width:100%;max-width:340px;margin:var(--s3) auto 0;text-align:left}
/* the light theme's card is white: the line and the list take ink colours
   (the boxed version was white on white here and nobody had looked) */
html[data-theme="light"]:not(#v2) .demoway-t{color:var(--ink-3)}
html[data-theme="light"]:not(#v2) .demoway-t:hover{color:var(--ink)}
html[data-theme="light"]:not(#v2) .demoway-b{background:var(--surface-2);border-color:var(--rule);color:var(--ink)}
html[data-theme="light"]:not(#v2) .demoway-p{background:#fff;border-color:var(--rule);color:var(--ink)}
html[data-theme="light"]:not(#v2) .demoway-p:hover{background:var(--surface-2);border-color:var(--rule-strong)}
html[data-theme="light"]:not(#v2) .demoway .lnk{color:var(--int)}
/* the verify button: its fill is the code's progress */
html:not(#v2) .pinverify{position:relative;overflow:hidden;width:100%;max-width:330px;height:48px;margin-top:var(--s3);border:0;border-radius:var(--r-lg);
  background:var(--surface-2);color:var(--ink-3);font:var(--w-semi) var(--t-body)/1 var(--font);cursor:pointer;
  transition:color .25s var(--ease),transform .12s var(--ease),box-shadow .3s var(--ease)}
html:not(#v2) .pinverify:disabled{cursor:default}
html:not(#v2) .pinverify:not(:disabled){box-shadow:0 10px 24px -10px rgba(22,139,255,.55)}
html:not(#v2) .pinverify:not(:disabled):active{transform:scale(.98)}
html:not(#v2) .pinverify{--pf:0%}
html:not(#v2) .pinfill{position:absolute;left:0;top:0;bottom:0;width:var(--pf);border-radius:inherit;
  background:linear-gradient(90deg,var(--int-lo),var(--int) 60%,var(--int-hi));transition:width .42s cubic-bezier(.32,.72,0,1),background-color .3s var(--ease)}
/* two copies of the label: a muted one on the track and a white one
   clipped to the fill, so the text turns white exactly where the fill
   has reached it */
html:not(#v2) .pinlbl{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:8px}
html:not(#v2) .pinlbl-w{color:#fff;clip-path:inset(0 calc(100% - var(--pf)) 0 0);transition:clip-path .42s cubic-bezier(.32,.72,0,1)}
html:not(#v2) .pinspin{display:none;width:14px;height:14px;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:pinspin .8s linear infinite}
@keyframes pinspin{to{transform:rotate(360deg)}}
html:not(#v2) .pinlbl-go{display:none;line-height:0}
/* checking */
html:not(#v2) .pinwrap.busy .pinspin{display:inline-block}
html:not(#v2) .pinwrap.busy .pinfill{background:linear-gradient(90deg,var(--int-lo),var(--int) 60%,var(--int-hi))}
html:not(#v2) .pinwrap.busy .pinbox.cur{box-shadow:none;border-color:rgba(22,139,255,.55)}
html:not(#v2) .pinwrap.busy .pinbox.cur i{animation:none}
/* a wrong code: red, a shake, and the reason */
html:not(#v2) .pinwrap.bad .pinring-fg{stroke:var(--risk)}
html:not(#v2) .pinwrap.bad .pinlock{color:var(--risk);background:var(--risk-bg);border-color:var(--risk-line)}
html:not(#v2) .pinwrap.bad .pinbox.on{border-color:var(--risk-line);background:var(--risk-bg)}
html:not(#v2) .pinwrap.bad .pinbox.on b{color:var(--risk)}
html:not(#v2) .pinwrap.bad .pinbox.on i{background:var(--risk)}
/* a right code: the ring closes, the lock becomes a tick, the boxes go
   green one after another, the title says who, then the screen opens */
html:not(#v2) .pinwrap.ok .pinring-fg{stroke:var(--ok)}
html:not(#v2) .pinwrap.ok .pinlock{opacity:0;transform:scale(.55)}
html:not(#v2) .pinwrap.ok .pintick{opacity:1;transform:none}
html:not(#v2) .pinwrap.ok .pinbadge::after{animation:pinpulse .9s var(--ease) .1s}
@keyframes pinpulse{0%{opacity:.8;transform:scale(.9)}100%{opacity:0;transform:scale(1.35)}}
html:not(#v2) .pinwrap.ok .pinbox{animation:pinok .5s cubic-bezier(.3,1.3,.42,1) both}
html:not(#v2) .pinwrap.ok .pinbox:nth-child(2){animation-delay:.07s}
html:not(#v2) .pinwrap.ok .pinbox:nth-child(3){animation-delay:.14s}
html:not(#v2) .pinwrap.ok .pinbox:nth-child(4){animation-delay:.21s}
html:not(#v2) .pinwrap.ok .pinbox:nth-child(5){animation-delay:.28s}
html:not(#v2) .pinwrap.ok .pinbox:nth-child(6){animation-delay:.35s}
@keyframes pinok{0%{background:var(--surface-2);border-color:var(--rule)}40%{transform:scale(1.08)}
  100%{background:var(--ok);border-color:var(--ok);transform:none;box-shadow:0 10px 22px -10px rgba(49,208,160,.6)}}
html:not(#v2) .pinwrap.ok .pinbox b{color:#fff;transition-delay:.2s}
html:not(#v2) .pinwrap.ok .pinbox i{opacity:0}
html:not(#v2) .pinwrap.ok .pinfill{background:linear-gradient(90deg,#0F7A52,var(--ok))}
html:not(#v2) .pinwrap.ok .pinverify{box-shadow:0 10px 24px -10px rgba(49,208,160,.6)}
html:not(#v2) .pinwrap.ok .pinspin{display:none}
html:not(#v2) .pinwrap.ok .pinlbl-go{display:inline-block}
html:not(#v2) .pinwrap.ok .pinkey{opacity:.45;pointer-events:none}
/* the title and the lede swap in place */
html:not(#v2) .pin-title,html:not(#v2) .pin-lede{position:relative}
html:not(#v2) .pin-t1,html:not(#v2) .pin-l1{display:block;transition:opacity .3s var(--ease),transform .4s cubic-bezier(.32,.72,0,1)}
html:not(#v2) .pin-t2,html:not(#v2) .pin-l2{position:absolute;left:0;right:0;top:0;display:block;opacity:0;transform:translateY(10px);
  transition:opacity .35s var(--ease) .1s,transform .45s cubic-bezier(.32,.72,0,1) .1s}
html:not(#v2) .pinwrap.ok .pin-t1,html:not(#v2) .pinwrap.ok .pin-l1{opacity:0;transform:translateY(-8px)}
html:not(#v2) .pinwrap.ok .pin-t2,html:not(#v2) .pinwrap.ok .pin-l2{opacity:1;transform:none}
/* light theme */
html[data-theme="light"]:not(#v2) .pinring-bg{stroke:rgba(14,11,92,.1)}
html[data-theme="light"]:not(#v2) .pinlock,html[data-theme="light"]:not(#v2) .pinbox{background:#fff}
/* phones: the boxes are one size everywhere (50 by 54, 15% down from the
   four-box first cut); the client gate's six only need a tighter gap */
@media (max-width:420px){html:not(#v2) .pinboxes{gap:8px}}
@media (prefers-reduced-motion:reduce){
  html:not(#v2) .pinboxes.shake,html:not(#v2) .pinbox.pop b,html:not(#v2) .pinwrap.ok .pinbox,html:not(#v2) .pinwrap.ok .pinbadge::after,html:not(#v2) .pinbox.cur i{animation:none}
  html:not(#v2) .pinwrap.ok .pinbox{background:var(--ok);border-color:var(--ok)}
}

/* ---------- 21. THE WORKER'S APP (49-portal.js viewPortalStaff) ----------
   The employee portal is a phone app first. On a desk it has the office's
   rail: the same glass panel, the mark, the same highlight under the
   current section, a count where the office has its badges, the avatar
   and the log-out at the foot. The page is a left-aligned column at a
   reading width, the phone's full-width controls come back onto the
   ladder, jobs sit two across, and the things to sign are rows in one
   panel per job rather than a card and a full-width button each. */
@media (min-width:901px){
  html:not(#v2) .appshell{grid-template-columns:92px 1fr}
  html:not(#v2) .apprail{
    width:80px;margin:12px 0 12px 12px;height:calc(100dvh - 24px);top:12px;padding:20px 0 16px;gap:4px;
    border-radius:28px;background:var(--glass);border:1px solid var(--glass-edge);box-shadow:var(--glass-hi),var(--sh2);
    -webkit-backdrop-filter:blur(26px) saturate(150%);backdrop-filter:blur(26px) saturate(150%);
  }
  html:not(#v2) .apprail::after{display:none}
  html:not(#v2) .apprail .logo{margin:0 auto 12px;justify-content:center}
  html:not(#v2) .apprail .logo .wlogo{width:34px!important;-webkit-mask-size:auto 100%;mask-size:auto 100%}
  html:not(#v2) .railtabs{display:flex;flex-direction:column;gap:4px;align-items:center;padding:0 8px;width:100%}
  html:not(#v2) .railtab{width:64px;padding:10px 0 8px;gap:5px;border:0;border-radius:14px;color:var(--chrome-2);
    font-size:var(--t-micro);font-weight:var(--w-semi);letter-spacing:var(--ls-micro);box-shadow:none;
    transition:background-color .16s var(--ease),color .16s var(--ease)}
  html:not(#v2) .railtab:hover{background:rgba(255,255,255,.07);color:#fff}
  html:not(#v2) .railtab.on{background:rgba(255,255,255,.15);color:#fff;box-shadow:inset 0 1px 0 rgba(255,255,255,.12)}
  html:not(#v2) .railtab.on::before{display:none}
  html:not(#v2) .railtab .raillbl{line-height:1.2}
  html:not(#v2) .railfoot{margin-top:auto;gap:10px;align-items:center}
  html:not(#v2) .railav{width:34px;height:34px;border-radius:50%;display:grid;place-items:center;color:#fff;
    font-size:var(--t-meta);font-weight:var(--w-semi);letter-spacing:0;cursor:default}
  html:not(#v2) .railout{width:44px;height:44px;border-radius:50%;display:grid;place-items:center;
    background:rgba(255,255,255,.08);border:1px solid var(--glass-edge);color:var(--chrome-2)}
  html:not(#v2) .railout:hover{background:rgba(255,255,255,.14);color:#fff}
  /* the main column: status at the top right, a left-aligned reading column */
  html:not(#v2) .appmain{background:transparent}
  html:not(#v2) .appmain .mob-head{background:transparent;padding:14px 32px 0}
  html:not(#v2) .appmain .mob-head > .spread{max-width:none;margin:0;min-height:32px}
  html:not(#v2) .appmain .mob-head .pill{height:28px;padding:0 12px}
  html:not(#v2) .appmain .page,html:not(#v2) .appmain .mob-body{padding:8px 32px 64px;overflow:visible}
  html:not(#v2) .appmain .page > *,html:not(#v2) .appmain .mob-body > *{max-width:960px;margin-left:0;margin-right:0}
  html:not(#v2) .apprail .railquick{width:44px;height:44px;border-radius:50%}
  html:not(#v2) .appmain .page > .stack > h1.dsp,
  html:not(#v2) .appmain .page > .stack > div:not(.card) > h1.dsp{font-size:var(--t-h1);line-height:var(--lh-display)}
  /* the field app's greeting is the screen's name on a desk, as the portal's is */
  html:not(#v2) .appmain .fgreet{font-family:var(--font-display);font-size:var(--t-h1);font-weight:var(--w-bold);
    letter-spacing:var(--ls-8);line-height:var(--lh-display);color:var(--ink);margin:0 0 var(--s4)}
  /* phone-shaped controls back on the ladder */
  html:not(#v2) .appmain .btn.block{display:inline-flex;width:auto;min-width:220px}
  html:not(#v2) .appmain .btn.lg{height:42px;padding:0 20px;font-size:var(--t-body)}
  html:not(#v2) .appmain .grid.g2:has(> .bigbtn){display:flex;gap:12px}
  html:not(#v2) .appmain .bigbtn{width:auto;min-width:200px;height:42px;padding:0 16px 0 10px;gap:10px;border-left-width:1px}
  html:not(#v2) .appmain .bigbtn .ico{width:26px;height:26px}
  /* jobs two across */
  html:not(#v2) .appmain .pj-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
  html:not(#v2) .appmain .pj-list > *{margin:0}
}
/* the count on the Actions section, on the rail and on the phone's tab bar */
html:not(#v2) .railico{position:relative;display:inline-flex;line-height:0}
html:not(#v2) .railcount{position:absolute;top:-7px;right:-14px;min-width:18px;height:18px;padding:0 5px;border-radius:99px;
  background:var(--gold);color:#2A1B00;font:var(--w-bold) var(--t-micro)/18px var(--font);font-style:normal;text-align:center;
  letter-spacing:0;box-shadow:0 0 0 2px #15125A}
/* things to sign: a panel per job, a row per thing, the whole row the target */
html:not(#v2) .pa-group{padding:0;overflow:hidden}
html:not(#v2) .pa-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:14px 20px 12px}
html:not(#v2) .pa-head b{font-size:var(--t-lead);font-weight:var(--w-semi);min-width:0}
html:not(#v2) .pa-head .tiny{white-space:nowrap;flex:none}
html:not(#v2) .pa-list{display:flex;flex-direction:column}
html:not(#v2) .pa-row{display:grid;grid-template-columns:150px minmax(0,1fr) auto;align-items:center;gap:16px;width:100%;
  min-height:56px;padding:12px 20px;border:0;border-top:1px solid var(--rule);border-radius:0;background:transparent;
  text-align:left;color:var(--ink);font:inherit;cursor:pointer;transition:background-color .16s var(--ease)}
html:not(#v2) .pa-row:hover{background:rgba(255,255,255,.05)}
html:not(#v2) .pa-row:active{background:rgba(255,255,255,.09)}
html:not(#v2) .pa-row:focus-visible{outline:2px solid var(--int);outline-offset:-2px}
html:not(#v2) .pa-kind{font-family:var(--font-head);font-size:var(--t-micro);font-weight:var(--w-semi);text-transform:uppercase;
  letter-spacing:var(--ls-caps);color:var(--ink-3);white-space:nowrap}
html:not(#v2) .pa-main{display:flex;align-items:center;gap:8px;min-width:0}
html:not(#v2) .pa-main b{font-weight:var(--w-semi);font-size:var(--t-body);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
html:not(#v2) .pa-draft{background:var(--warn-bg);color:var(--warn);flex:none}
html:not(#v2) .pa-go{display:inline-flex;align-items:center;gap:8px;color:var(--int-hi,var(--int));font-size:var(--t-meta);
  font-weight:var(--w-semi);white-space:nowrap}
html:not(#v2) .pa-go .ic:last-child{color:var(--ink-3)}
@media (max-width:720px){
  html:not(#v2) .pa-head{padding:14px 16px 12px}
  html:not(#v2) .pa-row{grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"kind go" "main go";row-gap:2px;
    padding:12px 16px;min-height:64px}
  html:not(#v2) .pa-kind{grid-area:kind}
  html:not(#v2) .pa-main{grid-area:main}
  html:not(#v2) .pa-main b{white-space:normal}
  html:not(#v2) .pa-go{grid-area:go}
}
@media (max-width:420px){html:not(#v2) .pa-go > span{display:none}}   /* the icons say it on a narrow phone */
/* a note on a job: the tick and its line stay on one line */
html:not(#v2) .appmain .row > .ic,html:not(#v2) .mshell .row > .ic{flex:none}
/* the light theme's rail is the office's */
@media (min-width:901px){
  html[data-theme="light"]:not(#v2) .apprail{
    background:linear-gradient(180deg,rgba(255,255,255,.44),rgba(255,255,255,.26));border:1px solid rgba(255,255,255,.85);
    -webkit-backdrop-filter:blur(28px) saturate(170%);backdrop-filter:blur(28px) saturate(170%);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.95),inset 0 0 0 1px rgba(14,11,92,.04),0 18px 44px rgba(14,11,92,.12);
    color:var(--ink)}
  html[data-theme="light"]:not(#v2) .apprail .logo,html[data-theme="light"]:not(#v2) .apprail .wlogo{color:#0E0B5C}
  html[data-theme="light"]:not(#v2) .railtab{color:#44476E}
  html[data-theme="light"]:not(#v2) .railtab:hover,html[data-theme="light"]:not(#v2) .railtab.on{color:#0E0B5C;background:rgba(255,255,255,.85)}
  html[data-theme="light"]:not(#v2) .railout{background:rgba(255,255,255,.6);color:#0E0B5C;border-color:rgba(14,11,92,.1)}
  html[data-theme="light"]:not(#v2) .appwho{color:var(--ink-3)}
  html[data-theme="light"]:not(#v2) .appwho b{color:var(--ink)}
}
html[data-theme="light"]:not(#v2) .railcount{box-shadow:0 0 0 2px #fff}
/* the phone head's one button: its icon was set white inline, which on the
   light theme's white button was a blank square */
html:not(#v2) .mob-head .headbtn{color:#fff}
html[data-theme="light"]:not(#v2) .mob-head .headbtn{color:#0E0B5C}
html[data-theme="light"]:not(#v2) .pa-row:hover{background:rgba(14,11,92,.04)}
html[data-theme="light"]:not(#v2) .pa-row:active{background:rgba(14,11,92,.07)}

/* ---------- 22. THE CLIENT PORTAL (42-portal.js viewPortal) ----------
   The customer's screens, on the same standard as everybody else's: the
   app's own tab component in the head, a glass tab bar at the foot on a
   phone (five tabs across the top of a 390 screen scrolled, and the last
   two were nowhere), the welcome panel without its grid pattern, and a
   quiet log-out. The content was already V2; this is the chrome. */
html:not(#v2) .portal-head{background:transparent;color:var(--ink);padding:14px 28px 0}
html:not(#v2) .portal-head .portal-in.spread{min-height:44px}
html:not(#v2) .portal-label{color:var(--ink-3)}
html:not(#v2) .portal-tabs{gap:4px;margin-top:var(--s3);padding-bottom:0;border-bottom:1px solid var(--rule)}
html:not(#v2) .ptab{padding:12px 16px;font-size:var(--t-body);font-weight:var(--w-semi);color:var(--ink-3);
  border-bottom:0;border-radius:12px 12px 0 0;position:relative;transition:var(--tr-ui)}
html:not(#v2) .ptab:hover{color:var(--ink);background:rgba(255,255,255,.05)}
html:not(#v2) .ptab.on{color:var(--ink)}
html:not(#v2) .ptab.on::after{content:"";position:absolute;left:10px;right:10px;bottom:-1px;height:2px;border-radius:2px;background:var(--int)}
html:not(#v2) .ptab .ic{opacity:1}
html:not(#v2) .ptab-n{min-width:20px;height:20px;padding:0 6px;background:var(--int);color:#fff;font-family:var(--font-fig);font-size:var(--t-micro);line-height:1}
html:not(#v2) .ptab-n.warn{background:var(--gold);color:#2A1B00}
html:not(#v2) .portal-hero{background:var(--surface-2);border:1px solid var(--rule);border-left:var(--bar) solid var(--int);border-radius:var(--r-panel,24px)}
html:not(#v2) .portal-hero::after{display:none}
html:not(#v2) .phero-date{color:var(--ink-3)}
html:not(#v2) .portal-body{padding:var(--s5) 28px var(--s8)}
@media (max-width:720px){
  html:not(#v2) .portal-head{padding:12px 16px 0}
  html:not(#v2) .portal-head .portal-in.spread{min-height:40px}
  /* the tabs go to the foot, as the field app's and the employee portal's do */
  html:not(#v2) .portal-tabs{position:fixed;z-index:40;left:14px;right:14px;width:auto;max-width:none;bottom:calc(12px + var(--sab));height:68px;margin:0;
    display:flex;align-items:stretch;justify-content:space-around;gap:0;overflow:visible;border:1px solid var(--glass-edge);border-radius:var(--r-pill);
    background:rgba(22,19,88,.72);-webkit-backdrop-filter:blur(26px) saturate(160%);backdrop-filter:blur(26px) saturate(160%);
    box-shadow:var(--glass-hi),0 18px 44px rgba(3,2,30,.5);-webkit-mask-image:none;mask-image:none;scroll-snap-type:none}
  html:not(#v2) .ptab{flex:1 1 0;min-width:0;flex-direction:column;justify-content:center;gap:4px;padding:6px 2px;min-height:0;
    font-size:var(--t-micro);font-weight:var(--w-semi);letter-spacing:var(--ls-micro);color:var(--ink-3);border-radius:16px;white-space:nowrap}
  html:not(#v2) .ptab:hover{background:transparent}
  html:not(#v2) .ptab.on{color:#fff}
  html:not(#v2) .ptab.on::after{display:none}
  html:not(#v2) .ptab .ic{width:21px;height:21px}
  html:not(#v2) .ptab-n{position:absolute;top:4px;left:calc(50% + 4px);min-width:18px;height:18px;padding:0 5px;font-size:var(--t-micro);line-height:18px;
    background:var(--gold);color:#2A1B00;box-shadow:0 0 0 2px #15125A}
  html:not(#v2) .ptab-n:not(.warn){background:var(--int);color:#fff}
  html:not(#v2) .portal-body{padding:var(--s4) 16px calc(100px + var(--sab))}
  html:not(#v2) .portal-hero{border-radius:20px}
}
/* the light theme */
html[data-theme="light"]:not(#v2) .portal-head{color:var(--ink)}
html[data-theme="light"]:not(#v2) .portal-head .logo,html[data-theme="light"]:not(#v2) .portal-head .wlogo{color:#0E0B5C}
html[data-theme="light"]:not(#v2) .ptab:hover{background:rgba(14,11,92,.05)}
html[data-theme="light"]:not(#v2) .portal-hero{background:#fff;color:var(--ink)}
@media (max-width:720px){
  html[data-theme="light"]:not(#v2) .portal-tabs{background:rgba(255,255,255,.82);border-color:rgba(14,11,92,.1);box-shadow:inset 0 1px 0 #fff,0 18px 44px rgba(14,11,92,.14)}
  html[data-theme="light"]:not(#v2) .ptab.on{color:#0E0B5C}
  html[data-theme="light"]:not(#v2) .ptab-n{box-shadow:0 0 0 2px #fff}
}

/* ---------- 20. FIGURE STRIPS: a row of counts that reads as one thing ----------
   From Ahmed's screenshot of the Live operations key (21 October): the
   cells sat at their own widths with a divider after each, the first one
   flush to the card's edge while the title above it started 24px in, and
   the last one leaving half the row empty. A strip is a grid of equal
   cells now, with a short rule between cells and none at the ends, its
   first cell on the same line as the panel's text. The Home job flow strip
   is the same family: nine equal cells, the figure over a two-line label,
   instead of a row that scrolled off the right-hand edge. */
html:not(#v2) .om-keys{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:0;margin:0;padding:0 var(--s5);
  border-top:1px solid var(--rule);border-bottom:1px solid var(--rule)}
html:not(#v2) .om-key{position:relative;display:flex;align-items:baseline;gap:8px;min-height:56px;padding:12px 16px;margin:0;border:0;
  border-radius:0;text-align:left;min-width:0}
html:not(#v2) .om-key:first-child{padding-left:0}
html:not(#v2) .om-key:last-child{padding-right:0}
html:not(#v2) .om-key::after{content:"";position:absolute;right:0;top:25%;bottom:25%;width:1px;background:var(--rule)}
html:not(#v2) .om-key:last-child::after{display:none}
html:not(#v2) .om-key i{width:10px;height:10px;align-self:center;margin-top:1px}
html:not(#v2) .om-key b{font-family:var(--font-fig);font-size:var(--t-figure-sm);font-weight:var(--w-semi);letter-spacing:var(--ls-fig);line-height:var(--lh-solid);font-variant-numeric:tabular-nums}
html:not(#v2) .om-key span{font-size:var(--t-meta);color:var(--ink-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
html:not(#v2) .om-key:hover{background:transparent}
html:not(#v2) .om-key:not(.on){opacity:.42}
@media (max-width:720px){
  html:not(#v2) .om-keys{grid-template-columns:repeat(2,minmax(0,1fr));padding:0 var(--s4);border-bottom:1px solid var(--rule)}
  html:not(#v2) .om-key{min-height:52px;padding:10px 12px;border-bottom:0}
  html:not(#v2) .om-key:nth-child(odd){padding-left:0}
  html:not(#v2) .om-key:nth-child(even){padding-right:0}
  html:not(#v2) .om-key:nth-child(even)::after{display:none}
  html:not(#v2) .om-key:nth-child(-n+2){border-bottom:1px solid var(--rule)}
}
/* the Home job flow strip */
html:not(#v2) .jf-mini{display:grid;grid-template-columns:repeat(9,minmax(0,1fr));gap:0;overflow:visible;border-top:1px solid var(--rule);border-bottom:1px solid var(--rule);margin:0 0 8px}
html:not(#v2) .jf-mst{position:relative;display:flex;flex-direction:column;align-items:flex-start;gap:4px;padding:10px 12px;margin:0;min-width:0;
  white-space:normal;text-align:left;border-radius:0}
html:not(#v2) .jf-mst:first-child{padding-left:0}
html:not(#v2) .jf-mst:last-child{padding-right:0}
html:not(#v2) .jf-mst::after{content:"";position:absolute;right:0;top:22%;bottom:22%;width:1px;background:var(--rule)}
html:not(#v2) .jf-mst:last-child::after{display:none}
html:not(#v2) .jf-mst b{font-size:var(--t-figure-sm);line-height:var(--lh-solid)}
html:not(#v2) .jf-mst span{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;line-height:var(--lh-ui);min-height:calc(2 * var(--t-micro) * var(--lh-ui))}
html:not(#v2) .jf-mst.zero{opacity:.55}
@media (max-width:900px){
  html:not(#v2) .jf-mini{display:flex;flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;border-top:0}
  html:not(#v2) .jf-mst{flex:0 0 112px;padding:8px 12px 10px 0;margin-right:12px}
  html:not(#v2) .jf-mst::after{display:none}
  html:not(#v2) .jf-mst:first-child{padding-left:0}
}

/* ---------- 23. REFINEMENT PASS (26 October) ----------
   From the UI QA pass of 26 October (qa/_qa/QA-REPORT-2026-10-26.md).
   Refinement only: every control, every word and every feature that was
   there is still there. Each rule names the finding it answers. */

/* D1. A tile or a card that is a <button> centred what was in it once the
   grid stretched it to the row's height, so a row of them went ragged:
   three Commercial labels 9px under the fourth, one Scaffolds reference
   18px over the next. A button now lays out from its top edge, as the
   <div> tiles beside it always did. */
/* (a card laid out as a row, .spread or .row, is a flex row already and
   was never centred, so it is left as it is) */
html:not(#v2) button:is(.tile,.card):not(.spread,.row){display:flex;flex-direction:column;justify-content:flex-start;align-items:stretch;text-align:left}

/* D4. The desk top bar stays at the top and turns to glass when the page
   scrolls, as designed. "15-command.css .main>*{position:relative}" came
   later than the bar's sticky at the same weight and won, so the search,
   Ask G, the bell and the theme button scrolled away with the page. */
html:not(#v2) .main > .topbar{position:sticky;top:0;z-index:30}

/* D3. "Not ready yet" is one quiet line above the page title (31d-tours.js
   tourNrLine), at every width. It never covers a control. */
html:not(#v2) .nr-line{display:flex;align-items:center;gap:8px;margin:0 0 var(--s2);font-size:var(--t-meta);
  line-height:var(--lh-ui);color:var(--ink-2);font-weight:var(--w-body)}
html:not(#v2) .nr-line b{color:var(--ink);font-weight:var(--w-semi)}
html:not(#v2) .nr-line .nr-dot{width:8px;height:8px}

/* D6. On a phone, Guides is a row in More (30-shell.js openMore), as it is
   in the sidebar's foot on a desk. The round button floated over the start
   of whatever line scrolled under it. */
html[data-device="phone"]:not(#v2) .tour-gbtn{display:none}

/* D7. A phone head is a column that fills the gutter; 19d section 1 pushed
   a short title block to the right edge at every width, which set the Jobs
   lede 24px off the gutter. The Jobs lede keeps two lines of room, so a
   filter that swaps a one-line lede for a two-line one moves nothing. */
@media (max-width:720px){
  html:not(#v2) .page-head{align-items:stretch}
  html:not(#v2) .page-head .jb-lede{min-height:2lh}
}

/* D8. Hover in the light theme. Section 4's hovers are written for navy
   (white text) and section 9 then painted the controls white, which kept
   the white text and lost the background: a chip or a ghost button went
   white on white under the pointer, and the primary did not answer at all. */
html[data-theme="light"]:not(#v2) :is(.btn-s,.chip:not(.on),.icobtn):hover{background:#EEF0FA;border-color:var(--ink-3);color:var(--ink)}
html[data-theme="light"]:not(#v2) .btn-g:hover{background:rgba(14,11,92,.06);color:var(--ink)}
html[data-theme="light"]:not(#v2) .btn-p:hover{background:#1C1966;border-color:#1C1966}
html[data-theme="light"]:not(#v2) .jcard:hover{border-color:var(--rule-strong)}
html[data-theme="light"]:not(#v2) .railout:hover{background:#FFFFFF;color:#0E0B5C}

/* D9. An empty figure reads None on the figure line, quietly, so its
   caption stays level with the captions beside it. */
html:not(#v2) :is(.today-v,.tile > .v):empty::before{content:"None";color:var(--ink-3);font-weight:var(--w-body)}

/* D10. The field app's page is on the ground, like every other surface,
   not on a flat slab that starts under the head. */
html:not(#v2) .mob-body{background:transparent}

/* D12. A card title that runs to two lines is set at the heading leading,
   not solid (15px type on a 15px line, the lines touching). */
html:not(#v2) .dsp-sm{line-height:var(--lh-heading)}
/* D13. Titles break into balanced lines, never one word left on its own. */
html:not(#v2) :is(h1,h2,h3,h4,.jc-t,.sec-title,.dsp-sm){text-wrap:balance}

/* D14. The operations board has six tiles, not seven: the last one takes
   the room the seventh would have had, so the board has no hole. */
html:not(#v2) .today > .today-c:last-child:nth-child(6){grid-column:span 2}

/* W4. An insight is a panel like every other, its status a soft wash from
   the leading edge (as .card.led does) instead of a hard bar on square
   corners. In the stacked list on Home the rows keep their rules. */
html:not(#v2) .insight{border-left:0;background-image:linear-gradient(90deg,rgba(108,184,255,.14) 0,rgba(255,255,255,0) 38%),var(--pane)}
html:not(#v2) .insight:not(.insight-stack > .insight){border:1px solid var(--glass-edge);border-radius:var(--r-xl);box-shadow:var(--glass-hi)}
html[data-theme="light"]:not(#v2) .insight{background-image:linear-gradient(90deg,rgba(42,63,214,.08) 0,rgba(255,255,255,0) 38%)}
html[data-theme="light"]:not(#v2) .insight:not(.insight-stack > .insight){border-color:var(--rule)}

/* W5. The client's main button in daylight: white on blue (7.5 to 1),
   not navy on blue (2.1 to 1). */
html[data-theme="light"]:not(#v2) .btn-a{color:#fff}
html[data-theme="light"]:not(#v2) .btn-a .ic{color:#fff}

/* W6. On a phone the bell, More and the job's sections come up from the
   bottom like every other sheet, with the same handle, and drag down to
   close (51-motion.js). A notification's buttons sit under its words, so
   a two-line message is two lines, not seven. */
@media (max-width:720px){
  html:not(#v2) .sheet.drawer,html:not(#v2) .sheet.drawer.wide{top:auto;bottom:0;left:0;right:0;width:100vw;max-width:100vw;
    height:auto;max-height:92dvh;border-left:0;transform:translateY(100%)}
  html:not(#v2) .sheet.drawer.in{transform:none}
  html:root .sheet.drawer .sheet-grab{display:block;position:absolute;top:0;left:0;right:0;height:22px;z-index:2;touch-action:none;cursor:grab}
  html:root .sheet.drawer .sheet-grab::before{content:"";position:absolute;top:8px;left:50%;width:38px;height:5px;margin-left:-20px;
    border-radius:var(--rp);background:var(--rule-strong)}
  html:root .sheet.drawer .sheet-head{touch-action:none;padding-top:calc(var(--s4) + 8px)}
  html:root .sheet.drawer.dragging{transition:none!important}
  html:not(#v2) .nc-row{flex-direction:column;align-items:stretch;gap:var(--s3)}
  html:not(#v2) .nc-acts{justify-content:flex-start;align-items:center}
  html:not(#v2) .nc-acts .icobtn{margin-left:auto}
}

/* D11. Jobs: Table and Board are one toggle of one width, so pressing it
   leaves every button in the head where it was (33d-jobboard.js and
   33-jobs.js carry the same buttons in the same order).
   The width is in em (26 October, night): at the 15px button size "Board"
   came out 3px wider than "Table" and both passed 104px, so on a phone the
   table's head wrapped Import to a second line and the board's did not.
   The basis is the same 8em, so where the head's buttons share a tablet's
   row (820px and under) they share it from the same start. */
html:not(#v2) .jb-view{min-width:8em;flex-basis:8em;justify-content:center}

/* W7. The Routes tab keeps its badge's room every week, empty or not, so
   the week arrows never move the tabs beside it. */
html:not(#v2) .tabs .tab-badge{min-width:7ch}
html:not(#v2) .tabs .tab-badge.is-empty{visibility:hidden}

/* W3. A section folded on a phone (51d-steady.js folds, 38-gov.js library):
   the title is the line you tap, a chevron says it opens. */
html:not(#v2) details.mfold > summary.mfold-sum{display:flex;align-items:center;gap:var(--s3);cursor:pointer;list-style:none;min-height:var(--h-md)}
html:not(#v2) details.mfold > summary.mfold-sum::-webkit-details-marker{display:none}
html:not(#v2) details.mfold > summary.mfold-sum > .sec-title,html:not(#v2) details.mfold > summary .mfold-t{flex:1;min-width:0;margin:0}
html:not(#v2) details.mfold .mfold-ch{flex:none;display:inline-flex;color:var(--ink-3);transition:transform .22s var(--ease)}
html:not(#v2) details.mfold[open] .mfold-ch{transform:rotate(180deg)}
html:not(#v2) details.mfold[open] > summary.mfold-sum{margin-bottom:var(--s3)}
html:not(#v2) details.mfold > summary.mfold-sum:focus-visible{outline:2px solid var(--int);outline-offset:4px;border-radius:var(--r-sm)}
html:not(#v2) details.mfold-in{border:1px solid var(--glass-edge);border-radius:var(--r-lg);padding:0 var(--s3);background:var(--surface-2)}
html:not(#v2) details.mfold-in[open]{padding-bottom:var(--s3)}
html:not(#v2) details.mfold-in .mfold-t{font-weight:var(--w-semi);font-size:var(--t-meta);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
html:not(#v2) details.mfold-in .mfold-n{flex:none}
@media (prefers-reduced-motion:reduce){html:not(#v2) details.mfold .mfold-ch{transition:none}}

/* W1. A chip set or tool row marked data-holdwrap is laid out at the
   open-sidebar width in both states (51d-steady.js holds), so the report
   picker and the planner's tools keep their lines while the sidebar
   springs. Nothing is hidden: they still wrap. (Not Ask G's suggestions:
   they sit in a capped column the sidebar does not widen, so the open
   width cannot be worked out from the main column there.) */

/* PAIRS. One look per component on every surface. */
/* a count on a tab is in the system face; Oswald is for figures */
html:not(#v2) .ptab-n{font-family:var(--font);font-weight:var(--w-bold)}
/* the current tab on a phone: white (navy in daylight) on a soft pill,
   in the office, the field app, the employee portal and the client portal */
@media (max-width:720px){
  html:not(#v2) :is(.mbtab,.mtab,.ptab).on{color:#fff}
  html:not(#v2) :is(.mbtab,.mtab).on::before{content:"";position:absolute;inset:2px;border-radius:var(--r-md);background:rgba(255,255,255,.12);box-shadow:none;z-index:-1}
  html:not(#v2) .ptab{z-index:0}
  html:not(#v2) .ptab.on::before{content:"";position:absolute;inset:2px;border-radius:var(--r-md);background:rgba(255,255,255,.12);z-index:-1}
  html[data-theme="light"]:not(#v2) :is(.mbtab,.mtab,.ptab).on{color:#0E0B5C}
  html[data-theme="light"]:not(#v2) :is(.mbtab,.mtab,.ptab).on::before{background:rgba(14,11,92,.07)}
}
/* the field app greets him at the page title's size on a phone, as the
   employee portal does and as it already did on a desk */
html:not(#v2) .fgreet{font-family:var(--font-display);font-size:var(--t-h1);font-weight:var(--w-bold);
  letter-spacing:var(--ls-8);line-height:var(--lh-display);color:var(--ink);margin:0 0 var(--s4)}
/* the primary button is 42 tall with 14 corners on every surface */
@media (min-width:901px){html:not(#v2) .appmain .btn.lg{border-radius:var(--r-md)}}
/* the field app's big action is the app's primary: white on navy, navy in daylight */
html[data-theme="dark"]:not(#v2) .bigbtn.p{background:#FFFFFF;color:var(--navy-brand,#0E0B5C);border-color:#FFFFFF}
html[data-theme="dark"]:not(#v2) .bigbtn.p .ico{background:rgba(14,11,92,.08);color:var(--navy-brand,#0E0B5C)}
html[data-theme="light"]:not(#v2) .bigbtn.p{background:#0E0B5C;border-color:#0E0B5C;color:#fff}
/* a tile that opens a screen answers the pointer the way the day board's do */
html:not(#v2) button.tile.go{transition:transform var(--dur) var(--ease),background-color .16s var(--ease),border-color .16s var(--ease)}
@media (hover:hover){
  html:not(#v2) button.tile.go:hover{background-color:var(--surface-2);border-color:rgba(255,255,255,.24);transform:translateY(-2px)}
  html[data-theme="light"]:not(#v2) button.tile.go:hover{border-color:var(--rule-strong)}
}
/* the worker's rail and tabs, the client's tabs and a thing to sign press
   under the finger like every office control (51-motion.js PRESSABLE) */
html:not(#v2) :is(.ptab,.pa-row){transition:color .16s var(--ease),background-color .16s var(--ease),transform 140ms cubic-bezier(.2,.9,.3,1)}
html:not(#v2) :is(.railtab,.ptab).pressing{transform:scale(.97);transition-duration:80ms}
html:not(#v2) .pa-row.pressing{transform:scale(.99);transition-duration:80ms}
@media (prefers-reduced-motion:reduce){
  html:not(#v2) :is(.railtab,.ptab,.pa-row).pressing,html:not(#v2) button.tile.go:hover{transform:none}
}
/* the client portal on a desk: the logo, the first tab's label and the
   cards share one left edge, and Log out ends where the cards end (the
   head sat 28px outside the body on both sides) */
@media (min-width:721px){
  html:not(#v2) .portal-head .portal-in{padding-left:28px;padding-right:28px;box-sizing:border-box}
  html:not(#v2) .portal-tabs > .ptab:first-child{margin-left:-16px}
}
/* one empty state: the dashed panel, centred, on the panel radius */
html:not(#v2) .empty{border-radius:var(--r-panel)}
html:not(#v2) .ml-empty{padding:var(--s7) var(--s5);text-align:center;border:1px dashed var(--rule-strong);border-radius:var(--r-panel);
  background:var(--surface-2);max-width:none;margin:var(--s4) 0}
html:not(#v2) .ml-empty p{max-width:52ch;margin:0 auto var(--s3)}
/* a card on a phone is padded 16, as LAYOUT-SYSTEM.md says */
@media (max-width:900px){html:not(#v2) .card.pad{padding:var(--sp-tight)}}
/* Ask G on a touch screen says how a touch screen closes it */
html:not(#v2) .g-esc-t{display:none}
@media (hover:none){html:not(#v2) .g-esc-k{display:none} html:not(#v2) .g-esc-t{display:inline}}

/* R1. The few values that were off the system. */
html:not(#v2) .seg button{height:calc(var(--h-sm) - 8px)}                 /* a segmented control is 36 */
html:not(#v2) .jf-tabs .jf-tab{height:var(--h-md);padding:0 16px}          /* a tab is 42 */
html:not(#v2) :is(.tabs button,.jf-tabs .jf-tab){border-radius:var(--r-md) var(--r-md) 0 0}
@media (min-width:721px){html:not(#v2) .ptab{border-radius:var(--r-md) var(--r-md) 0 0}}
html:not(#v2) .sheet.modal{border-radius:var(--r-xl)}                        /* sheets on the panel radius */
html:not(#v2) .sheet.drawer{border-radius:var(--r-xl) 0 0 var(--r-xl)}
@media (max-width:720px){
  html:not(#v2) .sheet.modal{border-radius:var(--r-xl) var(--r-xl) 0 0}
  html:not(#v2) .sheet.drawer,html:not(#v2) .sheet.drawer.wide{border-radius:var(--r-xl) var(--r-xl) 0 0}
}
@media (min-width:901px){html:not(#v2) .apprail{border-radius:var(--r-xl)}}
html:not(#v2) :is(.plan-t.fig,.money-v.fig,.gauge-mv){font-weight:var(--w-semi)}   /* Oswald at 600 */
html:not(#v2) .jf-sec h2{letter-spacing:var(--ls-micro)}                    /* a 12px label is tracked open */

/* ---------- 24. TYPE AND SPACING, THE APPLE WAY (26 October) ----------
   Ahmed, from a phone screenshot of the code screen: "how would Apple do
   their spacing". What Apple does, applied: things that belong together
   sit close and groups sit apart, on 4 / 8 / 16 / 24 / 32; secondary text
   is smaller and quieter, never louder; large type is drawn only a little
   tight (10-tokens.css, tracking and leading); a link is a colour, not an
   underline; a count is the same size as the words beside it. */

/* ----- the code screen (49-portal.js gateHTML) -----
   The head is one stack, spaced the way Apple stacks a sign-in: the mark,
   16 to whose door this is, 4 to the title, 8 to one quiet line of
   explanation set in two balanced lines; then 20 to the card. Inside the
   card the lock and the boxes are one group, the keys the next, the
   button the next, the links last. The title is the screen's name, so it
   takes the page title's size and tracking (section 3). The keys are
   controls, so they take the system face at a regular weight, as every
   key on an iPhone does; the code being typed stays a figure. Every step
   is measured against a window: a 408 by 790 side panel, an iPhone, a
   laptop at 900 and at 800 tall, all with the button in view
   (qa/gatespec.mjs). */
html:not(#v2) .pinhead{padding:calc(var(--s6) + var(--sat)) var(--s5) var(--sp-tile)}
html:not(#v2) .pin-eyebrow{margin-top:var(--s4);color:var(--sky-400);font-size:var(--t-meta);font-weight:var(--w-semi);letter-spacing:var(--ls-0)}
html:not(#v2) .pin-title{margin-top:var(--s1);color:#fff}
html:not(#v2) .pin-lede{margin:var(--s2) auto 0;max-width:25em;font-size:var(--t-body);line-height:var(--lh-ui);color:rgba(255,255,255,.66)}
html:not(#v2) .pinhead .pin-lede{text-wrap:balance}   /* two even lines; the head is navy in both themes */
html:not(#v2) .pinbody{padding-top:var(--s5)}
html:not(#v2) .pinbadge{margin-bottom:var(--s3)}
html:not(#v2) .pinboxes{margin-bottom:var(--s2)}
html:not(#v2) .pinmsg{min-height:28px;margin-bottom:var(--s2);font-size:var(--t-meta)}
html:not(#v2) .pinpad{gap:var(--s3) 14px}
html:not(#v2) .pinkey{font-family:var(--font-display);font-size:var(--step-5);font-weight:var(--w-body);letter-spacing:0;
  background:rgba(255,255,255,.07);border-color:transparent;color:#fff}
html:not(#v2) .pinkey:hover{background:rgba(255,255,255,.11)}
html:not(#v2) .pinkey.ghost{background:transparent;color:var(--ink-2)}
html:not(#v2) .pinverify{margin-top:var(--s4)}
html:not(#v2) .pinfoot{margin-top:var(--s4);font-size:var(--t-body)}
html:not(#v2) .pinfoot .lnk{font-weight:var(--w-med)}
html:not(#v2) .pinfoot-dot{margin:0 var(--s2);color:rgba(255,255,255,.35)}
html:not(#v2) .demoway{margin-top:var(--s3)}
html:not(#v2) .demoway-t{font-size:var(--t-meta);color:rgba(255,255,255,.55)}
html[data-theme="light"]:not(#v2) .pinkey{background:rgba(14,11,92,.05);color:var(--ink)}
html[data-theme="light"]:not(#v2) .pinkey:hover{background:rgba(14,11,92,.09)}
html[data-theme="light"]:not(#v2) .pinkey.ghost{background:transparent;color:var(--ink-3)}
html[data-theme="light"]:not(#v2) .pinfoot-dot{color:var(--ink-3)}
/* on a narrow phone the two links stack, the dot goes, and each link is
   a target a thumb can find rather than one line of text */
@media (max-width:420px){
  html:not(#v2) .pinfoot{gap:0}
  html:not(#v2) .pinfoot-dot{display:none}
  html:not(#v2) .pinfoot .lnk{padding:6px var(--s3)}
  html:not(#v2) .pinfoot{margin-top:var(--s2)}
}
@media (min-width:721px){
  html:not(#v2) .pinhead{padding:var(--s6) var(--s6) var(--s5)}
}
/* a short window gives up the air first, then the mark and the badge, and
   only then the keys' size; under a finger the keys keep their glove sizes */
@media (max-width:720px) and (max-height:900px){
  html:not(#v2) .pinwrap{padding-top:var(--s3);padding-bottom:var(--s3)}
  html:not(#v2) .pinhead{padding-top:calc(var(--s5) + var(--sat));padding-bottom:var(--s4)}
  html:not(#v2) .pinhead .logo.lg .wlogo{height:48px!important;width:181px!important}   /* the mark is sized inline (20-util.js LOGO) */
  html:not(#v2) .pinbody{padding-top:var(--s4);padding-bottom:var(--s4)}
  html:not(#v2) .pinbadge,html:not(#v2) .pinring{width:52px;height:52px}
}
@media (min-width:721px) and (max-height:820px){
  html:not(#v2) .pinhead{padding-top:var(--s5);padding-bottom:var(--s4)}
  html:not(#v2) .pinbody{padding-top:var(--s4)}
  html:not(#v2) .pinbadge,html:not(#v2) .pinring{width:52px;height:52px}
}
@media (max-height:800px){
  html:not(#v2) .pinhead{padding-top:calc(var(--s4) + var(--sat));padding-bottom:var(--s4)}
  html:not(#v2) .pinhead .logo.lg .wlogo{height:44px!important;width:166px!important}
  html:not(#v2) .pinbody{padding-top:var(--s3)}
  html:not(#v2) .pinbadge,html:not(#v2) .pinring{width:44px;height:44px}
  html:not(#v2) .pinbadge{margin-bottom:var(--s3)}
  html:not(#v2) .pinmsg{min-height:24px}
  html:not(#v2) .pinkey{font-size:var(--step-4)}
  html:not(#v2) .pinverify{margin-top:var(--s3)}
}
@media (min-width:721px) and (max-height:800px){
  html:not(#v2) .pinwrap{padding-top:var(--s4);padding-bottom:var(--s4)}
  html:not(#v2) .pinmsg{min-height:24px}
}
@media (max-height:700px){
  html:not(#v2) .pinhead{padding-top:calc(var(--s3) + var(--sat));padding-bottom:var(--s3)}
  html:not(#v2) .pinhead .logo.lg .wlogo{height:40px!important;width:151px!important}
  html:not(#v2) .pin-eyebrow{margin-top:var(--s3)}
  html:not(#v2) .pin-lede{margin-top:6px}
  html:not(#v2) .pinbody{padding-top:var(--s3)}
  html:not(#v2) .pinbadge,html:not(#v2) .pinring{width:40px;height:40px}
  html:not(#v2) .pinmsg{min-height:24px}
  html:not(#v2) .pinpad{row-gap:var(--s2)}
  html:not(#v2) .pinverify{margin-top:var(--s3)}
}

/* ----- a link is a colour, not an underline ----- */
html:not(#v2) .lnk{text-decoration:none;font-weight:var(--w-med)}
html:not(#v2) .lnk:hover{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px}
/* inside a sentence a link keeps a quiet underline, so it is never told
   apart by its colour alone */
html:not(#v2) :is(p,li,.lede) .lnk{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px;
  text-decoration-color:color-mix(in srgb,currentColor 45%,transparent)}

/* ----- a note under a title is quieter than the title -----
   A title set 13px semibold with a note under it in .lede.tiny is the
   shape of most rows in the app: a notification, a brief row, a setting,
   a stop, an attention card. The base system set that note at 12px. The
   glass layer then gave every .lede the body size, and as one rule
   outweighs the other it took the notes with it, so 453 notes were set
   at 15px under 13px titles: the explanation louder than the thing it
   explains. Apple's footnote is 13 under a 13 to 17 headline; the note
   takes the meta size and keeps its ink-2 colour, so weight and colour
   tell title from note and the note never outranks its title. */
html:not(#v2) .lede.tiny{font-size:var(--t-meta);line-height:var(--lh-ui)}
/* a paragraph of small print keeps to a reading measure on a wide screen;
   a caption inside a table or a row keeps the width it is given */
@media (min-width:1181px){
  html:not(#v2) :is(.page,.card,section,.sheet-body) > p.tiny{max-width:72ch}
}

/* ----- lines break where a reader would break them -----
   A heading is balanced, so a two-line title is two even lines rather
   than a full line and one word. Running text avoids leaving a single
   word on its last line. Both are hints a browser that does not know
   them ignores, so nothing depends on them. */
html:not(#v2) :is(h1,h2,h3,.page-title,.sec-title,.dsp-md,.sheet-head h2){text-wrap:balance}
html:not(#v2) :is(p,.lede){text-wrap:pretty}

/* ----- small print is not tightened -----
   Body text is drawn a touch tight (10-tokens.css, --ls-body), and a
   letter-spacing set in em is inherited as the pixels it came to, so
   every 12px caption under it was drawn as tight as the 15px body. Apple
   draws its 12pt captions at 0. This is a default with no weight of its
   own: any rule that sets a tracking for one of these still wins. */
:where(.tiny,.tag,small,.lbl,.avatar){letter-spacing:var(--ls-micro)}

/* ----- a count is the size of the words beside it -----
   17-fixes.css set every .num to the input size on a phone, to stop
   Safari zooming into number fields; it caught the count in every chip,
   so "Today 11" set the 11 two sizes larger than "Today". Fixed where it
   was written: it names input.num now, so a figure keeps the size its
   own panel gives it and a chip's count takes the chip's. */

/* ----- a row keeps its button beside it on a phone -----
   A gang is a row: its name, its lorry, who is in it, and Edit. On a
   phone the attention-card rule let the button drop under the words at
   full width, so every gang took 140px and half of it was the button.
   A row on an iPhone keeps its trailing control on the row. */
@media (max-width:720px){
  html:not(#v2) .att.gangrow{flex-wrap:nowrap;align-items:center}
  html:not(#v2) .att.gangrow > .btn{width:auto;margin:0}
  /* the job flow card on Home: the button stays at the top right, as a
     card's own action does, and its words (not its box) meet the edge */
  html:not(#v2) .jf-home > .spread{flex-wrap:nowrap;align-items:flex-start;gap:var(--s3)}
  html:not(#v2) .jf-home > .spread > .btn-g{margin:-14px -12px 0 0;flex:none}
}

/* ----- a scaffold's number is coloured, not highlighted -----
   The number on a scaffold card is green when its money is on target and
   red when it is behind (37-people.js, 12-modules.css). The glass layer's
   pill colours share the two class names, so the green one gained a tinted
   box with no padding, which reads as selected text, and the red one went
   grey, which hid the one warning it carries. */
html:not(#v2) .dsp.tag-ok{background:none;color:var(--ok)}
html:not(#v2) .dsp.tag-off{background:none;color:var(--risk)}

/* ----- a table on a phone is a list, at a list's row height -----
   On a phone every table is a stack of cards, one row per field. The
   glass layer's 56px row, right for a desk table, came with them, so each
   field (Customer, When, Value) stood in 56px for a 20px line, and a
   quote took 330px. A row is 40px at least, as a row of an iPhone list,
   and grows with what is in it; the last row of a card has no rule under
   it. */
@media (max-width:720px){
  html:not(#v2) .tbl td{height:auto;min-height:40px}
  html:not(#v2) .tbl td.cell-lead,html:not(#v2) .tbl tr.is-empty td{min-height:0}
  html:not(#v2) .tbl tbody tr > td:last-child{border-bottom:0}
}

/* ----- a padded block inside a card is padded -----
   The rota board (37i-rota-board.js) is a card holding a padded head, a
   table that runs edge to edge, and a padded list of changes. Only
   .card.pad had a padding, so the head and the list sat on the card's
   edge with the week's name cut against its top. */
html:not(#v2) .card > .pad{padding:var(--sp-panel)}
@media (max-width:900px){html:not(#v2) .card > .pad{padding:var(--sp-tight)}}
/* on a phone the table between them is a stack of cards, so it takes the
   same inset rather than its cards meeting the outer card's edge */
@media (max-width:720px){html:not(#v2) .card > .pad ~ .tblwrap{padding:0 var(--sp-tight)}}
/* and on a phone a day of the rota is a row of that card, the width of the
   card, its day on the left and its box on the right. The desk's 48px day
   column came with it and squeezed each day's box to a sliver nobody could
   press. */
@media (max-width:720px){
  html:not(#v2) .rota-t td.c{width:auto;padding-left:0;padding-right:0}
  html:not(#v2) .rota-t td.c > .rdot{flex:none}
}

/* ----- an empty figure: None, quietly, where the figure would stand ----- */
html:not(#v2) :is(.today-v,.tile > .v):empty::before{font-family:var(--font);font-size:var(--t-title);font-weight:var(--w-med);letter-spacing:0}

/* ----- the phone's top bar: Apple's margins, and a dot inside its button ----- */
@media (max-width:720px){
  html:not(#v2) .mtop{padding-left:var(--s2);padding-right:var(--s3);gap:var(--s1)}
  html:not(#v2) .mtop .dotbadge{top:8px;right:8px;width:10px;height:10px;min-width:0;padding:0}
}

/* ---------- 25. ALIGNMENT (26 October, evening) ----------
   Ahmed: "some centring and alignment problems have arisen ... we want
   everything formatted professionally across the app". Measured with
   qa/alignprobe.mjs on every screen and the common sheets, at a phone and a
   desk: a middle item floating in a row spread to its two ends, words off
   the vertical or horizontal centre of the control they sit in, an icon off
   its words' centre, and a status pill drawing two marks. */

/* ----- a phone table's value sits on the right, all of it -----
   A field with two parts ("sent 24 Oct" and "2 days ago", a status and "not
   exported", a date and who) was spread across the row with its first part
   floating in the middle. The label holds the left; everything else is one
   group on the right, 8px apart. */
@media (max-width:720px){
  html:not(#v2) .tbl td{justify-content:flex-end;column-gap:var(--s2)}
  html:not(#v2) .tbl td::before{margin-right:auto;padding-right:var(--s2)}
  /* a totals row (the rota's "On the tools") stacks into a card like the
     rows above it: 30-shell.js labels its cells now */
  html:not(#v2) .tbl tfoot{display:block}
  html:not(#v2) .tbl tfoot tr{display:block;margin-bottom:12px;padding:var(--s3) var(--s4) 12px;
    background:var(--surface);border:1px solid var(--border);border-radius:var(--r3);box-shadow:var(--sh1)}
  html:not(#v2) .tbl tfoot td{width:auto;background:none}
  /* a column centred on a desk (the rota's days) is a value like any other
     once it stacks, so it sits on the right with the rest */
  html:not(#v2) .tbl td.c{text-align:right}
}

/* ----- a value that runs to more lines keeps its label on the first -----
   A long value (what a rate covers, why a job lost money) wrapped beside a
   label centred against the whole block, so the label sat between its
   lines. A name with a muted line under it (a job and its customer, a
   document and what it is attached to) and a date with its notes were laid
   side by side as narrow columns, each wrapping on its own. labelTables()
   in 30-shell.js reads what each cell is made of (cellShape) and the phone
   lays it out to match: one piece wraps beside its label; lines stack under
   each other on the right; short pieces stay on one line and wrap whole.
   The label is a column of its own and sits on the value's first line. A
   cell with a control, an avatar or a meter in it is left as it was. */
@media (max-width:720px){
  html:not(#v2) .tbl td:is(.td-text,.td-lines):not(.mob-x){display:grid;grid-template-columns:max-content minmax(0,1fr);
    column-gap:var(--s4);row-gap:0;align-content:center;align-items:baseline;justify-items:end}
  html:not(#v2) .tbl td:is(.td-text,.td-lines)::before{grid-column:1;grid-row:1 / span 12;
    margin-right:0;padding-right:0;justify-self:start}
  html:not(#v2) .tbl td.td-lines > *{grid-column:2}
  html:not(#v2) .tbl td.td-flow{flex-wrap:wrap;align-content:center;align-items:baseline;row-gap:0}
  /* a job reference keeps its 44px of reach (13-devices.css), and the reach
     adds nothing to the row: an inline link's padding never did, and a cell
     on a phone lays its pieces out as boxes, where padding takes room */
  html:not(#v2) .tbl td > a{margin-block:-16px}
}

/* ----- one mark on a status pill -----
   A pill carries a glyph (tick, triangle, cross, dot, ring: 11-components.css,
   so a state never rests on colour alone) and sp() also put a 6px dot in
   front of the words, so "Sent" read as two dots and a word. The glyph
   stays; the dot goes wherever a glyph is drawn. */
html:not(#v2) .pill:is(.ok,.warn,.risk,.info,.grey) > .dot{display:none}

/* ----- a segmented control centres its words -----
   Its buttons are flex boxes, so text-align did nothing and a word shorter
   than its button sat to the left ("Gangs (4)" was 3px off centre). */
html:not(#v2) .seg button{justify-content:center}

/* ----- an attention card's dot is on its first line, in its colour -----
   It sat halfway down the card, between the label and the body, and was
   drawn in the text colour (white) rather than the card's state. */
html:not(#v2) .att > .dot{align-self:flex-start;margin-top:5px}
html:not(#v2) .att.risk > .dot{color:var(--risk)} html:not(#v2) .att.warn > .dot{color:var(--warn)}
html:not(#v2) .att.ok > .dot{color:var(--ok)} html:not(#v2) .att.info > .dot{color:var(--info)}

/* ----- a head keeps its title on a line of its own before it squeezes -----
   The operations map's head is a title, a pill and one or two buttons in a
   wrapping row. The title block was allowed to shrink to nothing first, so
   on a phone "Mon 26 Oct 2026 · 9 sites on the map" stood one word to a
   line beside the pill. It holds 220px now; below that the pill and the
   buttons take the next line. */
html:not(#v2) .ops-head > .grow{flex:1 1 220px}

/* ----- a diary entry reads along its title's line -----
   The time and the kind of entry were centred on the whole entry (title,
   note, the people on it), so on a two-line entry "07:30" sat below the
   title it belongs to. Both sit on the title's line now, as a calendar
   does, and the entry's tags start there too. */
html:not(#v2) .dy-row{align-items:start}
html:not(#v2) .dy-row > .num{line-height:calc(var(--t-meta) * var(--lh-ui))}
html:not(#v2) .dy-row > .num + span{display:flex;align-items:center;height:calc(var(--t-meta) * var(--lh-ui))}
html:not(#v2) .dy-row > .dy-tags{margin-top:calc((var(--t-meta) * var(--lh-ui) - 24px) / 2)}

/* ----- a borderless button in a phone's page head sits on the text edge -----
   The phone stacks a page head into a column and stretched its buttons to
   the full width, which is right for a filled button and wrong for a
   borderless one: the brief's Refresh floated in the middle of the page.
   It keeps its own width, and its words meet the edge the lede starts on. */
@media (max-width:720px){
  html:not(#v2) .page-head > .btn-g{align-self:flex-start;margin-left:calc(-1 * var(--s3))}
}

/* ----- the scaffold summary is a panel, not a docket rule -----
   Its V1 look was a printed docket: a 2px ink rule over it. On navy glass
   that is a hard white line, and on the job page the summary was the one
   block between the cards that was not a card. The rule is the ordinary
   hairline now, and inside a panel (the job page, 36g-spec.js) it draws
   none at all. */
html:not(#v2) .sp-card{border-top:1px solid var(--rule)}
html:not(#v2) .card > .sp-card{border:0;padding:0;margin:0}

/* ----- a card's status stays at its top right -----
   A card head is a title block and, at the end, a status pill (a customer
   and their margin). The head wrapped whenever the name ran long, so on
   the same row of cards one pill sat beside the name and the next one
   under it. The pill keeps the corner; a long name takes a second line. */
html:not(#v2) .card > .spread:first-child:has(> .pill:last-child){flex-wrap:nowrap;align-items:flex-start}
html:not(#v2) .card > .spread:first-child:has(> .pill:last-child) > :first-child{min-width:0;flex:1 1 auto}
html:not(#v2) .card > .spread:first-child:has(> .pill:last-child) > .pill{flex:none;margin-top:2px}

/* ----- one line between a section's title and its table -----
   A section head draws a rule under its title, and a table draws its own
   frame, so a table straight under a title had two lines 12px apart. The
   table's frame is the line. */
html:not(#v2) .sec-head:has(+ .tblwrap){border-bottom-color:transparent;padding-bottom:0}

/* ----- a locked day's padlock sits on the box's corner -----
   In the field app's rota the padlock was drawn inside the top-right of a
   42px box, over the end of the day's name ("Mon", "Tue"). It is a small
   badge on the corner now, clear of the words, as a count sits on an icon. */
html:not(#v2) .rotabox .rb-l{top:-6px;right:-6px;width:18px;height:18px;border-radius:var(--r-pill);
  display:grid;place-items:center;background:var(--surface);border:1px solid var(--glass-edge);opacity:1;color:var(--ink-2)}

/* ----- a disclosure is a chevron, not the browser's triangle -----
   About twenty "More" and "show the detail" rows used the browser's own
   black triangle, which sat on a different baseline from the words and
   looked like a default nobody chose. They draw the same chevron as the
   folds: pointing at the words while shut, turned down while open. The
   folds, the score's "clear" list and the fix sheet's "+" keep their own. */
html:not(#v2) details:not(.mfold):not(.ovclear):not(.fxmore) > summary:not(.mfold-sum){list-style:none;cursor:pointer}
html:not(#v2) details:not(.mfold):not(.ovclear):not(.fxmore) > summary:not(.mfold-sum)::-webkit-details-marker{display:none}
html:not(#v2) details:not(.mfold):not(.ovclear):not(.fxmore) > summary:not(.mfold-sum)::marker{content:""}
html:not(#v2) details:not(.mfold):not(.ovclear):not(.fxmore) > summary:not(.mfold-sum)::before{content:"";display:inline-block;
  width:6px;height:6px;margin:0 10px 0 2px;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
  opacity:.7;transform:translateY(-2px) rotate(-45deg);transition:transform .2s var(--ease)}
html:not(#v2) details[open]:not(.mfold):not(.ovclear):not(.fxmore) > summary:not(.mfold-sum)::before{transform:translateY(-3px) rotate(45deg)}
@media (prefers-reduced-motion:reduce){
  html:not(#v2) details:not(.mfold):not(.ovclear):not(.fxmore) > summary:not(.mfold-sum)::before{transition:none}
}

/* ----- a set of buttons on a phone lines up in columns -----
   The planner's four tools and a client's project actions wrapped at their
   own widths, so each line ended somewhere different. On a phone they are
   two equal columns; an odd one out takes the full width. */
@media (max-width:720px){
  html:not(#v2) :is(.pw-actions,.pj-acts){display:grid;grid-template-columns:1fr 1fr;gap:var(--s2)}
  html:not(#v2) :is(.pw-actions,.pj-acts) > .btn{width:100%;justify-content:center}
  html:not(#v2) :is(.pw-actions,.pj-acts) > .btn:last-child:nth-child(odd){grid-column:1/-1}
}

/* ----- a tile's info mark follows its label's last word -----
   The label was a flex row, so a label that wrapped ("Expiring within 30
   days") pushed its info mark to the far edge, halfway between its lines,
   while a short one kept the mark beside it. The label is a line of text
   now and the mark sits after its last word, every time. */
html:not(#v2) .tile .k{display:block}
html:not(#v2) .tile .k .ihelp{vertical-align:-3px}
html:not(#v2) .tile .k > .ic{vertical-align:-2px;margin-right:4px}
/* any label's info mark is held to its last word (tile() and helpGlue
   wrap the two together), so a label that wraps never leaves the mark
   alone at the start of a line */
html:not(#v2) .ihelp-end{white-space:nowrap}
/* and the figures in a row of tiles share a line: 51d-steady.js gives the
   labels in one row the height of the tallest, so "Expired" beside
   "Expiring within 30 days" no longer puts its figure 20px higher */


/* ---------- 26. TYPE AND SIZE, THE APPLE WAY (26 October, night) ----------
   Ahmed: "another apple grade typography and formatting polish ... spacing,
   sizing, font sizing, the whole shebang". Measured before: on a phone 45%
   of every character on the office screens was 12px, 29% 13px and 22% 15px,
   which is iOS's Small text setting, a notch below what an iPhone ships
   with. */

/* ----- a phone reads at iOS's own sizes -----
   The text roles step up one lattice step at phone width: what you read is
   17 (iOS Body), secondary copy, table values and buttons 15 (Subhead),
   notes under a title, help text, chips and small labels 13 (Footnote).
   12 is left for what is not read as a sentence: pills, counts, the tab
   bar. Display roles keep their own tiers; the bar title keeps 15 so a
   screen's name still fits beside the mark and the four buttons. */
@media (max-width:720px){
  :root{--t-tiny:var(--step--1);--t-small:var(--step--1);--t-meta:var(--step-0);--t-body:var(--step-1)}
  /* a page's opening line is secondary copy, not body text */
  html:not(#v2) .lede{font-size:var(--t-meta)}
  /* a field's label, a key, an eyebrow: the note size, not the pill size */
  html:not(#v2) :is(.tbl td::before,.lbl,.kv-k,.eyebrow){font-size:var(--t-tiny)}
  html:not(#v2) .mtop-title{font-size:var(--step-0)}
}

/* ----- emphasis is semibold -----
   A <b> took the browser's bold, 700, so 13px names and 15px titles sat a
   weight above the 600 of every heading and button beside them. Apple sets
   emphasis in semibold at text sizes; bold is kept for page titles and the
   few figures that ask for it by name. */
html:not(#v2) :is(b,strong){font-weight:var(--w-semi)}

/* ----- a job's facts are one group, its actions the next -----
   On a phone the facts under a job's name wrapped 16px apart, further apart
   than the 12px between them and the buttons, so the eye grouped the last
   fact with the actions. Facts wrap 8 apart; the actions start 20 below. */
html:not(#v2) .card > .spread.jb-hero{row-gap:var(--sp-tile)}

/* ----- a status after a title wraps whole -----
   "Nothing waiting on you · 3 overdue" broke after its dot. On a phone the
   count takes its own line and the dot goes. */
@media (max-width:720px){
  html:not(#v2) .jf-overdue{display:block}
  html:not(#v2) .jf-overdue > .jf-sep{display:none}
}

/* ----- an empty figure on a person's band says None, like a tile -----
   Attendance with nothing booked left a gap where the figure stands, and
   its note sat on the figure's line. */
html:not(#v2) .pband-v:empty::before{content:"None";color:var(--ink-3);font-family:var(--font);
  font-size:var(--t-title);font-weight:var(--w-med);letter-spacing:0}

/* ----- a set of buttons at the head of a page is one kind of thing -----
   On a phone a page's actions sit in an even grid, and a borderless button
   among framed ones read as a gap in it (Job flow: Ignored senders and
   Connect Google beside Paste an email). In a page head on a phone it takes
   the framed look of the secondary buttons beside it. */
@media (max-width:720px){
  html:not(#v2) .page-head .row.wrap > .btn-g{background:rgba(255,255,255,.09);border:1px solid var(--glass-edge);
    color:var(--ink);box-shadow:var(--glass-hi)}
  html[data-theme="light"]:not(#v2) .page-head .row.wrap > .btn-g{background:#FFFFFF;border-color:var(--rule-strong)}
}

/* ----- a button's words are sized to the button -----
   Every button label was 13px or less, the 44px full-width primaries on a
   phone included (New job, Fix all 6, Invoice pack), so a label read like a
   placeholder in its own control. 36px carries 13, 40 to 44 carry 15, 52
   carries 17, all semibold. Chips and segmented options are 36px controls
   and carry 13. */
html:not(#v2) .btn{font-size:var(--step-0)}
html:not(#v2) .btn.sm{font-size:var(--step--1)}
html:not(#v2) .btn.lg{font-size:var(--step-1)}
html:not(#v2) :is(.chip,.seg button){font-size:var(--step--1)}
/* and a segmented option keeps its words on one line, with 12px a side so
   the planner's Gangs (4) and Lorries (6) still fit their column */
html:not(#v2) .seg button{white-space:nowrap;padding-left:var(--s3);padding-right:var(--s3)}
@media (max-width:720px){html:not(#v2) .btn.sm{font-size:var(--step-0)}}

/* ----- one heading voice -----
   Section titles were set in the secondary ink, so "Gangs" sat quieter than
   the names under it. A heading is in the primary ink; the secondary ink is
   for what supports it. */
html:not(#v2) .sec-title{color:var(--ink)}

/* ----- a row's action is not the screen's action -----
   A table with a white primary button in every row (Compliance's Photograph
   tag and Renew, Training's Schedule) set six of the loudest control on the
   screen side by side. Apple keeps one prominent button to a view; inside a
   table a row's own action takes the secondary look. */
html[data-theme="dark"]:not(#v2) .tbl .btn-p{background:rgba(255,255,255,.09);color:var(--ink);
  border-color:var(--glass-edge);box-shadow:var(--glass-hi)}
html[data-theme="dark"]:not(#v2) .tbl .btn-p .ic{color:currentColor}
html[data-theme="dark"]:not(#v2) .tbl .btn-p:hover{background:rgba(255,255,255,.15);border-color:rgba(255,255,255,.24)}
html[data-theme="dark"]:not(#v2) .tbl .btn-p:active{background:rgba(255,255,255,.2)}
html[data-theme="light"]:not(#v2) .tbl .btn-p{background:#FFFFFF;border-color:var(--rule-strong);color:var(--ink)}
html[data-theme="light"]:not(#v2) .tbl .btn-p:hover{background:#EEF0FA;border-color:var(--ink-3)}

/* ----- a figure inside a line is set in the line's face -----
   Oswald is for a figure that stands on its own, at 24px and over. A time in
   a plan, a price in a card's corner, a numbered step and a gauge's movement
   were set in it at 12 to 19px, where a condensed face reads as another
   language. They take the system face, semibold, with tabular figures. */
html:not(#v2) :is(.plan-t,.jc-v,.money,.brief-n,.dispatch-n,.gauge-mv,.gauge-of,.fhero-t){
  font-family:var(--font);font-weight:var(--w-semi);font-variant-numeric:tabular-nums;letter-spacing:0}

/* ----- a paragraph is never footnote-sized -----
   Explanations that run to several lines (a driver's CPC, what a setting
   does) were set at the note size. A paragraph takes the secondary-copy
   size, 13 on a desk and 15 on a phone; 12 and 13 stay for one-line notes. */
html:not(#v2) p.tiny{font-size:var(--t-meta)}

/* ----- tabs are one bar -----
   On a phone tabs were drawn as folders with the chosen one filled in the
   page's own navy, so the selected tab read as switched off. Phone and desk
   share one bar: the words in a row, the chosen one in the primary ink with
   the 2px rule under it. (:not(.x) lifts these over the folder rule.) */
@media (max-width:720px){
  html:not(#v2) .tabs button:not(.x){border:0;border-radius:0;background:none;box-shadow:none;
    padding:0 var(--s3);min-height:var(--h-md);color:var(--ink-3)}
  html:not(#v2) .tabs button.on:not(.x){background:none;color:var(--ink)}
  html:not(#v2) .tabs button.on:not(.x)::after{display:block}
}

/* ----- the folded sections of a long page read as one grouped list -----
   On a phone Settings (and Codes and access, PPE, Rota and time off,
   Training, Documents) was a column of separate 78px cards 16px apart, over
   1,100px before anything opened. Closed folds next to each other join into
   one list of 52px rows with a hairline between, the way an iPhone's
   Settings reads; a fold that opens stands on its own again. The title is
   the card-title size, open or shut, so it does not jump. */
@media (max-width:720px){
  html:not(#v2) details.card.mfold > summary .sec-title{font-size:var(--t-lead)}
  html:not(#v2) details.card.mfold:not([open]){padding-top:var(--s1);padding-bottom:var(--s1)}
  html:not(#v2) .stack:not(.sm):not(.md) > details.card.mfold:not([open]) + details.card.mfold:not([open]){
    margin-top:calc(-1 * var(--s4) - 1px);border-top-left-radius:0;border-top-right-radius:0}
  html:not(#v2) .stack:not(.sm):not(.md) > details.card.mfold:not([open]):has(+ details.card.mfold:not([open])){
    border-bottom-left-radius:0;border-bottom-right-radius:0}
}

/* ----- a field in a bar is the bar's height -----
   The jobs search was 46px beside 36px chips and 42px buttons. */
html:not(#v2) .jsearch{height:var(--h-md)}

/* ----- a page's actions share their columns -----
   On a phone a page's buttons wrapped at their own widths, so a 2 by 2 split
   at 185px on one line and 200 on the next. Each takes half the row when its
   words fit, and an odd one out takes the whole row. The Jobs head is the
   same: two by two on the board and on the table, so the toggle between
   them leaves every button where it was. */
@media (max-width:720px){
  html:not(#v2) :is(.page-head,.jb-hero) > .row.wrap > .btn,
  html:not(#v2) .page-head > .row:has(> .jb-view) > .btn{flex:1 1 calc(50% - var(--s1))}
}

/* ----- a screen whose title says more than its bar shows it -----
   On a phone the bar names the screen and the page's own title is hidden.
   Where the title says more than the bar (a quote's name under its
   reference, a customer's name the bar has to cut short), the page shows it,
   as an iPhone shows a large title. 51d-steady.js decides (pageTitles). */
@media (max-width:720px){
  html:not(#v2) .page-head .page-title.pt-more{display:block}
}
/* a phone number is one unit and never breaks across a line */
html:not(#v2) a[href^="tel:"]{white-space:nowrap}

/* ----- a panel has one kind of heading -----
   On a job's page the left-hand panels were headed by the 12px eyebrow
   (Erect and dismantle, RAMS, Site evidence, Load requirements, Scaffold
   summary) and the right-hand ones by the 19px section title (The work, Who
   we deal with), so one screen had two heading voices. An eyebrow that heads
   a panel, with no title under it, is the panel's title and takes the
   section title's size, weight and ink. An eyebrow above a title or a figure
   stays an eyebrow. */
html:not(#v2) :is(.card > .spread:first-child > .eyebrow:first-child,
  .card > .eyebrow:first-child:not(:has(+ :is(h1,h2,h3,h4,.page-title,.sec-title,.dsp,.fig,b,strong,.num,.v))),
  .card .sp-h){
  font-family:var(--font-display);font-size:var(--t-title);font-weight:var(--w-semi);color:var(--ink);
  letter-spacing:var(--ls-2);text-transform:none;line-height:var(--lh-heading)}

/* ----- the job flow card is padded like every other panel -----
   It carried 20 by 24 everywhere, so on a phone its words started 8px further
   in than the 16px panels above and below it. 24 on a desk, 16 from 900px. */
html:not(#v2) .jf-home{padding:var(--sp-panel)}
@media (max-width:900px){html:not(#v2) .jf-home{padding:var(--sp-tight)}}

/* ----- in a grid of facts, the label sits over its value -----
   On a desk a scaffold's or a person's facts sat four to a row, each pair
   spread across its column, so a value ("Reading Solar Installation") sat
   closer to the next label than to its own. In a grid the label stands over
   its value; a single column of facts keeps label left, value right. An
   empty value says None rather than leaving a label on its own. */
@media (min-width:721px){
  html:not(#v2) .grid > .kv{flex-direction:column;align-items:flex-start;justify-content:flex-start;gap:2px}
  html:not(#v2) .grid > .kv > .kv-v{text-align:left}
}
html:not(#v2) .kv > .kv-v:empty::before{content:"None";color:var(--ink-3);font-weight:var(--w-body)}

/* a photograph's caption is a caption, not a stamp: no tracking, semibold */
html:not(#v2) .ph-cap{letter-spacing:0;font-weight:var(--w-semi)}

/* ----- panels are 24 apart on every desk screen -----
   Brief and the detail pages (a job, a scaffold, a quote, a customer) stacked
   their panels 16 apart, the gap meant for rows inside a panel, while Home and
   Commercial used 24. A column of panels takes the panel gap; a list of
   repeated compact cards (the diary's days) keeps its own. */
@media (min-width:901px){
  html:not(#v2) .stack:has(> :is(.card:not(.tight),.jf-home,.tblwrap)){gap:var(--gap-panels)}
}

/* ----- an empty capsule says None -----
   Site evidence drew empty capsules where a stage had nothing to show; an
   empty one reads as None in the quiet ink, with no capsule around it. */
html:not(#v2) .ho-chip:empty{background:none;border-color:transparent;box-shadow:none;padding-left:0;color:var(--ink-3)}
html:not(#v2) .ho-chip:empty::before{content:"None";font-weight:var(--w-body)}
/* beside a capsule that says something, an empty one says nothing at all */
html:not(#v2) .ho-chip + .ho-chip:empty{display:none}

/* ----- the inbox's boxes sit under a hairline, not a 2px white bar -----
   The mailbox chooser carried the old docket rule, 2px of the primary ink,
   the heaviest line in the app, on a screen that is otherwise hairlines. */
html:not(#v2) .ml-boxes{border-top:1px solid var(--rule)}

/* ----- the code screen keeps the sizes it was drawn to -----
   The code screen was fitted to a phone line by line (section 24), so it
   keeps the text sizes it was fitted with rather than stepping up with the
   rest of the phone: its links and its Verify label stay 15, its note 13. */
@media (max-width:720px){
  html:not(#v2) .pinwrap{--t-tiny:var(--step--2);--t-small:var(--step--2);--t-meta:var(--step--1);--t-body:var(--step-0)}
  html:not(#v2) .pinwrap .pin-eyebrow{font-size:var(--step--1)}
  html:not(#v2) .pinwrap .lede{font-size:var(--step-0)}
}

/* =========================================================
   27. THE PHONE IN WORKING ORDER (27 October)

   Ahmed: the phone has to work flawlessly for every kind of user, and
   nothing comes out. Found by walking every screen each way in reaches
   (owner, contracts, office, health and safety, supervisor, yard, the
   field app with and without the lorry, the employee and client
   portals) at 320 to 430 wide and on its side, and by pressing every
   control on them with a touch. Nothing here moves a pixel on a desk;
   the one desk change is that a wide table now scrolls sideways instead
   of losing its right-hand columns (the last rule).
   ========================================================= */

/* ----- a quote's progress fits the screen -----
   Four steps 32px apart ran past a phone's edge ("Accepted" 55px off
   it). They keep the 32 on a desk and close up on a phone, wrapping if
   a step's words need it. */
html:not(#v2) .tline.qtl{gap:var(--s6)}
@media (max-width:720px){
  html:not(#v2) .tline.qtl{gap:var(--s2) var(--s4);flex-wrap:wrap}
}

/* ----- the info mark keeps out of the field under it -----
   On a touch screen the mark's 44px box hung 8px over the field below
   its label, and its 7px halo took that to 15: the top of the field
   opened help instead of the keyboard. The box is the 44 with no halo,
   and a field under a label with a mark sits above the mark where the
   two meet, so the field gets that strip. */
@media (pointer:coarse){
  html:not(#v2) .ihelp::before{inset:0}
  html:not(#v2) :is(label,.lbl):has(.ihelp) + :is(input,select,textarea){position:relative}
}

/* ----- a pill pressed as a button gets a thumb's reach -----
   The training matrix's Exp, the job's warning pill and the rest were
   20-odd px to press. On a touch screen each is pressed through an
   invisible 44px band (its ::after; ::before is the pill's glyph), so the
   pill's paint, shape, ring and place are exactly as they were. The
   position is weightless, so a pill placed absolutely stays placed.
   (A first version grew the box with clear borders and margins, and an
   independent review found it reshaped every pill's ends, emptied the
   pills with a fixed height and fought text links' own spacing; that
   version is gone, and text links are left as they were drawn.) */
@media (pointer:coarse){
  :where(html:not(#v2) button.pill:not(.pill-go)){position:relative}
  html:not(#v2) button.pill:not(.pill-go)::after{content:"";position:absolute;left:0;right:0;top:50%;height:44px;transform:translateY(-50%)}
  /* a row in a list is a row a thumb can press */
  html:not(#v2) .jb-scaf{min-height:44px}
  /* weightless, so a label already taller (the inbox's 56, a confirm
     box's 48) keeps its own height */
  :where(html:not(#v2) label:has(> input:is([type=checkbox],[type=radio]))){min-height:44px}
}

/* ----- a table card keeps its label -----
   On a phone a table row is a card of label and value. A value that is a
   control (the job's "Needs commercial review" pill, 207px) pushed its
   label off the card's left edge at 320: "mmercial". The control drops
   under its label instead when the two will not fit side by side. */
@media (max-width:720px){
  html:not(#v2) .tbl td:has(> :is(button,.btn,.pill,select,input)){flex-wrap:wrap;row-gap:var(--s1)}
}

/* ----- a phone on its side -----
   Safari zooms the whole page into any field under 16px the moment it
   is pressed. Up to 720px wide the fields are 17 (17-fixes.css); a phone
   held sideways is wider than that, and its fields went back to 15. The
   same rule, at the same weight, for a phone on its side. */
@media (max-height:520px) and (max-width:1099px) and (pointer:coarse){
  :where(html:not(#v2)) :is(input,select,textarea,.inp,.sel){font-size:var(--t-input)}
  :where(html:not(#v2)) :is(.pinbody .inp,.pinbody .num,#pinentry){font-size:var(--t-figure)}
}
/* The field app keeps its + on its side (it hides under 721px wide),
   and the end of the list now scrolls clear of it: it sat over the
   last card's arrow on More, Jobs, Rota and Route. */
@media (min-width:721px) and (max-height:520px) and (pointer:coarse){
  html:not(#v2) .mob-body{padding-bottom:calc(156px + var(--sab,0px));scroll-padding-bottom:calc(156px + var(--sab,0px))}
}

/* ----- a placeholder photo keeps its stencil on a phone -----
   A seeded photo's drawing is 4:3 and a phone shows it square, cut down
   the middle, so its stencil read "RRY ROUTE" and "ONT ELEVATION". On a
   phone the stencil moves in to where the square shows it. */
@media (max-width:720px){
  html:not(#v2) .ph.shot .ph-t{transform:translateX(20px)}
}

/* ----- a wide table scrolls sideways again -----
   The glass panel (section 5) clipped its table with overflow:hidden, so
   on a phone held sideways, an iPad and a desk the columns past the edge
   were gone (the jobs table's Value and Margin, Workforce's Day cost)
   while the fade at the edge still promised more. 12-modules and
   15-command both say a table scrolls rather than hides its columns.
   Same rounded clip; it now scrolls. Not the phone tier, where a table is
   a stack of labelled cards and nothing scrolls sideways. */
@media (min-width:721px){
  html:not(#v2) .tblwrap{overflow-x:auto;overflow-y:hidden}
}

/* =========================================================
   28. MOTION AND THE DESK (27 October, evening)
   From Ahmed's screen recording in Safari and two desk screenshots
   (Fleet and Growth). Nothing here is phone-only and nothing removes a
   piece of the design: each rule makes something that was already
   there move or sit the way it was meant to.
   ========================================================= */

/* ----- a day-board figure lifts, it does not jump -----
   The cell rose 2px under the pointer with only its background in the
   transition, so the rise happened in one frame, both ways. */
html:not(#v2) .today > :is(a,button).today-c{
  transition:background var(--dur) var(--ease),border-color .16s var(--ease),transform var(--dur) var(--ease)}

/* ----- an insight reads top to bottom -----
   The claim, then the figure, then the why, then the way in. The figure
   used to sit beside the title when it fitted and drop under it when it
   did not, so one row of cards had it both ways (Growth: "Conversion
   falls off sharply above £20k" against "Solar Install Access is White's
   strongest work"); and the way in sat wherever the text ended, so two
   cards side by side had their links at different heights and one card
   carried a band of empty space. The figure is always under the title
   now and the way in is always at the foot (31-ui.js insightCard). */
html:not(#v2) .insight{display:flex;flex-direction:column}
html:not(#v2) .insight > .spread{flex-direction:column;align-items:flex-start;flex-wrap:nowrap;gap:4px}
html:not(#v2) .insight > .spread > h4{flex:none;max-width:100%}
html:not(#v2) .insight > .spread > .insight-stat{flex:none;max-width:100%;white-space:normal}
html:not(#v2) .insight > .ins-foot{margin-top:auto;padding-top:var(--s3)}

/* ----- a vehicle's three facts share a line on a desk -----
   Driver, MOT and Service are each a word or two. The grid gave them two
   columns, so Service sat alone under Driver with a gap beside it. */
@media (min-width:721px){
  html:not(#v2) .vfacts{grid-template-columns:repeat(3,minmax(0,1fr))}
}

/* ----- the plus sits in the corner, not on the work -----
   At 52px, 24px in from the corner, it covered the right-hand end of
   whatever scrolled under it: the Log buttons on Fleet, the end of a
   figure on Growth, a job's status pill. At 44px (the smallest a
   control is drawn anywhere in this app) and 10px in, its left edge
   stops where a card's own padding ends, so it sits on the margin of a
   card and never on what the card says. Same white, same navy +, same
   shadow, same place; the page already ends 112px clear of it. */
@media (min-width:721px){
  html:not(#v2) .shell ~ .fab{width:44px;height:44px;border-radius:16px;right:10px;bottom:16px}
}
/* An iPad held upright (721 to 900) has 16px page margins and 16px card
   padding, so there is no margin for the + to sit on and it would cover
   the right-hand end of every card. There it moves into the top bar, the
   way the phone's bar already carries it. Same button, same action. */
html:not(#v2) .topbar .tb-quick{display:none}
@media (min-width:721px) and (max-width:900px){
  html:not(#v2) .topbar .tb-quick{display:grid}
  html:not(#v2) .shell ~ .fab{display:none}
}
/* the save chip is one phrase and never breaks; the search field gives
   up the room instead (it broke onto two lines on an upright iPad) */
html:not(#v2) .topbar .savechip{white-space:nowrap;flex:none}

/* =========================================================
   29. HEADINGS LEAD (27 October, night)
   Ahmed: "body text throughout is not disruptive to any of the heading
   texts". qa/designqa.mjs read every heading on every screen of every
   account, on a phone and a desk, against the first body text under it:
   bigger than its heading, as heavy and as bright as it, closer than 2px,
   or squeezing it onto another line. These are the ones it found.
   ========================================================= */

/* A job file's sections (Request, Dates, Price) were 12px labels over 13px
   rows, so the rows read louder than the heading over them. They are the
   body size, semibold, in the heading ink: on a phone 17 over 15. */
html:not(#v2) .jf-sec h2{font-size:var(--t-body);color:var(--ink);letter-spacing:var(--ls-0);margin:0 0 10px}

/* A phrase that must not break: a date range, a kind of work. */
.nw{white-space:nowrap}

/* =========================================================
   30. AHMED'S SCREENSHOTS (28 October, morning, Safari at 1408)
   "Look at that shit at the top", "attached to", "text in boxes is
   too big", "font hierarchy non existent", "spacing too tight",
   "a bounding box inside the search bar", "we have been through this
   so many times". Each rule names what it fixes; each kind is in
   qa/designqa.mjs now so the next one is found by the sweep.
   ========================================================= */

/* ----- the top bar's scroll edge -----
   Whatever scrolls under the bar was cut by a hard edge: the bottom
   half of a page title, the bottom half of a figure, standing under a
   band. The bar now ends in a short fade of its own glass, so content
   dissolves into it on the way under rather than being sliced. */
html.scrolled:not(#v2) .topbar::after{
  content:"";position:absolute;left:0;right:0;top:100%;height:24px;pointer-events:none;
  background:linear-gradient(180deg,rgba(10,8,64,.55),rgba(10,8,64,0));
  -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
  -webkit-mask-image:linear-gradient(180deg,#000,transparent);mask-image:linear-gradient(180deg,#000,transparent);
}
html.scrolled[data-theme="light"]:not(#v2) .topbar::after{background:linear-gradient(180deg,rgba(255,255,255,.72),rgba(255,255,255,0))}
@media (prefers-reduced-transparency:reduce){
  html.scrolled:not(#v2) .topbar::after{-webkit-backdrop-filter:none;backdrop-filter:none}
}

/* ----- the opening line in a box is secondary copy -----
   A box's first paragraph was body size, the same as the titles inside
   the box (Modules: the intro the size of "PPE & kit"; Growth: an
   insight's explanation the size of its title). In a box it is the
   note size and the second ink, and it stands a full step clear of
   what follows it, as far below as the heading is above it. A page's
   own opening line, under a 39px title, keeps the body size. */
html:not(#v2) :is(.card,.insight) > .lede{font-size:var(--t-meta);line-height:var(--lh-prose);color:var(--ink-2)}
html:not(#v2) .card:not(.insight) > .lede:not(:last-child){margin-bottom:var(--s4)}

/* ----- a field inside a bar draws no box of its own -----
   Ask G's field took every field's focus ring, so typing showed a
   ring inside the bar's ring. The bar is the box and shows the focus. */
html:not(#v2) .gbar :is(input,textarea):focus,
html:not(#v2) .gbar :is(input,textarea):focus-visible{border-color:transparent;box-shadow:none;background:transparent;outline:none}

/* ----- parts of a form never touch -----
   A field, a row of fields or a grid of them that follows a field, a
   row of fields or a line of text starts a step below it ("Attached
   to" sat 0px under the row above; the brief's addresses under
   Delivery; the RAMS contacts under their note). Not inside a grid
   or a row, where the gap is the grid's. */
html:not(#v2) :is(.card,.sheet,.modal) :not(.grid,.ra-grid,.row,.spread,.chips,.stack,.g2,.g3,.g4) >
  :is(.field,.grid,.ra-grid,p,.lede) + :is(.field,.grid:has(> .field),.ra-grid){margin-top:var(--s3)}
/* a button that follows a line of text in a box, likewise */
html:not(#v2) :is(.card,.sheet,.modal) :not(.grid,.row,.spread,.chips,.stack,.g2,.g3,.g4,.actions,.btn-row) >
  :is(p,.lede,.tiny,.muted) + :is(.btn,a.btn){margin-top:var(--s3)}

/* ----- steps in a list read as steps -----
   Getting RAMS ready: a step's title, its note and its button stood as
   close to the next step as to each other. The note sits under its
   title, the button a step under the note, and the steps a full panel
   gap apart. */
html:not(#v2) .rs-steps{gap:var(--s5)}
html:not(#v2) .rs-steps > li > b{display:block}
html:not(#v2) .rs-steps > li > .tiny.muted{margin-top:2px}
html:not(#v2) .rs-steps > li > .btn{margin-top:var(--s3)}
html:not(#v2) .rs-steps > li > ul{margin:var(--s2) 0 0}
html:not(#v2) .rs-steps > li > ul > li + li{margin-top:var(--s1)}

/* ----- the gangs panel: the hint and the button below it -----
   "Drag a gang onto a job" sat on top of New gang with no gap. */
html:not(#v2) .pw-hint{margin:var(--s3) 0}

/* ----- See it as anybody -----
   "You are here" took a column of its own beside the name and squeezed
   the Owner card's words into a column one word wide. It sits under
   the words now, and the card keeps the width for them. A role nobody
   holds shows an empty seat, not a question mark, and its line starts
   with what the role does, not with a separator. */
html:not(#v2) .rolecard .rc-here{display:inline-flex;margin-top:var(--s2)}
html:not(#v2) .rolecard .avatar.vacant{background:transparent!important;border:1.5px dashed var(--rule-strong);box-shadow:none}
/* ----- an insight: title, figure, why -----
   The title was 15px over 15px text, told apart by weight alone. The
   title is a card's title size, the figure stays the loudest thing in
   the box, and the why is the note size above (Growth, Home). */
html:not(#v2) .insight > .spread > h4{font-size:var(--t-lead)}

/* ----- on a phone, where a row wraps onto a second line -----
   The phone sweep at 390: a row's second line sat 8px under its first
   (a note and its button, a booking and Change, a count and Mark all
   seen), words were squeezed into a column beside a long pill or a set
   of times (the privacy register's names; a route's job names), and a
   diary item's Open sat 9px under its title. A wrapped line stands a
   full step below the one above, and words keep room to be words. */
@media (max-width:720px){
  html:not(#v2) .row{row-gap:var(--s3)!important}
  html:not(#v2) .row > .grow{min-width:min(100%,180px)}
  html:not(#v2) .dy-row{row-gap:var(--s3)}
  /* the second selector of each pair outranks "a card's status stays at
     its top right" above, which kept a pill as long as a sentence beside
     the name and left the name one word wide (the privacy register). A
     short pill still keeps the corner: the name only needs 55% */
  html:not(#v2) .card.tight > .spread:has(> .pill),
  html:not(#v2) .card.tight > .spread:first-child:has(> .pill:last-child){flex-wrap:wrap;row-gap:var(--s2)}
  html:not(#v2) .card.tight > .spread:has(> .pill) > b:first-child,
  html:not(#v2) .card.tight > .spread:first-child:has(> .pill:last-child) > b:first-child{flex:1 1 55%;min-width:55%}
  /* a line of words and the button that wraps under it (On hire's
     Change and Book it, the bell's Mark all seen) */
  html:not(#v2) .spread:has(> .btn, > .row > .btn){row-gap:var(--s3)}
  /* a route's stops: Move sat 12px under its own stop and 9px over the
     next one, so it read as the next stop's. It lines up under its own
     stop's name now, and the stops stand a panel step apart */
  html:not(#v2) .dispatch-b > .stack.sm:has(> .row > .btn){gap:var(--s5)}
  html:not(#v2) .dispatch-b > .stack.sm > .row > .btn{margin-left:32px}
}

/* ----- a customer's email and phone -----
   On a phone the line broke after the dot between them and left it
   hanging at the end of the email. On a phone they take a line each;
   wider, they share one with the dot between (36-sales.js). */
@media not all and (max-width:720px){html:not(#v2) .reach-br{display:none}}
@media (max-width:720px){html:not(#v2) .reach-sep{display:none}}

/* =========================================================
   31. THE EDGES OF A SCROLL (28 October, midday)
   "The app allows for excessive scrolling as well and sometimes this
   refreshes the page or just looks bad when done at the bottom."
   Each kind is in qa/designqa.mjs (OVERSCROLL, CHAIN, SLACK).
   ========================================================= */

/* ----- the page stops at its ends -----
   Pulled past its top or its bottom the page bounced: on a phone a pull
   at the top reloaded the app, and the bottom showed bare ground under
   the last card. The body said not to, but a browser reads the root for
   the page. Sideways neither, so a sideways drift on a trackpad cannot
   leave the page by accident (the app has its own back). */
html:not(#v2){overscroll-behavior:none}

/* ----- what scrolls inside the page keeps its scroll -----
   Reaching the end of the sidebar, a sheet, Ask G, a list or a preview
   carried on into the page behind it. It stops there now, as a list
   inside an iPhone app does, keeping its own small give. Up and down
   only: a table that scrolls sideways must still let the page scroll. */
html:not(#v2) :is(.side-nav,.sheet-body,.mob-body,.appmain .page,.combo-list,.helppop,.tw-panel,
  .gans,.gsug,.cmdstops,.cmdmap,.addrlist,.diag,.jf-pre,.jf-tl-col,.wrpaper,.om-card,#chat,.dv-sheet,textarea){overscroll-behavior-y:contain}

/* ----- the shade behind a sheet is not for scrolling -----
   On an iPhone a drag on the shade moved the page behind the sheet. */
html:not(#v2) :is(.scrim,#sidescrim,.gscrim){touch-action:none}

/* ----- a page ends a step above the tab bar -----
   On a phone the last card stopped 136px above the floating tab bar,
   and a short page scrolled though everything fitted: the shell keeps
   the bar's room (96px) and the page added the desk's room for the +
   and Guides buttons, which a phone does not show. It ends one panel
   step (24px) above the bar now. ("Not ready yet" no longer floats over
   the foot of a phone; it is a line in the page head, 31d-tours.js.) */
html:not(#v2) .mshell .page{padding-bottom:var(--s2)}

/* =========================================================
   32. AHMED'S SECOND ROUND (28 October, afternoon)
   Each part names the screenshot it answers. Each kind of fault has a
   check in qa/designqa.mjs: OVERSCROLL and CHAIN (the page bouncing and
   the pull that reloads it), SLACK (scrolling on past the last card),
   VOID (a card stretched mostly empty), PEEK (a figure over the line
   above it), NUDGE (a row that scrolls sideways a few pixels), GHOST (a
   button that does not look like one), CONTRAST (a flag too pale on a
   white page), SCALE (a chart's bars out of proportion to their figures),
   OFFMID (a head's button not centred on its title) and the theme-color
   part of CHROME (the browser bar the wrong colour for the theme).
   ========================================================= */

/* ----- a card stands as tall as what is in it -----
   "Look at all that space margin by work type is using": a card beside a
   taller one was stretched to its height and stood mostly empty under its
   content (Commercial, Profit, a lorry's defects, a person's tickets). A
   section card in a grid takes its own height; the row is as tall as its
   tallest card. Cards in a row that are nearly alike keep one height so
   their edges line up; 51d-steady.js marks the one that would stand more
   than 120px and more than a third empty. */
html:not(#v2) .grid > section.card.nat{align-self:start}

/* ----- a bar's figure stays inside its chart -----
   Pointed at or pressed, a bar showed its figure 19px above the column,
   over the rule under the card's title. The chart keeps that headroom
   inside itself now; the bars are as tall as they were. */
html:not(#v2) .bars{height:170px;padding-top:20px;padding-bottom:22px}
/* and every bar is drawn to scale: the name under a bar shared the bar's
   column, so any bar over four fifths of the tallest was squeezed to the
   same height as it (Solar at 46.6% stood level with Domestic at 40.2%).
   The names sit under the columns, in the chart's own foot. */
html:not(#v2) .bars .b{position:relative}
html:not(#v2) .bars .b>s{position:absolute;left:0;right:0;top:100%;margin-top:6px}

/* ----- a close button is sized to what it closes -----
   The info popover and the welcome carried the 44px glass square of a
   toolbar. A small surface gets the round close of a sheet on an iPhone:
   28px drawn, still 44px to the finger. */
html:not(#v2) :is(.helppop-head,.tw-head) .icobtn{
  position:relative;width:30px;height:30px;min-width:0;min-height:0;padding:0;border-radius:50%;
  background:rgba(255,255,255,.10);border:0;box-shadow:none;color:var(--ink-2)}
html:not(#v2) .helppop-head .icobtn{width:26px;height:26px;margin:-2px -4px 0 0}
html:not(#v2) :is(.helppop-head,.tw-head) .icobtn .ic{width:14px;height:14px}
html:not(#v2) :is(.helppop-head,.tw-head) .icobtn::after{content:"";position:absolute;inset:-9px}
html:not(#v2) :is(.helppop-head,.tw-head) .icobtn:hover{background:rgba(255,255,255,.18);color:var(--ink)}
html[data-theme="light"]:not(#v2) :is(.helppop-head,.tw-head) .icobtn{background:rgba(14,11,92,.07);border:0}
html[data-theme="light"]:not(#v2) :is(.helppop-head,.tw-head) .icobtn:hover{background:rgba(14,11,92,.12)}

/* ----- a row of tabs does not scroll up and down -----
   The open tab's underline hung 1px below the row, so the row scrolled
   by 1px and Safari drew a scrollbar beside "Today". The underline sits
   inside the tab; a row that is too wide still slides sideways, with no
   bar drawn. */
html:not(#v2) .tabs{overflow-y:hidden;scrollbar-width:none}
html:not(#v2) .tabs::-webkit-scrollbar{display:none}
html:not(#v2) .tabs button.on::after,html:not(#v2) .jf-tabs .jf-tab.on::after{bottom:0}

/* ----- a spacer left at the end of a page is not a gap to scroll into -----
   51-motion.js restarts a screen's entrance by setting and removing
   animation-name on the page's children, and the browser then writes the
   spacer's style back as "height: var(--s5);", so both spellings */
html:not(#v2) .page :is(div[style="height:var(--s4)"],div[style="height:var(--s5)"],
  div[style="height: var(--s4);"],div[style="height: var(--s5);"]):last-child{display:none}
/* a screen that draws its own page inside the shell's page (Data and
   retention, the privacy notice) took the page's room twice: 32px more in
   at each side than every other screen, and 272px to scroll past its
   last card. The inner page keeps its width and adds no room of its own. */
html:not(#v2) .page .page{padding:0}

/* ----- a new screen does not flash -----
   "Do you see that shadow flicker": each new screen began fully
   transparent, so for two frames the bare ground showed where the
   panels had been. It arrives at nine tenths and settles. */
@keyframes page-in{from{opacity:.9;translate:0 4px}to{opacity:1;translate:0 0}}

/* ----- a page's buttons all look like buttons -----
   Ignored senders and Connect Google were bare words beside framed
   buttons (Job flow). At the head of a page every button is framed, at
   every width, and every one is the same height. */
html:not(#v2) .page-head .row > .btn-g{background:rgba(255,255,255,.09);border:1px solid var(--glass-edge);
  color:var(--ink);box-shadow:var(--glass-hi)}
html:not(#v2) .page-head .row > .btn-g:hover{background:rgba(255,255,255,.15);border-color:rgba(255,255,255,.24)}
html[data-theme="light"]:not(#v2) .page-head .row > .btn-g{background:#FFFFFF;border-color:var(--rule-strong);box-shadow:0 1px 2px rgba(14,11,92,.05)}
html[data-theme="light"]:not(#v2) .page-head .row > .btn-g:hover{background:#EEF0FA;border-color:var(--ink-3)}
html:not(#v2) .page-head .row > .btn:not(.sm){min-height:44px}

/* ----- Live operations -----
   "This should be presented way better": the status and Locate floated
   as loose words at the right of the head, and the four keys were tall
   condensed numbers between hairlines. The head's two controls read as a
   status and a button; each key is a control of its own, its name first
   and its count at its end, tinted in its colour while it is shown and
   plain when it is hidden. */
html:not(#v2) .ops-head{align-items:center}
html:not(#v2) .ops-head .btn-g{background:rgba(255,255,255,.09);border:1px solid var(--glass-edge);color:var(--ink)}
html[data-theme="light"]:not(#v2) .ops-head .btn-g{background:#FFFFFF;border-color:var(--rule-strong)}
html:not(#v2) .ops-head .pill{height:30px;padding:0 12px;font-size:var(--t-meta);font-weight:var(--w-med)}
html[data-theme="light"]:not(#v2) .ops-head .pill.grey{background:#F0F1F8;color:var(--ink-2);box-shadow:inset 0 0 0 1px var(--rule)}
html:not(#v2) .om-keys{gap:10px;padding:var(--s4) var(--s5)}
html:not(#v2) .om-key,html:not(#v2) .om-key:first-child,html:not(#v2) .om-key:last-child{
  align-items:center;gap:10px;min-height:52px;padding:10px 16px;border-radius:14px;
  border:1px solid var(--rule);background:rgba(255,255,255,.04);opacity:1}
html:not(#v2) .om-key::after{display:none}
html:not(#v2) .om-key span{order:1;flex:1 1 auto;font-size:var(--t-meta);font-weight:var(--w-med);color:var(--ink)}
html:not(#v2) .om-key b{order:2;font-family:var(--font-display);font-size:var(--t-lead);font-weight:var(--w-semi);
  letter-spacing:-.01em;line-height:1;color:var(--ink)}
html:not(#v2) .om-key:hover{background:rgba(255,255,255,.08)}
html:not(#v2) .om-key.on.k-over{background:color-mix(in srgb,var(--om-over,#FF7A66) 13%,transparent);border-color:color-mix(in srgb,var(--om-over,#FF7A66) 34%,transparent)}
html:not(#v2) .om-key.on.k-insp{background:color-mix(in srgb,var(--om-insp,#FFC24D) 13%,transparent);border-color:color-mix(in srgb,var(--om-insp,#FFC24D) 34%,transparent)}
html:not(#v2) .om-key.on.k-today{background:color-mix(in srgb,var(--om-today,#6CB8FF) 13%,transparent);border-color:color-mix(in srgb,var(--om-today,#6CB8FF) 34%,transparent)}
html:not(#v2) .om-key.on.k-live{background:color-mix(in srgb,var(--om-live,#5BE3B0) 13%,transparent);border-color:color-mix(in srgb,var(--om-live,#5BE3B0) 34%,transparent)}
html:not(#v2) .om-key:not(.on){background:transparent;border-style:dashed}
html:not(#v2) .om-key:not(.on) :is(span,b){color:var(--ink-3)}
html[data-theme="light"]:not(#v2) .om-key{background:#FFFFFF;box-shadow:0 1px 2px rgba(14,11,92,.04)}
html[data-theme="light"]:not(#v2) .om-key:not(.on){background:transparent;box-shadow:none}
@media (max-width:720px){
  html:not(#v2) .om-keys{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding:var(--s3) var(--s4)}
  html:not(#v2) .om-key,html:not(#v2) .om-key:nth-child(odd),html:not(#v2) .om-key:nth-child(even),html:not(#v2) .om-key:nth-child(-n+2){
    padding:10px 12px;border-bottom:1px solid var(--rule)}
  html:not(#v2) .om-key:not(.on){border-bottom-style:dashed}
  /* two to a row on a phone, "Inspection due" and "Working today" were cut
     to "Inspection..." and "Working t...": a name takes a second line
     rather than lose its end */
  html:not(#v2) .om-key span{white-space:normal;overflow:visible;text-overflow:clip;text-wrap:balance;line-height:1.2}
}

/* ----- the attention list's flags read on any page -----
   "Contrast of these on a white page is terrible": solid red and solid
   ochre slabs, square-cornered, with dark brown on ochre. They are the
   app's own status pills now: the colour as a tint, the words in its
   strong ink, in either theme. The list's rule is the ordinary hairline,
   not a bar of ink. */
html:not(#v2) .om-flag{font-family:var(--font);text-transform:none;letter-spacing:0;font-size:var(--t-micro);
  font-weight:var(--w-semi);line-height:1.35;padding:3px 10px;border-radius:999px;white-space:nowrap}
html:not(#v2) .om-flag.f-over{background:var(--risk-bg);color:var(--risk);box-shadow:inset 0 0 0 1px var(--risk-line)}
html:not(#v2) .om-flag.f-insp{background:var(--warn-bg);color:var(--warn);box-shadow:inset 0 0 0 1px var(--warn-line)}
html:not(#v2) .om-flag.f-today{background:var(--info-bg);color:var(--info);box-shadow:inset 0 0 0 1px var(--info-line,var(--rule))}
html:not(#v2) .om-att{border-top:1px solid var(--rule);padding-top:var(--s4)}

/* ----- the job flow board -----
   "This needs to be presented way better, this is an £8500 application":
   on a white page the stages had no edges, so twenty condensed numbers
   floated over twenty wrapped labels, the zeros as loud as the work, each
   group's total marooned at the far end of its row. Each stage is a tile
   now: one where work is waiting stands up white and solid with its count
   in the display face; an empty one recedes; a group's total sits beside
   its name; Overdue is tinted in its colour. */
html:not(#v2) .jf-gl{justify-content:flex-start;gap:8px;margin:0 0 10px;font-size:var(--t-meta);color:var(--ink)}
html:not(#v2) .jf-gl b{display:inline-flex;align-items:center;justify-content:center;min-width:22px;height:20px;padding:0 7px;
  border-radius:999px;font-family:var(--font);font-size:var(--t-micro);font-weight:var(--w-semi);letter-spacing:0;
  color:var(--ink-2);background:rgba(255,255,255,.09)}
html[data-theme="light"]:not(#v2) .jf-gl b{background:#EEF0FA}
html:not(#v2) .jf-grp[data-empty] .jf-gl{color:var(--ink-3)}
html:not(#v2) .jf-late .jf-gl b{color:var(--risk);background:var(--risk-bg)}
html:not(#v2) .jf-st{gap:6px;padding:12px 14px;justify-content:space-between}
@media (min-width:721px){html:not(#v2) .jf-st{min-height:86px}}
html:not(#v2) .jf-st b{font-family:var(--font-display);font-size:24px;font-weight:var(--w-semi);letter-spacing:-.02em;line-height:1}
html:not(#v2) .jf-st span{font-size:var(--t-micro);color:var(--ink-2)}
html:not(#v2) .jf-st:not(.zero){background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.14)}
html:not(#v2) .jf-st.zero{background:transparent;border-color:rgba(255,255,255,.07)}
html:not(#v2) .jf-st.zero b{color:var(--ink-3);font-weight:var(--w-med)}
html:not(#v2) .jf-st.zero span{color:var(--ink-3)}
html[data-theme="light"]:not(#v2) .jf-st{border:1px solid var(--rule);background:transparent}
html[data-theme="light"]:not(#v2) .jf-st:not(.zero){background:#FFFFFF;border-color:var(--rule-strong);box-shadow:0 1px 2px rgba(14,11,92,.06),0 4px 14px rgba(14,11,92,.05)}
html[data-theme="light"]:not(#v2) .jf-st.zero{background:#F7F8FC;border-color:#E9EBF5}
html[data-theme="light"]:not(#v2) .jf-st:hover{border-color:var(--ink-3)}
html:not(#v2) .jf-st.on{background:var(--int-soft);border-color:var(--int-line)}
html:not(#v2) .jf-late .jf-st,html[data-theme="light"]:not(#v2) .jf-late .jf-st{background:var(--risk-bg);border-color:var(--risk-line);box-shadow:none}
html:not(#v2) .jf-late .jf-st.zero{background:transparent;border-color:var(--rule)}
html:not(#v2) .jf-late .jf-st span{color:var(--risk)}
html[data-theme="light"]:not(#v2) .jf-strip{background:#FBFBFE}

/* ----- the welcome -----
   "This intro screen requires a lot more polish, font sizing just doesn't
   give me a warm feeling": a heavy 24px title, the introduction at body
   size in near-white, every row's name louder than the title's weight
   allowed, square outlined icons and a 44px close. The title is set in
   the display face, softer and larger; the introduction is the second
   ink at a reading measure; each row is a quiet icon, its name and a
   line under it, with Show me in the interactive colour. */
html:not(#v2) .tw-panel{padding:28px 28px 22px;border-radius:24px}
html:not(#v2) .tw-head .eyebrow{font-size:var(--t-micro);font-weight:var(--w-med);letter-spacing:.01em;text-transform:none;color:var(--ink-3)}
html:not(#v2) .tw-title{font-family:var(--font-display);font-size:28px;font-weight:var(--w-semi);letter-spacing:-.022em;
  line-height:1.15;margin-top:6px}
html:not(#v2) .tw-lede{margin-top:12px;max-width:46ch;font-size:var(--t-body);line-height:1.55;color:var(--ink-2)}
html:not(#v2) .tw-list{margin-top:20px;border-top:1px solid var(--rule)}
html:not(#v2) .tw-row{gap:14px;padding:14px 8px;margin:0 -8px;width:calc(100% + 16px);border-radius:14px;border-bottom:0;position:relative}
html:not(#v2) .tw-row + .tw-row::before{content:"";position:absolute;left:62px;right:8px;top:0;height:1px;background:var(--rule)}
html:not(#v2) .tw-row:hover{background:rgba(255,255,255,.06)}
html:not(#v2) .tw-row b{font-size:var(--t-body);font-weight:var(--w-semi);color:var(--ink);letter-spacing:-.005em}
html:not(#v2) .tw-row .grow span{font-size:var(--t-meta);color:var(--ink-3);margin-top:3px;line-height:1.4}
html:not(#v2) .tw-ic{width:40px;height:40px;border:0;border-radius:12px;background:rgba(255,255,255,.08);color:var(--ink)}
html:not(#v2) .tw-go{font-size:var(--t-meta);font-weight:var(--w-med);color:var(--int)}
html:not(#v2) .tw-go .ic{transition:transform .2s var(--ease)}
html:not(#v2) .tw-row:hover .tw-go .ic{transform:translateX(3px)}
html:not(#v2) .tw-foot{margin-top:20px;gap:12px}
html:not(#v2) .tw-foot .btn{min-height:44px}
html:not(#v2) .tw-foot .btn-g{font-size:var(--t-meta);color:var(--ink-2)}
html:not(#v2) .tw-tiny{margin-top:14px;font-size:var(--t-micro);line-height:1.5;color:var(--ink-3)}
html[data-theme="light"]:not(#v2) .tw-row:hover{background:#F3F4FA}
html[data-theme="light"]:not(#v2) .tw-ic{background:#EEF0FA;color:#0E0B5C}
@media (max-width:699px){
  html:not(#v2) .tw-panel{padding:22px 18px calc(18px + var(--sab,0px));border-radius:24px 24px 0 0}
  html:not(#v2) .tw-title{font-size:24px}
}

/* ----- a scaffold's card -----
   "Look at how the whole due soon thing is so glued to everything": the
   line under the tag number sat 4px under it, the money 8px under that,
   and the four facts were labels stacked on their values with nothing
   between. The facts are rows now, the name on the left and the value on
   the right, a step apart, and the head, the line and the money each
   stand clear of the next. */
html:not(#v2) .sc-desc{margin-top:8px!important;font-size:var(--t-meta);line-height:1.4;color:var(--ink-2)}
html:not(#v2) .sc-money{margin-top:16px!important}
html:not(#v2) .sc-facts{display:flex!important;flex-direction:column;gap:10px!important}
html:not(#v2) .sc-facts > div{display:flex;align-items:baseline;justify-content:space-between;gap:16px;min-width:0}
html:not(#v2) .sc-facts > div > div:first-child{flex:none;font-size:var(--t-meta);color:var(--ink-3)}
html:not(#v2) .sc-facts > div > div:last-child{min-width:0;text-align:right;overflow-wrap:break-word;text-wrap:pretty}
/* the names in the attention list start on one line whatever the flag
   beside them says, and a group's name sits level with its neighbours
   whether or not it carries a total */
@media (min-width:721px){
  html:not(#v2) .om-row{grid-template-columns:minmax(0,176px) minmax(0,1fr) minmax(0,1.2fr)}
  html:not(#v2) .om-row .om-flag{justify-self:start}
}
html:not(#v2) .jf-gl{align-items:center;min-height:22px}
html[data-theme="light"]:not(#v2) .om-key.on.k-over{background:color-mix(in srgb,var(--om-over) 8%,#fff)}
html[data-theme="light"]:not(#v2) .om-key.on.k-insp{background:color-mix(in srgb,var(--om-insp) 8%,#fff)}
html[data-theme="light"]:not(#v2) .om-key.on.k-today{background:color-mix(in srgb,var(--om-today) 8%,#fff)}
html[data-theme="light"]:not(#v2) .om-key.on.k-live{background:color-mix(in srgb,var(--om-live) 8%,#fff)}
/* the welcome opens with a little light at its top left, the way the
   ground behind the app does */
html:not(#v2) .tw-panel{background-image:radial-gradient(560px 240px at 8% -12%,rgba(108,184,255,.16),transparent 70%)}
html[data-theme="light"]:not(#v2) .tw-panel{background-image:radial-gradient(560px 240px at 8% -12%,rgba(92,86,255,.10),transparent 70%)}
/* white initials and the open stage of a job's life were set on a blue
   that gave them 4.1:1; the deeper step of the same blue gives 5.4:1 */
.avatar{background-color:var(--sky-700)}
html[data-theme="dark"]:not(#v2) .lifestep.on .p{background:var(--sky-700);border-color:var(--sky-700)}

/* ----- a head's button sits on the middle of its title -----
   "These types of buttons on the right hand side need to be centered
   too": a section head lined its title up by the text's baseline, so the
   title rode at the top of the row and the button beside it, centred in
   the row, hung low (Pricing rules, Open the rules). Everything in a head
   is centred on one line now. */
html:not(#v2) .sec-head{align-items:center}
html:not(#v2) .sec-head > :is(.row,.chips){align-items:center}
/* a desk page keeps 112px at its foot for the + button; a screen without
   the + button (the privacy notice) ends a normal step below its last
   card instead of 112px */
html:not(#v2) #app:not(:has(> .fab)) .shell .page:not(.page .page){padding-bottom:calc(var(--s5) + var(--s4))}
/* the privacy notice is drawn straight into #app with no shell round it,
   so the rule above never reached it */
html:not(#v2) #app > .page{padding-bottom:calc(var(--s5) + var(--s4) + var(--sab,0px))}

/* ----- the foot of every page is one step below its last card -----
   The sweep at every size (SLACK) found desk pages ending 112px below the
   last card when the + in the corner needs 60 (44px, 16px up), the staff
   and field apps on a desk ending 64px down with nothing floating there
   at all, and an upright iPad ending 120px down with its + moved into the
   top bar. Each now ends 40px clear of whatever floats at its foot. */
@media (min-width:901px){
  html:not(#v2) .shell .page:not(.page .page){padding-bottom:100px}
  html:not(#v2) .appmain .page,html:not(#v2) .appmain .mob-body{padding-bottom:calc(var(--s5) + var(--s4))}
}
@media (min-width:721px) and (max-width:900px){
  html:not(#v2) .shell .page:not(.page .page){padding-bottom:calc(var(--s5) + var(--s4) + var(--sab,0px))}
}
/* the gap a card or list keeps below itself, when nothing follows it: Live
   operations' map card (24px), the yard's last task (12px), the staff
   app's list of jobs (24px) */
html:not(#v2) .page > :last-child,
html:not(#v2) .page > .stack:last-child > :last-child{margin-bottom:0 !important}
/* the client portal ends 40px below its footer line, not 64 */
@media (min-width:721px){
  html:not(#v2) .portal-body{padding-bottom:calc(var(--s5) + var(--s4))}
}
/* the field app on an upright iPad keeps its + above the tab bar, on its
   side, as it does on a phone held sideways (section 29), so the end of
   the list scrolls clear of the + as well as the bar. Clearing only the
   bar left a Route list 7px taller than its box (NUDGE) with its last
   card's end under the +. */
html[data-device="tablet"]:not(#v2) .phone .mob-body:has(~ .fab){padding-bottom:calc(156px + var(--sab,0px));scroll-padding-bottom:calc(156px + var(--sab,0px))}

/* ----- the client portal's tabs do not scroll up and down -----
   The open tab's underline hung 1px below the row, as the job flow tabs'
   did, so the row scrolled by a pixel (NUDGE). On a phone they are the
   bar along the bottom and have no underline. */
@media (min-width:721px){
  html:not(#v2) .portal-tabs{overflow-y:hidden}
  html:not(#v2) .ptab.on::after{bottom:0}
}

/* ----- a table a few pixels too wide fits instead of scrolling -----
   Surveys, a survey's elevations, a person's kit, a customer's quotes,
   Profit by town and the fleet's performance were 2 to 8px wider than
   their card, so each scrolled sideways by that much and wore the
   "there is more" fade for nothing. scrollHintMark (30-shell.js) gives a
   table that is within 16px of fitting .fit, which lets its text wrap. */
html:not(#v2) .tblwrap > .tbl.fit{min-width:0;width:100%}
html:not(#v2) .tblwrap > .tbl.fit :is(th,td):not(:first-child){padding-left:10px}
html:not(#v2) .tblwrap > .tbl.fit :is(th,td):not(:last-child){padding-right:10px}

/* ----- counts on the page's own tabs and buttons read at 4.5:1 -----
   White on the dark theme's coral was 2.6:1 ("42" on a person's tabs),
   white on the sky badge 4.1:1 ("1h 18m" on Planning), and dark on the
   light theme's gold 4.49:1. The sidebar's badges are the nav bar's and
   are left as they are. */
html:not([data-theme="light"]):not(#v2) .page .nav-badge:not(.amber):not(.sky){background:#D63C22}
html:not(#v2) .page .nav-badge.sky{background:var(--sky-700)}
html[data-theme="light"]:not(#v2) :is(.page .nav-badge.amber,.railcount){color:#1A1000}

/* ----- a ranked list of names reads across (Margin by work type) -----
   Each row is the whole name, a 10px bar from a square start to a 4px
   round end, and the figure at the row's end in figures that line up.
   The names are the second ink and the figures the first, never the
   bar's colour. The bars are measured against the largest. */
html:not(#v2) .hbars{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr) max-content;column-gap:14px;row-gap:12px;align-items:center;margin-top:4px}
html:not(#v2) .hb{display:grid;grid-column:1/-1;grid-template-columns:subgrid;align-items:center;min-height:20px}
html:not(#v2) .hb-k{font-size:var(--t-meta);color:var(--text-2);max-width:16ch;line-height:1.25;text-wrap:balance}
html:not(#v2) .hb-t{display:block;height:10px;min-width:0}
html:not(#v2) .hb-t > i{display:block;height:100%;min-width:2px;border-radius:0 4px 4px 0;background:var(--navy-700);transition:width .5s var(--ease),opacity var(--dur)}
html:not([data-theme="light"]):not(#v2) .hb-t > i{background:var(--sky-500)}
html:not(#v2) .hb:hover .hb-t > i{opacity:.82}
html:not(#v2) .hb-v{font-size:var(--t-meta);font-weight:var(--w-semi);color:var(--text);font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap}
/* Temp roof's colour is the ink: navy on the light theme, pale on the dark
   one. Its bar and the edge of its job cards were navy on navy (typeColour). */
html:not([data-theme="light"]):not(#v2){--wt-temproof:#CFD2F2}

/* ----- a job's stage rail never cuts a stage's name -----
   Thirteen stages share the page's width. On an upright iPad each had
   58px and "Paperwork" needs 60, so it read "Paperw..." (ELLIPSIS). Where
   the page is narrower than the rail needs, it is the strip a phone gets,
   which scrolls sideways and opens on the job's own stage (51d-steady.js). */
html:not(#v2) .jf-railwrap{container-type:inline-size}
@container (max-width:839px){
  html:not(#v2) .jf-rail{grid-template-columns:none;display:flex;overflow-x:auto;scrollbar-width:none;padding-top:0}
  html:not(#v2) .jf-rail li{min-width:74px;flex:0 0 auto}
  /* a strip that scrolls sideways clips at its top edge too, and each
     stage's dot sat across that edge, so only its lower half showed (on a
     phone since the strip was made). The strip keeps 6px above the dots
     and draws its line through them itself. */
  html:not(#v2) .jf-rail{padding-top:6px;border-top:0;background:linear-gradient(var(--rule),var(--rule)) 0 5px/100% 1px no-repeat}
}
html:not(#v2) .jf-rail::-webkit-scrollbar{display:none}

/* ----- Live operations' keys go two by two in a narrow card -----
   On Home on a laptop the card is half the page, and four keys across left
   "Inspection due" 57px of the 92 it needs (ELLIPSIS). */
html:not(#v2) .om-wrap{container-type:inline-size}
@container (max-width:859px){
  html:not(#v2) .om-keys{grid-template-columns:repeat(2,minmax(0,1fr))}
}
/* ----- a phone held sideways scrolls its last card clear of the bar -----
   Wider than 720px the bottom bar keeps the home indicator's room inside
   itself as well as below it, so the shell's room for the bar (96px and
   the indicator) falls short by the indicator, and with the page's foot
   at 8px (section 31) the last card ended 12px under the bar
   (mobiletest, landscape, 128 screens). The page adds the indicator back. */
@media (min-width:721px){
  html:not(#v2) .mshell .page{padding-bottom:calc(var(--s2) + var(--sab,0px))}
}

/* ----- Ask G answers in place (28 October, Ahmed's recording) -----
   Every press inside the panel rebuilt it: the scrim and the panel went
   to nothing for two frames and faded back, and the suggestions collapsed
   to a "Checking..." line on the way. The panel now stays (44-g.js
   paintBody): while G works, what was showing dims a little and the bar
   says what it is checking; the answer arrives in its place, rising from
   nine tenths, and the panel's height moves to fit it. */
html:not(#v2) .gpanel .gbusy{opacity:.5;pointer-events:none;transition:opacity .14s var(--ease)}
@keyframes gfresh{from{opacity:.35;transform:translateY(3px)}to{opacity:1;transform:none}}
html:not(#v2) .gpanel .gfresh{animation:gfresh .22s cubic-bezier(.22,.8,.24,1) both}
html:not(#v2) .gwrap .gpanel.gsizing{transition:transform .25s cubic-bezier(.16,1,.3,1),height .26s cubic-bezier(.22,.8,.24,1)}
@media (prefers-reduced-motion:reduce){
  html:not(#v2) .gpanel .gfresh{animation:none}
}

/* ----- a new screen is simply there (28 October, Ahmed's 15:44 recording) -----
   Each screen drew its first two frames at nine tenths and 4px low, then
   snapped to full: the snap read as the cards' highlights and shadows
   popping, and the 4px slide made every hairline shimmer on its way up.
   Panels further down did the same on the way into view (18px and a
   fade). A screen and its panels now appear in place, as a Mac app's
   sidebar changes its content. Nothing moves that you did not move. */
html:not(#v2) :is(.page,.stack).fade{animation:none}
/* the older entrance under it (14-art.css): every panel straight inside a
   page faded up from nothing over 6px, on every screen whose page is not
   marked .fade. It was what still flashed on most office screens (RAMS,
   Live operations, Fleet and PPE among them) after 28 October's midday fix. */
html:not(#v2) :is(.page,.appmain) > *{animation:none}
html:not(#v2) .rv,html:not(#v2) .rv.vis{opacity:1;translate:0 0;transition:none}

/* ----- the sidebar scrolls without a scrollbar (28 October, evening) -----
   Ahmed: the bar that showed down the sidebar's edge while it scrolled
   looked cheap. The list still scrolls by wheel, trackpad, touch and
   keyboard, and the fade over its last 28px (section 15) still says there
   is more below. The standard property covers Chrome, Firefox and Safari
   18.2 on; the WebKit pseudo-element covers older Safari. */
html:not(#v2) .side-nav{scrollbar-width:none;-ms-overflow-style:none}
html:not(#v2) .side-nav::-webkit-scrollbar{display:none;width:0;height:0;background:transparent}

/* ----- the sidebar as a drawer has a panel of its own (28 October, night) -----
   Ahmed, from his iPad held upright: the menu drawn straight over the
   cards. The sidebar's colour is transparent in V2 (--chrome above) and
   the glass behind it is drawn in section 1, above 900px only. At 900px
   and under the sidebar slides out as a drawer over the page, so in the
   dark theme nothing was behind it, and in the light theme it took the
   older navy chrome under navy type and could not be read. The drawer
   is now the sidebar's own glass, a step heavier because it lies over
   the page rather than the ground: the same edge, highlight and blur,
   12px in from the edges with the same 28px corners, and closed it
   slides all the way out, shadow and all. Found by SEETHRU in
   qa/designqa.mjs, which now opens the drawer at every size it has. */
@media (max-width:900px){
  html:not(#v2) .side{
    top:12px;left:12px;height:calc(100dvh - 24px);width:min(288px,calc(100vw - 48px));
    border-radius:28px;border:1px solid var(--glass-edge);
    background:rgba(21,18,90,.86);
    -webkit-backdrop-filter:blur(26px) saturate(150%);backdrop-filter:blur(26px) saturate(150%);
    box-shadow:var(--glass-hi),var(--sh3);
    transform:translateX(calc(-100% - 40px));
  }
  html:not(#v2) .side.open{transform:none}
  html:not(#v2) .side::after{display:none}
  html[data-theme="light"]:not(#v2) .side{
    background:rgba(255,255,255,.86);border-color:rgba(255,255,255,.9);
    -webkit-backdrop-filter:blur(28px) saturate(170%);backdrop-filter:blur(28px) saturate(170%);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.95),inset 0 0 0 1px rgba(14,11,92,.05),var(--sh3);
    color:var(--ink);
  }
  /* its close button is white by an inline style, which on white glass is a blank square */
  html[data-theme="light"]:not(#v2) .side .side-top .icobtn{color:#0E0B5C!important;background:rgba(14,11,92,.06);border-color:rgba(14,11,92,.10)}
}

/* ----- the G button keeps its shape (28 October, night) -----
   In the top bar it gave way to its neighbours: 18px wide by 38 tall
   on an upright iPad mini when the bar says "Unsaved changes", 31 on
   a bigger iPad. On a phone the 44px touch height (12-modules) made
   the 34px circle a capsule beside three 44px buttons. It no longer
   shrinks, and on a phone it is a 44px circle. Found by SQUASH. With a
   mouse in a window that narrow the bar's other buttons gave way too,
   down to 22px wide; now only the search field gives way, and its words
   end in "..." as they always did. */
html:not(#v2) .topbar .gbtn{flex:none}
html:not(#v2) .topbar > :is(.icobtn,.savechip,.pill){flex:none}
@media (min-width:721px){ html:not(#v2) .topbar .searchbar{flex:0 1 auto;min-width:0} }
@media (max-width:720px){
  html:not(#v2) .gbtn.mob{width:44px;height:44px;flex:none}
}

/* ----- the More menu's last card keeps its shadow (28 October, night) -----
   For the yard the More menu is long enough to scroll, and its scroll ended
   16px under the save card at its foot, which cut the lower two thirds off
   the card's shadow (0 14px 34px). It ends 48px under it now. Found by
   SHADOW when the sweep began opening the More menu. */
html:not(#v2) .sheet-body .more-save{margin-bottom:32px}

/* ----- touch targets the phone and iPad audit found (28 October, night) -----
   qa/phoneqa.mjs at five phone widths, sideways and five iPad sizes:
   - The G button on a phone held sideways was 34px beside 44px buttons:
     the phone's rules are written for 720px and under, and a phone on its
     side is wider than that. It is 44 there too.
   - A switch was 38px wide to a finger on a phone and 38 by 21 on its
     side and on an iPad. Wherever the pointer is a finger it now reaches
     44 by 44 and looks as it did: the reach is padding outside the painted
     track (background-clip), taken back by a negative margin, so nothing
     around it moves (13-devices does the same upright on a phone). The
     knob is placed against the padding box, so it moves in by the padding.
     Its glow follows the track (a drop shadow of what is painted) rather
     than the reach, which on a phone had drawn it as a tall oval.
   - On an iPad the fields were 15px: the touch size of 17 (the type
     system's "17 on a touch screen") was written for phones, upright and
     on their side, and an iPad is neither. Safari on an iPhone zooms the
     page into any field under 16px when it is pressed. Every touch screen
     now gets 17.
   - On an iPad the G button was 38px beside 44px buttons, and the round
     button that opens and closes the sidebar was 28. The G button is 44
     on a touch screen; the sidebar's button keeps its 28 and reaches 44
     through an invisible ring (its ::after).
   - On an upright iPad the search field gives way to the buttons, and its
     "⌘K" hint was cut to "...". The hint is not shown at 900px and under.
   - The checkboxes in a grid (who is told about what, in Settings) were
     13px on an iPad and on a phone on its side; a phone upright has had
     24 since 27 October (13-devices). Every touch screen has 24 now, in a
     row 44 tall. */
html[data-device="phone"]:not(#v2) .gbtn.mob{width:44px;height:44px;flex:none}
@media (pointer:coarse){
  html:not(#v2) .switch{box-sizing:border-box;width:44px;height:44px;padding:11.5px 3px;
    background-clip:content-box;margin:-11.5px -3px}
  html:not(#v2) .switch::after{top:14px;left:5.5px}
  html:not(#v2) .switch.on{box-shadow:none;filter:drop-shadow(0 0 5px rgba(56,201,143,.35))}
  :where(html:not(#v2)) :is(input,select,textarea,.inp,.sel){font-size:var(--t-input)}
  :where(html:not(#v2)) :is(.pinbody .inp,.pinbody .num,#pinentry){font-size:var(--t-figure)}
  html:not(#v2) .topbar .gbtn{width:44px;height:44px}
  html:not(#v2) .side-toggle::after{content:"";position:absolute;inset:-9px;border-radius:50%}
  :where(html:not(#v2)) :is(td.r > input[type=checkbox],td.r > input[type=radio]){width:24px;height:24px;margin:0}
  :where(html:not(#v2)) td.r:has(> input:is([type=checkbox],[type=radio])){height:44px}
}
@media (max-width:900px){
  html:not(#v2) .topbar .searchbar .kbd{display:none}
}

/* ---------- 33. WINDOWS, CHROMEBOOKS, AND A PHONE ON ITS SIDE (29 October) ----------
   Ahmed: "This whole app has to also run seamlessly on chrome books and
   windows." Checked as Chrome draws it there: their user agent, none of the
   faces ahead of the embedded DM Sans, real scrollbars, 100% to 150%
   scaling (designqa sizes cb1366, win1536, win1280, win1093, cbtouch).

   The phone's chrome wider than 720px. A phone on its side, and any window
   under 520px tall and 1100px wide (a short laptop window), is stamped a
   phone and draws the phone's top bar and tab bar, but the rules that go
   with that chrome were written for 720px and under. On an iPhone held
   sideways (844 by 390) FIXOVER found the add button on the tab bar's last
   tab (52 by 52px; the + is in the phone's top bar already), notices came
   up 24px from the foot, behind the tab bar, and SEETHRU found the top bar
   still clear once the page scrolled under it. The tab bar keeps its own
   shape here. */
@media (min-width:721px){
  html[data-device="phone"]:not(#v2) .fab{display:none}
  html[data-device="phone"]:not(#v2) #toasts{bottom:calc(96px + var(--sab))}
  html.scrolled[data-device="phone"]:not(#v2) .mtop{background:rgba(10,8,64,.78);-webkit-backdrop-filter:blur(22px) saturate(150%);backdrop-filter:blur(22px) saturate(150%);box-shadow:0 1px 0 rgba(255,255,255,.08)}
  html[data-theme="light"][data-device="phone"]:not(#v2) .mtop{background:#0E0B5C!important;-webkit-backdrop-filter:none;backdrop-filter:none}
  html[data-theme="light"][data-device="phone"]:not(#v2) .mtop :is(.icobtn,.mtop-btn,button){background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.18);color:#fff}
  html[data-theme="light"][data-device="phone"]:not(#v2) .mtop .mtop-title{color:#fff}
}

/* The closed drawer leaves the keyboard's path. At 900px and under the
   sidebar is a drawer parked off the left edge, and its links still took
   the focus: Tab walked through 25 of them out of sight before reaching
   the page (OFFFOCUS, 800 by 640 and an iPad mini), and a screen reader read
   them. Hidden half a second after it closes, by an animation rather than
   a transition, so the drawer's own slide is untouched; opening shows it
   at once. */
@keyframes side-away{to{visibility:hidden}}
@media (max-width:900px){
  html:not(#v2) .side:not(.open){animation:side-away 0s linear .5s forwards}
}

/* Windows contrast themes (forced colours). Windows repaints the page in
   the user's own colours and drops the author's fills, so anything that
   said "on" or "chosen" with a fill alone said nothing: every switch
   vanished, track and knob both being fills, and a chosen chip, tab or
   segment looked like the rest. The logo, a white fill through a mask,
   went black on black, and icons given the brand navy by name stayed navy
   on black (the white buttons' icons, the add button's +). Only inside
   forced colours; nothing else changes. */
@media (forced-colors:active){
  html:not(#v2) .wlogo{forced-color-adjust:none;background-color:CanvasText}
  html:not(#v2) svg.ic{color:inherit!important}
  html:not(#v2) .switch{forced-color-adjust:none;background-color:GrayText!important}
  html:not(#v2) .switch::after{background-color:Canvas!important}
  html:not(#v2) .switch.on{background-color:Highlight!important}
  html:not(#v2) .switch.on::after{background-color:HighlightText!important}
  html:not(#v2) :is(.chip,.ptab,.railtab,.mbtab,.nav-item,.jf-st,.om-key,.seg button,.tabs button,.tabs a).on{
    forced-color-adjust:none;background-color:Highlight!important;color:HighlightText!important;border-color:Highlight!important}
  /* their glass decorations would paint over that highlight; the invisible
     touch bands they carry stay */
  html:not(#v2) :is(.chip,.ptab,.railtab,.mbtab,.nav-item,.jf-st,.om-key,.seg button,.tabs button,.tabs a).on::before,
  html:not(#v2) :is(.chip,.ptab,.railtab,.mbtab,.nav-item,.jf-st,.om-key,.seg button,.tabs button,.tabs a).on::after{
    background:none!important;box-shadow:none!important;border-color:transparent!important}
}

/* ---------- 34. AN INSIGHT'S FIGURE (29 October) ----------
   Ahmed, on Growth: "these font sizes here do not look polished, it does
   not look like something apple would produce". Each insight had two
   headlines: the 17px semibold claim, and 3px under it the figure line,
   19px semibold blue on a line-height of 1, number and words one size
   (OUTRANK, on every insight). Now the claim is the only headline; the
   figure is a figure (24px, the figure face, white) with its words small
   and quiet beside it on the same baseline, 8px under the claim; the why
   is 10px further down. Same order as before: the claim, the figure, the
   why, the way in. */
html:not(#v2) .insight > .spread{gap:8px}
html:not(#v2) .insight .insight-stat.ins-figs{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:8px;row-gap:4px;
  font-size:var(--t-meta);line-height:1;color:var(--ink-3)}
html:not(#v2) .insight .ins-v{font-family:var(--font-fig);font-size:var(--t-figure-sm);font-weight:var(--w-semi);
  letter-spacing:var(--ls-fig);line-height:1;color:var(--ink);font-variant-numeric:tabular-nums}
html:not(#v2) .insight .ins-l{font-size:var(--t-meta);font-weight:var(--w-med);color:var(--ink-3);line-height:var(--lh-ui)}
html:not(#v2) .insight .ins-sep{color:var(--ink-3);opacity:.6;padding:0 4px}

/* ---------- 35. ARRIVALS (7 November) ----------
   Ahmed sent a recording of a dashboard whose screens build themselves:
   the app opening out of a circle after sign-in, cards arriving one after
   another out of a blur, figures rolling into place like a counter, bars
   growing, the page softening as the next one is pressed. "Is it possible
   for us to add this to the current app ... make sure none of this breaks
   the app." 51e-arrive.js does it; this section holds the two things it
   cannot do from script.

   A rolling figure is drawn by an overlay of digit strips over the real
   figure, whose own text is never changed, so anything that reads the
   page reads the real number at every frame. The strips are written as
   generated content (attr(data-s)), because a text node inside the figure
   would be read as part of its number. Each strip is one column of one
   glyph, measured from the figure itself. */
html:not(#v2) :is(wos-odo,wos-col,wos-strip){display:block}
html:not(#v2) wos-strip::before{content:attr(data-s);white-space:pre}

/* The sign-in opening. The browser holds a picture of the code screen and
   the new screen grows out of the tick over it (51e-arrive.js sets where
   the circle starts and how far it goes), coming forward as it opens
   while the code screen steps back, as an iPhone opens an app. Nothing
   else on the page uses a view transition: everything else changes in
   place, in the same task. */
html.wos-open:not(#v2)::view-transition-group(root){animation:none}
html.wos-open:not(#v2)::view-transition-old(root){mix-blend-mode:normal;
  animation:wos-back var(--wos-open-ms,1050ms) cubic-bezier(.4,0,.2,1) both}
html.wos-open:not(#v2)::view-transition-new(root){mix-blend-mode:normal;
  animation:wos-open var(--wos-open-ms,1050ms) cubic-bezier(.55,0,.15,1) both}
@keyframes wos-open{
  from{clip-path:circle(0px at var(--wos-cx,50%) var(--wos-cy,40%));transform:scale(1.07);transform-origin:var(--wos-cx,50%) var(--wos-cy,40%)}
  to{clip-path:circle(var(--wos-r,150vmax) at var(--wos-cx,50%) var(--wos-cy,40%));transform:scale(1);transform-origin:var(--wos-cx,50%) var(--wos-cy,40%)}
}
@keyframes wos-back{
  from{transform:scale(1);opacity:1;transform-origin:var(--wos-cx,50%) var(--wos-cy,40%)}
  to{transform:scale(.93);opacity:.35;transform-origin:var(--wos-cx,50%) var(--wos-cy,40%)}
}

/* =========================================================
   36. THE CODE SCREEN HAS ROOM (7 November)
   Ahmed, from the first look at the arrivals: "the enter your code page
   is soo cramped ... give things space man!!!! if that means making
   things a bit smaller than so be it!!" Sections 19 and 24 fitted the
   screen to the window by giving up the air first; this does it the
   other way round. The things are smaller (the mark 40, the title 31,
   the boxes 42 by 50, the keys 44 under a cursor and 56 under a finger)
   and the air between the groups is the thing that stays: the head, 32
   to the card; inside the card the lock and the boxes, the message, the
   keys, the button, the links, each group its own step apart. As a
   window gets shorter the things shrink first and the air last.
   Selectors carry #app so they sit over every earlier layer without
   !important, apart from the mark, which is sized inline (20-util.js
   LOGO). Measured with qa/gatespec.mjs and qa/gatefit.mjs.
   ========================================================= */
html:not(#v2) #app .pinwrap .pinhead .pin-eyebrow{font-size:var(--step--1);letter-spacing:var(--ls-0)}
html:not(#v2) #app .pinwrap .pinhead .pin-title{line-height:1.14;letter-spacing:-.01em}
html:not(#v2) #app .pinwrap .pinboxes{gap:10px;margin:0;width:auto}
html:not(#v2) #app .pinwrap .pinbox{width:42px;height:50px;border-radius:12px}
html:not(#v2) #app .pinwrap .pinbox b{font-size:var(--step-4)}
html:not(#v2) #app .pinwrap .pinbox i{bottom:8px;width:12px}
html:not(#v2) #app .pinwrap .pinmsg{min-height:22px;margin:14px 0}
html:not(#v2) #app .pinwrap .pinkey{font-size:var(--step-4)}
html:not(#v2) #app .pinwrap .pinfoot{font-size:var(--step--1)}
html:not(#v2) #app .pinwrap .pinfoot .lnk{font-size:inherit}
html:not(#v2) #app .pinwrap .demoway{margin-top:12px}

/* a laptop, a desk, an iPad: one column, centred in the window */
@media (min-width:721px){
  html:not(#v2) #app > .pinwrap{padding:var(--s6)}
  html:not(#v2) #app .pinwrap .pinhead{max-width:400px;padding:0 0 32px}
  html:not(#v2) #app .pinwrap .pinhead .logo.lg .wlogo{height:40px!important;width:151px!important}
  html:not(#v2) #app .pinwrap .pinhead .pin-eyebrow{margin-top:26px}
  html:not(#v2) #app .pinwrap .pinhead .pin-title{margin-top:6px;font-size:var(--step-6)}
  html:not(#v2) #app .pinwrap .pinhead .pin-lede{margin-top:10px;font-size:var(--step-0)}
  html:not(#v2) #app .pinwrap .pinbody{max-width:400px;padding:32px 36px 28px}
  html:not(#v2) #app .pinwrap :is(.pinbadge,.pinring){width:48px;height:48px}
  html:not(#v2) #app .pinwrap .pinbadge{margin:0 auto 20px}
  html:not(#v2) #app .pinwrap .pinpad{max-width:276px;gap:12px 14px}
  html:not(#v2) #app .pinwrap .pinkey{height:44px}
  html:not(#v2) #app .pinwrap .pinverify{max-width:276px;height:46px;margin-top:24px}
  html:not(#v2) #app .pinwrap .pinfoot{margin-top:20px}
}
@media (min-width:721px) and (max-height:840px){
  html:not(#v2) #app > .pinwrap{padding:20px var(--s6)}
  html:not(#v2) #app .pinwrap .pinhead{padding-bottom:26px}
  html:not(#v2) #app .pinwrap .pinhead .logo.lg .wlogo{height:36px!important;width:136px!important}
  html:not(#v2) #app .pinwrap .pinhead .pin-eyebrow{margin-top:20px}
  html:not(#v2) #app .pinwrap .pinbody{padding:26px 36px 22px}
  html:not(#v2) #app .pinwrap :is(.pinbadge,.pinring){width:44px;height:44px}
  html:not(#v2) #app .pinwrap .pinbadge{margin-bottom:18px}
  html:not(#v2) #app .pinwrap .pinmsg{margin:12px 0}
  html:not(#v2) #app .pinwrap .pinkey{height:40px}
  html:not(#v2) #app .pinwrap .pinverify{height:44px;margin-top:20px}
  html:not(#v2) #app .pinwrap .pinfoot{margin-top:16px}
}
/* an iPad on its side (1024 by 768) or a touch laptop: its keys keep a
   finger's size below, so the air gives a little sooner than on a desk
   (the shorter steps that follow still win where they apply) */
@media (min-width:721px) and (max-height:800px) and (pointer:coarse){
  html:not(#v2) #app > .pinwrap{padding:14px var(--s6)}
  html:not(#v2) #app .pinwrap .pinhead{padding-bottom:20px}
  html:not(#v2) #app .pinwrap .pinbody{padding:22px 32px 18px}
  html:not(#v2) #app .pinwrap .pinbadge{margin-bottom:14px}
}
@media (min-width:721px) and (max-height:740px){
  html:not(#v2) #app > .pinwrap{padding:14px var(--s6)}
  html:not(#v2) #app .pinwrap .pinhead{padding-bottom:20px}
  html:not(#v2) #app .pinwrap .pinhead .logo.lg .wlogo{height:32px!important;width:121px!important}
  html:not(#v2) #app .pinwrap .pinhead .pin-eyebrow{margin-top:16px}
  html:not(#v2) #app .pinwrap .pinhead .pin-title{font-size:var(--step-5)}
  html:not(#v2) #app .pinwrap .pinbody{padding:22px 32px 18px}
  html:not(#v2) #app .pinwrap :is(.pinbadge,.pinring){width:40px;height:40px}
  html:not(#v2) #app .pinwrap .pinbadge{margin-bottom:14px}
  html:not(#v2) #app .pinwrap .pinbox{height:46px}
  html:not(#v2) #app .pinwrap .pinmsg{margin:10px 0}
  html:not(#v2) #app .pinwrap .pinpad{gap:10px 14px}
  html:not(#v2) #app .pinwrap .pinkey{height:38px;font-size:var(--step-3)}
  html:not(#v2) #app .pinwrap .pinverify{height:42px;margin-top:16px}
  html:not(#v2) #app .pinwrap .pinfoot{margin-top:14px}
}
/* a 1366 by 768 laptop's browser window (about 625 to 660 tall, the usual
   Windows laptop and Chromebook): one step further, the things first */
@media (min-width:721px) and (max-height:680px){
  html:not(#v2) #app > .pinwrap{padding:10px var(--s6)}
  html:not(#v2) #app .pinwrap .pinhead{padding-bottom:16px}
  html:not(#v2) #app .pinwrap .pinhead .logo.lg .wlogo{height:28px!important;width:106px!important}
  html:not(#v2) #app .pinwrap .pinhead .pin-eyebrow{margin-top:12px}
  html:not(#v2) #app .pinwrap .pinbody{padding:18px 32px 14px}
  html:not(#v2) #app .pinwrap :is(.pinbadge,.pinring){width:36px;height:36px}
  html:not(#v2) #app .pinwrap .pinbadge{margin-bottom:12px}
  html:not(#v2) #app .pinwrap .pinbox{height:44px}
  html:not(#v2) #app .pinwrap .pinmsg{margin:8px 0}
  html:not(#v2) #app .pinwrap .pinpad{gap:8px 12px}
  html:not(#v2) #app .pinwrap .pinkey{height:34px}
  html:not(#v2) #app .pinwrap .pinverify{height:40px;margin-top:14px}
  html:not(#v2) #app .pinwrap .pinfoot{margin-top:12px}
}
@media (min-width:721px) and (max-height:640px){
  html:not(#v2) #app > .pinwrap{padding:6px var(--s6)}
  html:not(#v2) #app .pinwrap .pinhead{padding-bottom:12px}
  html:not(#v2) #app .pinwrap .pinhead .pin-eyebrow{margin-top:10px}
  html:not(#v2) #app .pinwrap .pinhead .pin-title{margin-top:4px}
  html:not(#v2) #app .pinwrap .pinhead .pin-lede{margin-top:6px}
  html:not(#v2) #app .pinwrap .pinbody{padding:16px 32px 12px}
  html:not(#v2) #app .pinwrap .pinbadge{margin-bottom:10px}
  html:not(#v2) #app .pinwrap .pinmsg{margin:6px 0}
  html:not(#v2) #app .pinwrap .pinkey{height:32px}
  html:not(#v2) #app .pinwrap .pinverify{height:38px;margin-top:12px}
  html:not(#v2) #app .pinwrap .pinfoot{margin-top:10px}
}
/* an iPad or a touch laptop: the keys keep a finger's size */
@media (min-width:721px) and (pointer:coarse){
  html:not(#v2) #app .pinwrap .pinkey{height:56px}
  html:not(#v2) #app .pinwrap .pinpad{max-width:300px}
  html:not(#v2) #app .pinwrap .pinverify{max-width:300px;height:48px}
}
@media (min-width:721px) and (pointer:coarse) and (max-height:840px){
  html:not(#v2) #app .pinwrap .pinkey{height:48px}
}
@media (min-width:721px) and (pointer:coarse) and (max-height:800px){
  html:not(#v2) #app .pinwrap .pinkey{height:44px}
  html:not(#v2) #app .pinwrap .pinverify{height:44px}
}
@media (min-width:721px) and (pointer:coarse) and (max-height:680px){
  html:not(#v2) #app .pinwrap .pinkey{height:42px}
  html:not(#v2) #app .pinwrap .pinverify{height:42px}
  html:not(#v2) #app .pinwrap .pinpad{gap:6px 12px}
}

/* a phone: the head on the navy, the card under it to the foot */
@media (max-width:720px){
  html:not(#v2) #app > .pinwrap{padding-top:0;padding-bottom:0}
  html:not(#v2) #app .pinwrap .pinhead{padding:calc(26px + var(--sat)) 24px 24px}
  html:not(#v2) #app .pinwrap .pinhead .logo.lg .wlogo{height:34px!important;width:128px!important}
  html:not(#v2) #app .pinwrap .pinhead .pin-eyebrow{margin-top:20px}
  html:not(#v2) #app .pinwrap .pinhead .pin-title{margin-top:6px;font-size:var(--step-5)}
  html:not(#v2) #app .pinwrap .pinhead .pin-lede{margin-top:8px;font-size:var(--step--1);max-width:30em}
  html:not(#v2) #app .pinwrap .pinbody{padding:28px 24px calc(22px + var(--sab))}
  html:not(#v2) #app .pinwrap :is(.pinbadge,.pinring){width:44px;height:44px}
  html:not(#v2) #app .pinwrap .pinbadge{margin:0 auto 18px}
  html:not(#v2) #app .pinwrap .pinbox{width:40px}
  html:not(#v2) #app .pinwrap .pinboxes{gap:9px}
  html:not(#v2) #app .pinwrap .pinpad{max-width:300px;gap:12px 14px}
  html:not(#v2) #app .pinwrap .pinkey{height:56px;font-size:var(--step-5)}
  html:not(#v2) #app .pinwrap .pinverify{max-width:300px;height:48px;margin-top:22px}
  html:not(#v2) #app .pinwrap .pinfoot{margin-top:18px}
}
/* the two links share a line where the line has room for both */
@media (min-width:361px) and (max-width:720px){
  html:not(#v2) #app .pinwrap .pinfoot{display:flex;flex-direction:row;justify-content:center;align-items:center;flex-wrap:wrap;gap:0}
  html:not(#v2) #app .pinwrap .pinfoot-dot{display:inline;margin:0 10px}
  html:not(#v2) #app .pinwrap .pinfoot .lnk{padding:6px 0;white-space:nowrap}
  /* anything else in the foot (the line that says nothing is checked by a
     server, on a board that is not live) takes a line of its own */
  html:not(#v2) #app .pinwrap .pinfoot > :not(.lnk,.pinfoot-dot){flex:0 0 100%}
}
@media (max-width:720px) and (max-height:780px){
  html:not(#v2) #app .pinwrap .pinhead{padding:calc(22px + var(--sat)) 24px 20px}
  html:not(#v2) #app .pinwrap .pinhead .logo.lg .wlogo{height:30px!important;width:113px!important}
  html:not(#v2) #app .pinwrap .pinhead .pin-eyebrow{margin-top:16px}
  html:not(#v2) #app .pinwrap .pinhead .pin-title{font-size:var(--step-4)}
  html:not(#v2) #app .pinwrap .pinbody{padding:22px 24px calc(18px + var(--sab))}
  html:not(#v2) #app .pinwrap :is(.pinbadge,.pinring){width:38px;height:38px}
  html:not(#v2) #app .pinwrap .pinbadge{margin-bottom:14px}
  html:not(#v2) #app .pinwrap .pinbox{height:46px}
  html:not(#v2) #app .pinwrap .pinmsg{margin:10px 0}
  html:not(#v2) #app .pinwrap .pinpad{gap:10px 14px}
  html:not(#v2) #app .pinwrap .pinkey{height:50px}
  html:not(#v2) #app .pinwrap .pinverify{height:46px;margin-top:16px}
  html:not(#v2) #app .pinwrap .pinfoot{margin-top:12px}
}
@media (max-width:720px) and (max-height:680px){
  html:not(#v2) #app .pinwrap .pinhead{padding:calc(16px + var(--sat)) 20px 16px}
  html:not(#v2) #app .pinwrap .pinhead .logo.lg .wlogo{height:26px!important;width:98px!important}
  html:not(#v2) #app .pinwrap .pinhead .pin-eyebrow{margin-top:12px}
  html:not(#v2) #app .pinwrap .pinhead .pin-title{font-size:var(--step-3)}
  html:not(#v2) #app .pinwrap .pinhead .pin-lede{margin-top:6px}
  html:not(#v2) #app .pinwrap .pinbody{padding:18px 20px calc(14px + var(--sab))}
  html:not(#v2) #app .pinwrap :is(.pinbadge,.pinring){width:34px;height:34px}
  html:not(#v2) #app .pinwrap .pinbadge{margin-bottom:12px}
  html:not(#v2) #app .pinwrap .pinbox{height:44px}
  html:not(#v2) #app .pinwrap .pinmsg{min-height:20px;margin:8px 0}
  html:not(#v2) #app .pinwrap .pinpad{gap:9px 14px}
  html:not(#v2) #app .pinwrap .pinkey{height:46px;font-size:var(--step-4)}
  html:not(#v2) #app .pinwrap .pinverify{height:44px;margin-top:14px}
  html:not(#v2) #app .pinwrap .pinfoot{margin-top:10px}
  html:not(#v2) #app .pinwrap .demoway{margin-top:8px}
}

/* ---------- 37. SHINE, AND THE PHONE THE APPLE WAY (7 November, evening) ----------
   Ahmed: "all the boxes on the app, that the figures and text go into, and
   the side nav bar, they look a bit dull in terms of the colour, it looks
   washed, I need them to have a bit more of a shinier look and bounce to
   them", and, with a screenshot of the Apple Store app: "Also look at the
   Apple lay out for mobile!!"
   The boxes were one flat mid navy (#15125A) under a 5% top sheen and a
   10% rim, on a ground of nearly the same blue, so they read grey. Now a
   box is the brand blue with more colour in it, lit from its top left: a
   brighter sheen down its top third, a soft blue bloom in the corner the
   light comes from, a rim that catches the light along its top edge, and
   a deeper shadow under it, so it sits up off the ground. The sidebar is
   the same glass, and its pill is the brightest glass on the screen. On a
   phone the tab bar is Apple's floating glass bar: lighter, clearer glass
   with a bright rim, and the current tab in its own glass pill, its icon
   and name in the interactive blue. Colour and light only: no size, no
   space, nothing moved or removed. Dark theme (the one the app opens in);
   the light theme is as it was. The bounce is in 51-motion.js and
   51b-sidebar.js (the lift and the pill). */
html[data-theme="dark"]:not(#v2){
  --surface:#1A1674; --surface-2:#221E82; --surface-3:#2A2690;
  --glass-edge:rgba(255,255,255,.16);
  --glass-hi:inset 0 1px 0 rgba(255,255,255,.30),inset 1px 0 0 rgba(255,255,255,.05);
  --pane:linear-gradient(180deg,rgba(255,255,255,.10) 0%,rgba(255,255,255,.035) 32%,rgba(255,255,255,0) 60%),
         radial-gradient(120% 90% at 0% 0%,rgba(122,146,255,.20) 0%,rgba(122,146,255,0) 58%);
  --sh2:0 16px 40px rgba(3,2,30,.44),0 2px 6px rgba(3,2,30,.26);
  --glass:rgba(32,28,124,.66);
}
/* the boxes that had no sheen of their own get the same one */
html[data-theme="dark"]:not(#v2) :is(.tile,.att,.moneyc){background-image:var(--pane);box-shadow:var(--glass-hi),0 10px 26px rgba(3,2,30,.34)}
html[data-theme="dark"]:not(#v2) .rail > .tile{background-image:none;box-shadow:none}
/* a box under the pointer brightens as it lifts */
html[data-theme="dark"]:not(#v2) :is(.card.click,.card.hov,.jcard,.tile.go,button.tile,.att,.moneyc,.today > .today-c):hover{
  border-color:rgba(255,255,255,.28);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.40),inset 1px 0 0 rgba(255,255,255,.07),0 22px 48px rgba(3,2,30,.50),0 0 0 1px rgba(122,146,255,.10)}
/* the sidebar: the same lit glass, and its pill the brightest glass there is */
@media (min-width:901px){
  html[data-theme="dark"]:not(#v2) .side{
    background:linear-gradient(180deg,rgba(46,42,164,.72) 0%,rgba(28,24,118,.68) 40%,rgba(18,15,88,.72) 100%);
    border-color:rgba(255,255,255,.17);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.30),inset 1px 0 0 rgba(255,255,255,.05),var(--sh2);
  }
}
html[data-theme="dark"]:not(#v2) :is(.nav-glide,.nav-glide-was){
  background:linear-gradient(180deg,rgba(255,255,255,.26) 0%,rgba(255,255,255,.12) 100%);
  border-color:rgba(255,255,255,.30);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.45),0 8px 22px rgba(3,2,30,.32),0 0 22px rgba(108,184,255,.16);
}
html[data-theme="dark"]:not(#v2) .side-nav .nav-item.on .ic{color:var(--int-hi)}
/* the phone: Apple's floating glass tab bar */
@media (max-width:720px){
  html[data-theme="dark"]:not(#v2) :is(.mbar,.mob-tabs){
    background:linear-gradient(180deg,rgba(62,58,178,.46) 0%,rgba(26,22,108,.58) 100%);
    border:1px solid rgba(255,255,255,.22);
    -webkit-backdrop-filter:blur(24px) saturate(190%);backdrop-filter:blur(24px) saturate(190%);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.36),inset 0 -1px 0 rgba(255,255,255,.06),0 16px 40px rgba(3,2,30,.50);
  }
  html[data-theme="dark"]:not(#v2) :is(.mbtab,.mtab).on{color:var(--int-hi)}
  /* the office phone's current tab was marked by a 2px line along its top
     (the older line rule's height outlived the pill that replaced it);
     like the field app's, it is the pill now, Apple's glass capsule */
  html[data-theme="dark"]:not(#v2) :is(.mbtab,.mtab).on::before{
    content:"";display:block;position:absolute;inset:4px 4px;height:auto;width:auto;z-index:-1;
    background:linear-gradient(180deg,rgba(255,255,255,.22) 0%,rgba(255,255,255,.10) 100%);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.34),0 4px 14px rgba(3,2,30,.28);border-radius:var(--r-pill)}
  html[data-theme="dark"]:not(#v2) .ptab.on::before{
    background:linear-gradient(180deg,rgba(255,255,255,.22) 0%,rgba(255,255,255,.10) 100%);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.34);border-radius:var(--r-pill)}
  /* chips are Apple's raised capsules */
  html[data-theme="dark"]:not(#v2) .chip:not(.on){background:linear-gradient(180deg,rgba(255,255,255,.12),rgba(255,255,255,.06));
    border-color:rgba(255,255,255,.16);box-shadow:inset 0 1px 0 rgba(255,255,255,.22)}
}
/* Section 11 again, after the glass above: Reduce Transparency and More
   Contrast get solid panels, whatever the shine */
@media (prefers-reduced-transparency:reduce){
  html:not(#v2) :is(.side,.mbar,.mob-tabs){-webkit-backdrop-filter:none;backdrop-filter:none}
  html[data-theme="dark"]:not(#v2) :is(.side,.mbar,.mob-tabs){background:#15125A}
}
@media (prefers-contrast:more){
  html[data-theme="dark"]:not(#v2){--glass-edge:rgba(255,255,255,.4)}
  html:not(#v2) :is(.side,.mbar,.mob-tabs){-webkit-backdrop-filter:none;backdrop-filter:none}
  html[data-theme="dark"]:not(#v2) :is(.side,.mbar,.mob-tabs){background:#15125A}
}

/* ---------- 38. THE GLASS LENS, AND GLASS LIKE IT (7 November, night) ----------
   Ahmed, 19:17, with a recording of Instagram's tab bar on an iPhone:
   "Look at the style I want! Especially for the mobile version, this is
   how the mobile nav bar should be and similar elements on the app".
   The tab bar is clearer glass, light catching its rim along the top and
   the bottom, and the open tab sits in a lens of lighter glass that
   slides from tab to tab and can be drawn along the bar (51b-sidebar.js).
   The round buttons in the phone's top bar are the same glass. Colour
   and light only: nothing is moved, resized or removed, and with no
   script the open tab keeps the capsule of section 37. */
html[data-theme="dark"]:not(#v2) :is(.mbar,.mob-tabs).has-lens{
  background:linear-gradient(180deg,rgba(40,36,140,.28) 0%,rgba(12,10,56,.56) 100%);
  border-color:rgba(255,255,255,.12);
  -webkit-backdrop-filter:blur(20px) saturate(170%);backdrop-filter:blur(20px) saturate(170%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.34),inset 0 -1px 0 rgba(255,255,255,.10),0 18px 44px rgba(3,2,30,.50);
}
/* the rim: brightest where the light comes in, top left, and again at the far bottom edge */
html:not(#v2) :is(.mbar,.mob-tabs).has-lens::after{
  content:"";position:absolute;inset:-1px;border-radius:inherit;padding:1px;pointer-events:none;z-index:2;
  background:linear-gradient(165deg,rgba(255,255,255,.62) 0%,rgba(255,255,255,.12) 26%,rgba(255,255,255,.04) 58%,rgba(255,255,255,.30) 100%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;
}
html[data-theme="light"]:not(#v2) :is(.mbar,.mob-tabs).has-lens::after{
  background:linear-gradient(165deg,rgba(255,255,255,.95) 0%,rgba(255,255,255,.4) 30%,rgba(14,11,92,.06) 60%,rgba(255,255,255,.7) 100%)}
/* the lens */
html:not(#v2) :is(.mbar,.mob-tabs) > .tab-lens{
  position:absolute;left:0;top:0;width:0;height:0;margin:0;padding:0;border:0;display:block;
  border-radius:var(--r-pill);pointer-events:none;z-index:0;transform-origin:50% 50%;
  background:linear-gradient(180deg,rgba(255,255,255,.24) 0%,rgba(255,255,255,.11) 100%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.48),inset 0 -1px 0 rgba(255,255,255,.10),0 6px 18px rgba(3,2,30,.26);
  transition:opacity .2s var(--ease);
}
html[data-theme="light"]:not(#v2) :is(.mbar,.mob-tabs) > .tab-lens{
  background:linear-gradient(180deg,rgba(14,11,92,.10) 0%,rgba(14,11,92,.05) 100%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.9),0 4px 12px rgba(14,11,92,.10)}
html:not(#v2) :is(.mbar,.mob-tabs).has-lens > :is(.mbtab,.mtab){z-index:1}
html:not(#v2) :is(.mbar,.mob-tabs).has-lens > :is(.mbtab,.mtab).on::before{opacity:0}
/* a finger drawn along the bar is the lens's, not a scroll's */
html:not(#v2) :is(.mbar,.mob-tabs).has-lens,html:not(#v2) :is(.mbar,.mob-tabs).has-lens > *{touch-action:pan-y}
/* the phone's round top bar buttons, the same glass */
@media (max-width:720px){
  html[data-theme="dark"]:not(#v2) .mtop .icobtn{
    background:linear-gradient(180deg,rgba(255,255,255,.16) 0%,rgba(255,255,255,.06) 100%);
    border-color:rgba(255,255,255,.20);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.38),inset 0 -1px 0 rgba(255,255,255,.08),0 6px 16px rgba(3,2,30,.30)}
}
html[data-theme="dark"][data-device="phone"]:not(#v2) .mtop .icobtn{
  background:linear-gradient(180deg,rgba(255,255,255,.16) 0%,rgba(255,255,255,.06) 100%);
  border-color:rgba(255,255,255,.20);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.38),inset 0 -1px 0 rgba(255,255,255,.08),0 6px 16px rgba(3,2,30,.30)}
/* Section 11 again: Reduce Transparency and More Contrast get a solid bar
   and a lens that is a plain lighter shape */
@media (prefers-reduced-transparency:reduce){
  html:not(#v2) :is(.mbar,.mob-tabs).has-lens{-webkit-backdrop-filter:none;backdrop-filter:none}
  html[data-theme="dark"]:not(#v2) :is(.mbar,.mob-tabs).has-lens{background:#15125A}
  html[data-theme="dark"]:not(#v2) :is(.mbar,.mob-tabs) > .tab-lens{background:rgba(255,255,255,.18)}
}
@media (prefers-contrast:more){
  html:not(#v2) :is(.mbar,.mob-tabs).has-lens{-webkit-backdrop-filter:none;backdrop-filter:none}
  html[data-theme="dark"]:not(#v2) :is(.mbar,.mob-tabs).has-lens{background:#15125A}
  html:not(#v2) :is(.mbar,.mob-tabs) > .tab-lens{box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.7)}
}
/* Icons only (Ahmed, 20:15: "I don't think it's needed for us to have the
   names at the bottom of the navbar ... it just makes it messy for the UI.
   I think we just need to have the icons only"). Each name stays in the
   tab, read out by a screen reader and found by anything that reads the
   page, and is hidden from the eye; the icon is a little bigger and sits
   in the middle of its tab and of the lens. */
html:not(#v2) :is(.mbar,.mob-tabs).has-lens > :is(.mbtab,.mtab) > span:not(:first-child){
  position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;
  overflow:hidden!important;clip:rect(0 0 0 0)!important;clip-path:inset(50%)!important;white-space:nowrap!important;border:0!important}
html:not(#v2) :is(.mbar,.mob-tabs).has-lens > :is(.mbtab,.mtab){gap:0;justify-content:center}
html:not(#v2) :is(.mbar,.mob-tabs).has-lens > :is(.mbtab,.mtab) svg{width:24px;height:24px}

/* ---------- 39. NO STRIPE DOWN THE LEFT: A STATE IS THE BOX'S OWN GLASS (7 November, night) ----------
   Ahmed, 20:07, of the amber line down the left of the lorry card: "The
   highlight thing on the left side of the bounding box needs to go across
   the whole app, it looks ridiculously cheap, there has to be a better way
   to highlight something". Every box the app marked by a thick left edge
   (--bar in 10-tokens, "the left rule that carries status", and the 2 to
   4px ones) loses it. Where the box has an edge all round, its left edge
   is its other edges again; where the stripe was its only edge, the
   stripe's room is kept and drawn in nothing, so nothing inside moves. A
   box whose stripe carried a state takes the state into its glass: its
   rim, all the way round, is drawn in the state's colour (an outline just
   inside the edge, so no border, shadow or fill is replaced, and not while
   a key has it in focus, when the focus ring is the rim). A wash of the
   colour across the glass was tried and left amber and red looking muddy
   on the navy, so the rim alone carries it.
   The label pill, where there is one, stays the strong mark. The colour is
   --hl: set below for the boxes styled here, and in the element's own
   style where a screen draws it (where the stripe used to be set). The
   timeline's line (.ph-row) is a line between dots, not a box, and stays.
   qa/stripeqa.mjs fails on any stripe left on any screen. */
:root{--hl-rim:60%}
html[data-theme="light"]{--hl-rim:48%}
/* boxes with an edge all round: the left edge is the others again */
html:not(#v2) :is(.bigbtn,.lorry,.portal-hero,.rolecard,.healthstrip){border-left-width:1px;border-left-color:var(--rule)}
html:not(#v2) .gintro{border-left-width:1px;border-left-color:rgba(255,255,255,.16)}
html:not(#v2) :is(.ratebox,.yardtask){border-left-width:1px;border-left-color:var(--rule-strong)}
html:not(#v2) .rolecard:hover{border-left-color:var(--rule-strong)}
html:not(#v2) .pw-card{border-left-width:1px;border-left-color:color-mix(in srgb,var(--c) 40%,var(--rule))}
html:not(#v2) .pw-card.pw-down{border-left-style:dashed;border-style:dashed}
html:not(#v2) .pw-rec{border-left-color:transparent}
/* boxes whose only edge was the stripe: its room kept, drawn in nothing */
html:not(#v2) :is(.cmdstop,.jf-avail,.jf-inbox,.jf-next,.jf-quote-scope,.ml-note,.morning,.pickbar,.rfj-key,.sp-sub,.jf-st){border-left-color:transparent}
html:not(#v2) .jf-quote-scope{background:var(--surface-2);border-radius:var(--r2,12px)}
html:not(#v2) :is(.mo-step.next .mo-btn,.om-row.on){box-shadow:none}
html:not(#v2) .tbl tbody :is(tr.click:hover,tr.sel) td:first-child{box-shadow:none}
html:not(#v2) .tbl tbody tr.sel td{background-color:color-mix(in srgb,var(--int-hi) 12%,transparent)}
:where(.jf-avail,.jf-inbox,.jf-next,.ml-note,.morning,.pickbar){border-radius:var(--r2,12px)}
/* the state each one carried */
html:not(#v2) :is(.lorry.onplan,.lorry.finished,.jf-avail,.jf-next.done,.cmdstop.ok){--hl:var(--ok)}
html:not(#v2) :is(.lorry.behind,.jf-avail.no,.jf-next.late,.cmdstop.risk){--hl:var(--risk)}
html:not(#v2) :is(.jf-inbox,.ml-note,.yardtask,.cmdstop.warn){--hl:var(--warn)}
html:not(#v2) :is(.jf-next,.portal-hero,.ratebox,.morning,.rfj-key,.om-row.on,.mo-step.next .mo-btn){--hl:var(--int)}
html:not(#v2) .pickbar{--hl:var(--sky-500)}
html:not(#v2) .pw-card{--hl:var(--c)}
html:not(#v2) .rolecard:hover{--hl:var(--int)}
/* the glass a state makes */
html:not(#v2) :is([style*="--hl:"],.lorry.onplan,.lorry.finished,.lorry.behind,.jf-avail,.jf-next,.cmdstop.ok,.cmdstop.risk,.cmdstop.warn,
  .jf-inbox,.ml-note,.yardtask,.portal-hero,.ratebox,.morning,.rfj-key,.om-row.on,.mo-step.next .mo-btn,.pickbar,.pw-card,.rolecard:hover):not(:focus-visible){
  outline:1px solid color-mix(in srgb,var(--hl) var(--hl-rim),transparent);outline-offset:-1px}
/* 38, again (Ahmed, 20:25, of the field app's bar: "These icons need
   centering!! And they should turn white when the selector lands on
   them"). The field app's tab kept a 12px top padding from when it had a
   name under its icon, and the office phone's bar a padding at its foot
   for a home indicator it now floats above: so the icons sat 2 to 6px
   below the middle of the bar and of the lens. Both are even now, the icon
   in the middle of its tab, and the tab the lens is over is white (the
   others the quiet ink), wherever the lens is, moving or still. */
html:not(#v2) :is(.mbar,.mob-tabs).has-lens > :is(.mbtab,.mtab){padding:0!important;align-items:center;justify-content:center}
@media (max-width:720px){ html:not(#v2) :is(.mbar,.mob-tabs).has-lens{padding-top:5px;padding-bottom:5px} }
html[data-device="phone"]:not(#v2) :is(.mbar,.mob-tabs).has-lens{padding-top:5px;padding-bottom:5px}
html:not(#v2) :is(.mbar,.mob-tabs).has-lens > :is(.mbtab,.mtab),
html:not(#v2) :is(.mbar,.mob-tabs).has-lens > :is(.mbtab,.mtab).on{color:var(--ink-3)}
html:not(#v2) :is(.mbar,.mob-tabs).has-lens > :is(.mbtab,.mtab).lens-on{color:#fff}
html[data-theme="light"]:not(#v2) :is(.mbar,.mob-tabs).has-lens > :is(.mbtab,.mtab).lens-on{color:#0E0B5C}
html:not(#v2) :is(.mbar,.mob-tabs).has-lens > :is(.mbtab,.mtab) > span:first-child{display:inline-flex;line-height:0}
/* the ends of a bar with a lens kept clear (20:45): with 10px inside each
   end, a lens a little wider than a tab, one size on every tab, sits inside
   either rounded end with the same gap all round, so it never changes size
   as it travels and the graphics side can play the whole move */
@media (max-width:720px){ html:not(#v2) :is(.mbar,.mob-tabs).has-lens{padding-left:10px;padding-right:10px} }
html[data-device="phone"]:not(#v2) :is(.mbar,.mob-tabs).has-lens{padding-left:10px;padding-right:10px}
/* no browser box on a press (20:45, found in the recording): the office
   phone's tabs already had the tap highlight off (15-glass.css), the field
   app's and the portals' did not, so a press there flashed the browser's
   own rectangle over the tab under the lens (blue in Chrome, grey on an
   iPhone) for a tenth of a second. The press is the lens's and the bar's
   now on every bar, and on the round glass buttons of the phone's top bar */
:is(.mbar,.mob-tabs) > :is(.mbtab,.mtab),.mtop .icobtn{-webkit-tap-highlight-color:transparent}
/* and a tab drawn along with a mouse is the lens's to move, not a link the
   browser picks up and drags off (the review, 7 November, night) */
:is(.mbar,.mob-tabs).has-lens > :is(.mbtab,.mtab){-webkit-user-drag:none;-webkit-user-select:none;user-select:none}

/* ---------- 39. STATUS MARKS (8 November 2026) ----------
   Ahmed, 13:28: "on all pages - these alert style buttons need to not
   look so ai - they are one of the biggest tellers right now - they need
   to be turned into something apple would produce to notify people
   something". A state was a tinted capsule with a text glyph in it: an
   outlined triangle, a cross, a tick, a dot, on an amber, red, green or
   blue wash with an outline. It is marked now the way Apple marks one: a
   small filled symbol in the state's colour and plain words beside it, no
   box. A problem speaks in its colour, its words too; a state that is
   fine or under way stays quiet, its words in the secondary ink. Each
   state keeps its own shape (a tick, an exclamation mark, a cross, a dot)
   so none of them rests on colour alone. Labels that are not states keep
   their capsules: the people on a gang (grey, navy), filter tokens, and
   the neutral tags. The symbols are masks over the state's colour. */
html:not(#v2){--mk-check:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cdefs%3E%3Cmask id='k'%3E%3Crect width='16' height='16' fill='white'/%3E%3Cpath d='M4.6 8.3l2.25 2.3 4.5-4.75' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/mask%3E%3C/defs%3E%3Ccircle cx='8' cy='8' r='8' fill='white' mask='url%28%23k%29'/%3E%3C/svg%3E");--mk-excl:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cdefs%3E%3Cmask id='k'%3E%3Crect width='16' height='16' fill='white'/%3E%3Cpath d='M8 4.1v4.8' fill='none' stroke='black' stroke-width='1.95' stroke-linecap='round'/%3E%3Ccircle cx='8' cy='11.55' r='1.13' fill='black'/%3E%3C/mask%3E%3C/defs%3E%3Ccircle cx='8' cy='8' r='8' fill='white' mask='url%28%23k%29'/%3E%3C/svg%3E");--mk-cross:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cdefs%3E%3Cmask id='k'%3E%3Crect width='16' height='16' fill='white'/%3E%3Cpath d='M5.45 5.45l5.1 5.1M10.55 5.45l-5.1 5.1' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/mask%3E%3C/defs%3E%3Ccircle cx='8' cy='8' r='8' fill='white' mask='url%28%23k%29'/%3E%3C/svg%3E");--mk-dot:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='4' fill='white'/%3E%3C/svg%3E")}
html:not(#v2) .pill:is(.ok,.warn,.risk,.info):not(.token){
  background:none!important;border-color:transparent!important;box-shadow:none!important;
  height:auto;min-height:20px;padding:0;gap:6px;border-radius:8px;
  text-transform:none;letter-spacing:-.003em;font-weight:var(--w-semi);
}
html:not(#v2) .pill:is(.ok,.warn,.risk,.info):not(.token)::before{
  content:"";flex:none;width:14px;height:14px;opacity:1;background-color:var(--mark);
  -webkit-mask:var(--mark-i) center/contain no-repeat;mask:var(--mark-i) center/contain no-repeat;
}
html:not(#v2) .pill.ok:not(.token){--mark:var(--ok);--mark-i:var(--mk-check);color:var(--ink-2)}
html:not(#v2) .pill.warn:not(.token){--mark:var(--warn);--mark-i:var(--mk-excl);color:var(--warn)}
html:not(#v2) .pill.risk:not(.token){--mark:var(--risk);--mark-i:var(--mk-cross);color:var(--risk)}
html:not(#v2) .pill.info:not(.token){--mark:var(--info);--mark-i:var(--mk-dot);color:var(--ink-2)}
/* a pill that brings its own icon (Offline's wifi) shows that instead */
html:not(#v2) .pill:is(.ok,.warn,.risk,.info):not(.token):has(> svg)::before{display:none}
/* a mark that is also a button gets the toolbar's quiet press and hover */
html:not(#v2) button.pill:is(.ok,.warn,.risk,.info):not(.token){padding:2px 8px;margin:-2px -8px;cursor:pointer}
html:not(#v2) button.pill:is(.ok,.warn,.risk,.info):not(.token):hover{background:rgba(255,255,255,.08)!important;filter:none;box-shadow:none}
html[data-theme="light"]:not(#v2) button.pill:is(.ok,.warn,.risk,.info):not(.token):hover{background:rgba(14,11,92,.06)!important}
/* the save state in the top bar: a toolbar status, not an amber box. Its
   words are quiet; the symbol carries the state (21-store.js paintChip
   names the state in data-st) */
html:not(#v2) .savechip{background:none!important;border-color:transparent!important;box-shadow:none!important;
  color:var(--ink-2);font-weight:var(--w-semi);letter-spacing:-.003em;text-transform:none;border-radius:99px}
html:not(#v2) button.savechip:hover,html:not(#v2) .savechip:is(button,[role="button"]):hover{background:rgba(255,255,255,.08)!important}
html[data-theme="light"]:not(#v2) .savechip:is(button,[role="button"]):hover{background:rgba(14,11,92,.06)!important}
html:not(#v2) .savechip:is([data-st="saved"],[data-st="local"],[data-st="dirty"],[data-st="err"]) > svg{display:none}
html:not(#v2) .savechip:is([data-st="saved"],[data-st="local"],[data-st="dirty"],[data-st="err"])::before{
  content:"";flex:none;width:14px;height:14px;background-color:var(--mark);
  -webkit-mask:var(--mark-i) center/contain no-repeat;mask:var(--mark-i) center/contain no-repeat}
html:not(#v2) .savechip[data-st="saved"]{--mark:var(--ok);--mark-i:var(--mk-check)}
html:not(#v2) .savechip:is([data-st="local"],[data-st="dirty"]){--mark:var(--warn);--mark-i:var(--mk-excl)}
html:not(#v2) .savechip[data-st="err"]{--mark:var(--risk);--mark-i:var(--mk-excl);color:var(--risk)}
/* a planning warning: the symbol and its words, and "Fix" is a plain
   tinted button rather than an outlined box in capitals */
html:not(#v2) .warnrow{justify-content:flex-start;gap:8px;font-weight:var(--w-semi)}
html:not(#v2) .warnrow > span:first-child{display:inline-flex;align-items:center;gap:6px;min-width:0;flex:1 1 auto}
html:not(#v2) .warnrow .wmark{flex:none;width:13px;height:13px;background-color:var(--warn);
  -webkit-mask:var(--mk-excl) center/contain no-repeat;mask:var(--mk-excl) center/contain no-repeat}
html:not(#v2) .warnfix{font-family:var(--font);text-transform:none;letter-spacing:-.003em;font-size:var(--t-tiny);
  border-color:transparent;background:none;color:var(--warn);padding:2px 8px;border-radius:8px}
html:not(#v2) .warnfix:hover{background:rgba(255,194,77,.14);color:var(--warn);border-color:transparent}
html[data-theme="light"]:not(#v2) .warnfix:hover{background:rgba(159,95,5,.1)}
/* the tags and flags that warn (a man who may not be the right one, a
   scaffold over its date on the operations map) say it the same way */
html:not(#v2) .tag.warnable{background:none;border-color:transparent;padding-left:0;padding-right:0;display:inline-flex;align-items:center;gap:5px}
html:not(#v2) .tag.warnable::before{content:"";flex:none;width:12px;height:12px;background-color:var(--warn);
  -webkit-mask:var(--mk-excl) center/contain no-repeat;mask:var(--mk-excl) center/contain no-repeat}
html:not(#v2) .om-flag:is(.f-over,.f-insp,.f-today){background:none;box-shadow:none;padding:3px 0;display:inline-flex;align-items:center;gap:5px}
html:not(#v2) .om-flag:is(.f-over,.f-insp,.f-today)::before{content:"";flex:none;width:12px;height:12px;background-color:var(--mark);
  -webkit-mask:var(--mark-i) center/contain no-repeat;mask:var(--mark-i) center/contain no-repeat}
html:not(#v2) .om-flag.f-over{--mark:var(--risk);--mark-i:var(--mk-cross)}
html:not(#v2) .om-flag.f-insp{--mark:var(--warn);--mark-i:var(--mk-excl)}
html:not(#v2) .om-flag.f-today{--mark:var(--info);--mark-i:var(--mk-dot);color:var(--ink-2)}
/* "No gang" and "No lorry" against a job waiting on the day's dispatch are
   the job card's "No supervisor" in another place, and look it */
html:not(#v2) .waitw em{background:none;border-color:transparent;padding:0;display:inline-flex;align-items:center;gap:5px;font-weight:var(--w-semi)}
html:not(#v2) .waitw em::before{content:"";flex:none;width:12px;height:12px;background-color:var(--warn);
  -webkit-mask:var(--mk-excl) center/contain no-repeat;mask:var(--mk-excl) center/contain no-repeat}
/* two notices that were amber outlined boxes: the field app's "Nothing on
   today: showing Monday", which is news, not a warning, and the fuel
   screen's "the reading is right, save it anyway", a tick-box the warning
   above it already explains. Both take the quiet row the screen's other
   choices sit in */
html:not(#v2) .fahead{background:var(--surface-3);border-color:var(--rule);color:var(--ink-2)}
html:not(#v2) .fahead .ic{color:var(--int-hi,var(--int))}
html:not(#v2) .fahead b{color:var(--ink)}
html:not(#v2) .fxconfirm{background:var(--surface-3);border-color:var(--rule);color:var(--ink)}
@media (forced-colors:active){
  html:not(#v2) :is(.pill,.savechip,.tag.warnable,.om-flag,.waitw em)::before,html:not(#v2) .warnrow .wmark{forced-color-adjust:none;background-color:CanvasText}
}

/* ---------- 40. THE PHONE DOES NOT ZOOM (8 November 2026) ----------
   Ahmed, 23:22: "the mobile website shouldn't be zoomable, it should be
   fixed". The root takes the browser's double tap and pinch away (pan-x
   pan-y: a finger still scrolls either way); a part that already said
   none (a map, a sheet's grab, a scrim) or pan-y (the phone's tab bar)
   keeps saying it, since the browser goes by the narrowest on the way up.
   The viewport in the head and 30-shell.js do the rest; this replaces the
   17 October choice to leave pinch-zoom on (WCAG 1.4.4), which Ahmed has
   now made the other way. The phone's own Zoom (iOS Settings,
   Accessibility, Zoom; Android's magnification) still works over it. */
html:not(#v2){touch-action:pan-x pan-y}

/* ---------- 41. FIGURES OUT OF SIGHT, AND THE THEME SWITCH (9 November 2026) ----------
   Ahmed, 01:26: "Lewis can toggle something small that blurs important
   numbers for privacy reasons". While the eye in the top bar is on,
   every money figure (marked by 30c-private.js) is blurred where it
   stands, the rolling digits over a figure with it; an SVG label a
   little less, it is smaller. Nothing is moved or resized, and paper
   is not blurred. The eye itself says it is on in the accent. */
/* heavier (Waj, 9 Nov: "so they can't see anything at all"): the blur grows with the text, so a big figure is as lost as a small one */
html.wos-private [data-priv],html.wos-private wos-odo{filter:blur(12px);filter:blur(.55em);opacity:.85;-webkit-user-select:none;user-select:none}
html.wos-private span[data-priv]{display:inline-block}
html.wos-private.wos-film select{filter:blur(10px)}
html.wos-private svg [data-priv]{filter:blur(7px)}
@media print{html.wos-private [data-priv],html.wos-private wos-odo{filter:none}}
.icobtn.privbtn.on{color:var(--sky-400,#6FB6FF)}
html[data-theme="light"] .icobtn.privbtn.on{color:var(--navy-600,#2A3FA8)}
/* the theme switch's row: the switch sits at its end like iOS's */
.theme-row .switch{flex:none;pointer-events:none}
/* THE MORE LIST KEEPS ITS COLUMNS (Ahmed, 02:12: "Alignment needs to be on
   point - look at how switch view is completely off!!!"). Measured at 390:
   Switch view's picture was 30px against the 36px tiles, so its words
   started 6px left of every other row's; and the last mark of a row ended
   on three different lines, because a chevron's glyph stops 4.8px inside
   its 16px box, the swap arrows' 2.1px, and the switch's track at its box.
   The picture takes the tiles' 36px, and each mark is moved by its own
   air, so every row's words start on one line and every last mark ends on
   another (`privtest` measures both). */
.more-list .rolecard > .avatar{width:36px;height:36px;min-width:36px;flex:none;font-size:14px}
.more-list .rolecard > span:last-child:has(> svg:only-child){margin-right:-4.8px}
.more-list .rolecard > svg:last-child{margin-right:-2.1px}
/* the field app's More: its chevrons are the row's own last child */
.more-list .bigbtn > svg:last-child{margin-right:-4.8px}
/* ============================================================
   19e: WHAT WHITE'S OS ADDED ON TOP OF THE V2 DESIGN (28 October 2026)

   Ahmed's design layers (10 to 19d) are his, taken as he sends them.
   This file holds the styling for screens and fixes built in White's OS
   itself since his branch was taken: the email To/Cc suggestions, the
   route planner by lorry, the week planner (34j), and four touch fixes
   the phone tests asked for. Loaded last, so a later V2 rule does not
   undo them; nothing here restyles anything of Ahmed's.
   ============================================================ */

/* Job flow and email (36d, 36f) */
.jf-sortbar .sel,.jf-sortbar .inp{height:36px;padding-top:0;padding-bottom:0;font-size:var(--t-small)}
@media (max-width:720px){.jf-sortbar .sel,.jf-sortbar .inp{font-size:16px}} /* iOS zooms into a field under 16px and stays there */
/* To/Cc suggestions (20 Oct 2026) */
.ml-ac{position:relative}
.ml-sug{position:absolute;left:0;right:0;top:calc(100% + 4px);z-index:60;background:var(--surface,#fff);border:1px solid var(--rule-strong);
  border-radius:var(--r2);box-shadow:0 8px 24px rgba(0,0,0,.12);padding:4px;max-height:300px;overflow:auto}
.ml-sug[hidden]{display:none}
.ml-sugi{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;text-align:left;padding:8px 12px;border-radius:var(--r1,6px);background:none;border:0;cursor:pointer;color:inherit;font:inherit}
.ml-sugi b{font-size:var(--t-meta,14px);font-weight:600}
.ml-sugi span{font-size:var(--t-small,12px);opacity:.65}
.ml-sugi.on,.ml-sugi:hover{background:var(--surface-2,rgba(127,127,127,.12))}

/* A pill that is a button keeps its 44px reach on a touch screen. The
   V1 pill padding above set it back to 2px; the hit area is restored
   the way 13-devices.css made it, without changing how it looks. */
@media (max-width:720px){
  html:root button.pill{box-sizing:content-box;padding-block:11.5px;background-clip:content-box;margin-block:-11.5px}
}
/* the live operations header: on a phone the title and its date line
   take the whole first row, the pill and buttons wrap under them */
@media (max-width:720px){
  html:root .ops-head{flex-wrap:wrap}
  html:root .ops-head>.grow{flex:1 1 100%}
}
/* a phone drawer fills the screen edge to edge: pinned both sides rather
   than 100vw from the right, which on a browser with a scrollbar put it
   past the left edge by the scrollbar's width */
@media (max-width:720px){
  html:root .sheet.drawer,html:root .sheet.drawer.wide{left:0;right:0;width:auto}
}
/* the day board's big figure keeps a whole line to itself, so the words
   under it never ride up into it (uiaudit: tablet, "£81k" over its label) */
html:not(#v2) .today-v{line-height:1.25;display:block}

/* a same-screen re-render keeps the page where it is: no entrance fade.
   51-motion.js marks those renders with html.rerender; the rule that was
   meant to catch them only covered the page's children, not the page. */
html.rerender .page.fade,html.rerender .page.fade > *{animation:none!important}

/* Route planner: the day, by lorry (27 Oct 2026, 34i-dayplan.js) */
.dp-lorries{display:grid;gap:var(--s4);grid-template-columns:repeat(auto-fill,minmax(min(340px,100%),1fr))}
.dp-lorry{min-width:0}.dp-lorry .grow{min-width:0;overflow-wrap:anywhere}
@media (max-width:720px){.dp-lorries{grid-template-columns:1fr}}
.dp-gang{display:flex;flex-wrap:wrap;gap:6px;margin:12px 0 4px}
.dp-man{display:inline-flex;align-items:center;gap:4px;padding:6px 10px;border-radius:99px;background:var(--surface-3);font-size:var(--t-meta);font-weight:600;min-height:32px}
.dp-man.off{color:var(--risk)}
.dp-x{border:0;background:none;padding:6px;margin:-6px -8px -6px 0;color:inherit;opacity:.6;cursor:pointer;min-width:44px;min-height:44px}
.dp-run{list-style:none;margin:8px 0 0;padding:0}
.dp-drop{display:flex;align-items:center;gap:10px;padding:10px 0;border-top:1px solid var(--line)}
.dp-no{width:22px;flex:none;font-weight:700;text-align:center;opacity:.6}
.dp-arrows{display:flex;gap:2px;flex:none}
.dp-arrows .icobtn{min-width:44px;min-height:44px}
.dp-sel{width:auto;max-width:130px;flex:none}
.dp-pick{width:100%;text-align:left;min-height:52px}
.dp-pick[disabled]{opacity:.45}
@media (max-width:520px){.dp-drop{flex-wrap:wrap}.dp-drop .grow{flex:1 1 60%}.dp-sel{max-width:100%;width:100%;min-width:0;flex:1 1 100%}}
@media (max-width:720px){.dp-sel{font-size:16px}}

/* =========================================================
   28. MOTION AND THE DESK (27 October, evening)
   From Ahmed's screen recording in Safari and two desk screenshots
   (Fleet and Growth). Nothing here is phone-only and nothing removes a
   piece of the design: each rule makes something that was already
   there move or sit the way it was meant to.
   ========================================================= */

/* ----- a day-board figure lifts, it does not jump -----
   The cell rose 2px under the pointer with only its background in the
   transition, so the rise happened in one frame, both ways. */
html:not(#v2) .today > :is(a,button).today-c{
  transition:background var(--dur) var(--ease),border-color .16s var(--ease),transform var(--dur) var(--ease)}

/* ----- an insight reads top to bottom -----
   The claim, then the figure, then the why, then the way in. The figure
   used to sit beside the title when it fitted and drop under it when it
   did not, so one row of cards had it both ways (Growth: "Conversion
   falls off sharply above £20k" against "Solar Install Access is White's
   strongest work"); and the way in sat wherever the text ended, so two
   cards side by side had their links at different heights and one card
   carried a band of empty space. The figure is always under the title
   now and the way in is always at the foot (31-ui.js insightCard). */
html:not(#v2) .insight{display:flex;flex-direction:column}
html:not(#v2) .insight > .spread{flex-direction:column;align-items:flex-start;flex-wrap:nowrap;gap:4px}
html:not(#v2) .insight > .spread > h4{flex:none;max-width:100%}
html:not(#v2) .insight > .spread > .insight-stat{flex:none;max-width:100%;white-space:normal}
html:not(#v2) .insight > .ins-foot{margin-top:auto;padding-top:var(--s3)}

/* ----- a vehicle's three facts share a line on a desk -----
   Driver, MOT and Service are each a word or two. The grid gave them two
   columns, so Service sat alone under Driver with a gap beside it. */
@media (min-width:721px){
  html:not(#v2) .vfacts{grid-template-columns:repeat(3,minmax(0,1fr))}
}

/* ----- the plus sits in the corner, not on the work -----
   At 52px, 24px in from the corner, it covered the right-hand end of
   whatever scrolled under it: the Log buttons on Fleet, the end of a
   figure on Growth, a job's status pill. At 44px (the smallest a
   control is drawn anywhere in this app) and 10px in, its left edge
   stops where a card's own padding ends, so it sits on the margin of a
   card and never on what the card says. Same white, same navy +, same
   shadow, same place; the page already ends 112px clear of it. */
@media (min-width:721px){
  html:not(#v2) .shell ~ .fab{width:44px;height:44px;border-radius:16px;right:10px;bottom:16px}
}
/* An iPad held upright (721 to 900) has 16px page margins and 16px card
   padding, so there is no margin for the + to sit on and it would cover
   the right-hand end of every card. There it moves into the top bar, the
   way the phone's bar already carries it. Same button, same action. */
html:not(#v2) .topbar .tb-quick{display:none}
@media (min-width:721px) and (max-width:900px){
  html:not(#v2) .topbar .tb-quick{display:grid}
  html:not(#v2) .shell ~ .fab{display:none}
}
/* the save chip is one phrase and never breaks; the search field gives
   up the room instead (it broke onto two lines on an upright iPad) */
html:not(#v2) .topbar .savechip{white-space:nowrap;flex:none}

/* =========================================================
   29. HEADINGS LEAD (27 October, night)
   Ahmed: "body text throughout is not disruptive to any of the heading
   texts". qa/designqa.mjs read every heading on every screen of every
   account, on a phone and a desk, against the first body text under it:
   bigger than its heading, as heavy and as bright as it, closer than 2px,
   or squeezing it onto another line. These are the ones it found.
   ========================================================= */

/* A job file's sections (Request, Dates, Price) were 12px labels over 13px
   rows, so the rows read louder than the heading over them. They are the
   body size, semibold, in the heading ink: on a phone 17 over 15. */
html:not(#v2) .jf-sec h2{font-size:var(--t-body);color:var(--ink);letter-spacing:var(--ls-0);margin:0 0 10px}

/* A phrase that must not break: a date range, a kind of work. */
.nw{white-space:nowrap}

/* ============================================================
   THE WEEK, DRAFTED AND PUBLISHED (34j, 28 October 2026)
   ============================================================ */
.wp-dot{display:inline-block;width:9px;height:9px;border-radius:50%;flex:none;background:var(--ink-3);vertical-align:middle}
.wp-dot.green{background:var(--ok)}.wp-dot.amber{background:var(--warn)}.wp-dot.red{background:var(--risk)}
.wp-dot.info{background:var(--info)}.wp-dot.grey{background:var(--rule-strong)}
.wp-bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--s2) var(--s3);
  padding:var(--s2) var(--s3);margin:0 0 var(--s3);background:var(--surface);border:1px solid var(--rule);border-radius:var(--r3)}
.wp-state{display:inline-flex;align-items:center;gap:8px;font-size:var(--t-meta);color:var(--ink-2)}
.wp-btns{display:flex;flex-wrap:wrap;gap:6px}
.wp-btns .btn{min-height:44px}
.wp-issues{margin:0 0 var(--s3);padding:var(--s2) var(--s3);border:1px solid var(--warn-line,var(--rule));border-radius:var(--r3);background:var(--warn-bg)}
.wp-issues.ok{display:flex;flex-wrap:wrap;align-items:center;gap:8px;background:var(--ok-bg);border-color:var(--ok-line,var(--rule));font-size:var(--t-meta)}
.wp-issues summary{display:flex;flex-wrap:wrap;align-items:center;gap:8px;cursor:pointer;font-size:var(--t-meta);min-height:32px}
.wp-isslist{list-style:none;margin:var(--s2) 0 0;padding:0;display:flex;flex-direction:column;gap:6px}
.wp-iss{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:var(--r2);background:var(--surface);font-size:var(--t-meta)}
.wp-iss.red{box-shadow:inset 3px 0 0 var(--risk)}.wp-iss.amber{box-shadow:inset 3px 0 0 var(--warn)}.wp-iss.info{box-shadow:inset 3px 0 0 var(--info)}
.wp-iss{flex-wrap:wrap}.wp-iss .btn{flex:none;max-width:100%;white-space:normal}
/* the waiting work */
.wp-todopanel{display:flex;flex-direction:column;gap:var(--s2)}
.wp-search{width:100%;flex:none;max-width:none}
.wp-filters,.wp-daychips{display:flex;flex-wrap:wrap;gap:4px}
.wp-chip{min-height:32px;padding:4px 10px;border-radius:var(--r-pill);border:1px solid var(--rule);background:var(--surface-2);color:var(--ink-2);font-size:var(--t-small)}
.wp-chip i{font-style:normal;opacity:.6;margin-left:2px}
.wp-chip.on{background:var(--int-soft);border-color:var(--int);color:var(--int)}
.wp-todolist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px;max-height:min(62vh,720px);overflow:auto}
.wp-todo{--c:var(--ink-3);position:relative;display:flex;align-items:flex-start;gap:6px;border-radius:var(--r2);
  border:1px solid color-mix(in srgb,var(--c) 40%,var(--rule));background:var(--surface);box-shadow:inset 4px 0 0 var(--c)}
.wp-todo[draggable="true"]{cursor:grab}
.wp-todo.sel{box-shadow:inset 4px 0 0 var(--c),0 0 0 2px var(--int)}
.wp-todo.pw-dragging{opacity:.4}
.wp-chk{margin:12px 0 0 10px;width:18px;height:18px;flex:none}
.wp-todo-b{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px;text-align:left;padding:8px 10px 8px 8px;color:var(--ink)}
.wp-todo-t{display:flex;flex-wrap:wrap;align-items:center;gap:4px}
.wp-todo-t b{font-size:var(--t-small);line-height:var(--lh-heading);margin-right:2px}
.wp-todo-m{display:flex;align-items:center;gap:4px;flex-wrap:wrap;font-size:var(--t-micro);color:var(--ink-2)}
.wp-todo-m.amber{color:var(--warn)}
.wp-tag{font-size:var(--t-micro);font-weight:var(--w-semi);padding:1px 6px;border-radius:var(--r-pill);background:var(--surface-3);color:var(--ink-2)}
.wp-tag.red{background:var(--risk-bg);color:var(--risk)}.wp-tag.amber{background:var(--warn-bg);color:var(--warn)}
.wp-guess{font-style:normal;font-size:var(--t-micro);padding:0 5px;border-radius:var(--r-pill);background:var(--warn-bg);color:var(--warn)}
.wp-selbar{display:flex;flex-wrap:wrap;align-items:center;gap:6px;padding:8px;border-radius:var(--r2);background:var(--int-soft)}
.wp-empty{padding:var(--s3) 4px}
/* a gang's day */
.wp-load{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;text-align:left;padding:5px 7px;border-radius:var(--r2);
  font-size:var(--t-micro);color:var(--ink-2);background:var(--surface-2);border:1px solid transparent}
.wp-load>span:first-of-type{display:inline-flex;align-items:center;gap:6px}
.wp-load .wp-dot{margin-right:6px}
.wp-load.red{border-color:var(--risk);background:var(--risk-bg)}.wp-load.amber{border-color:var(--warn-line,var(--warn))}
.wp-load-why{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;color:var(--ink)}
.wp-load:hover,.wp-load:focus-visible{border-color:var(--int)}
.wp-more{min-height:30px;border:1px dashed var(--rule-strong);border-radius:var(--r2);color:var(--ink-3);font-size:var(--t-micro)}
.wp-more:hover,.wp-more:focus-visible{color:var(--int);border-color:var(--int)}
.pw-card-d{display:inline-block;margin-left:4px;padding:0 5px;border-radius:var(--r-pill);background:var(--warn-bg);color:var(--warn);letter-spacing:0}
.pw-rh-b{display:flex;flex-direction:column;gap:2px;text-align:left;color:var(--ink);width:100%}
.pw-rh-b:hover b,.pw-rh-b:focus-visible b{color:var(--int);text-decoration:underline}
/* the tap way in */
.wp-gangpick{display:flex;align-items:center;gap:10px;width:100%;min-height:56px;padding:10px 12px;text-align:left;border-radius:var(--r2);
  border:1px solid var(--rule);background:var(--surface-2);color:var(--ink)}
.wp-gangpick .grow{display:flex;flex-direction:column;gap:2px}
.wp-gangpick.green{border-color:var(--ok)}.wp-gangpick.amber{border-color:var(--warn)}
.wp-gangpick.red{opacity:.7;border-color:var(--risk)}
.wp-say{color:var(--ink-2)}
.wp-phonetodo{display:flex;align-items:center;gap:10px;width:100%;min-height:56px;padding:10px 12px;margin-bottom:var(--s3);
  text-align:left;border-radius:var(--r3);background:var(--int-soft);border:1px solid var(--int);color:var(--ink)}
.wp-phonetodo .grow{display:flex;flex-direction:column}
/* the day in full */
.wp-legs{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
.wp-leg{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:var(--r2);background:var(--surface-2)}
.wp-leg.amber{box-shadow:inset 3px 0 0 var(--warn)}
.wp-legn{width:24px;height:24px;border-radius:50%;display:inline-grid;place-items:center;background:var(--int-soft);color:var(--int);font-weight:var(--w-bold);font-size:var(--t-small);flex:none}
.wp-arrows{display:flex;gap:4px}
.wp-sugday{display:flex;flex-direction:column;gap:6px}
.wp-suggang{padding:8px 10px;border-radius:var(--r2);background:var(--surface-2)}
.wp-publist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px;max-height:50vh;overflow:auto}
.wp-publist li{padding:8px 10px;border-radius:var(--r2);background:var(--surface-2)}
@media (max-width:760px){.wp-bar{flex-direction:column;align-items:stretch}.wp-btns .btn{flex:1 1 45%}.wp-chip,.wp-more,.wp-iss .btn,.wp-load{min-height:44px}}
.pw.pw-todoside{grid-template-columns:260px minmax(0,1fr)}
@media (max-width:1100px){.pw.pw-todoside{grid-template-columns:230px minmax(0,1fr)}}
.wp-issmore{margin-left:auto;color:var(--int)}
.pw-sidetabs button{min-width:0;padding-left:4px;padding-right:4px;white-space:normal;line-height:1.15}
.wp-barl{display:flex;flex-direction:column;gap:2px}.wp-hours{display:inline-flex;align-items:center;gap:4px;color:var(--int);min-height:32px}
@media (max-width:760px){.wp-hours{min-height:44px}}
/* Touch: every control big enough for a thumb (journeys, 29 Oct).
   A tablet may not report a coarse pointer, so narrow screens count too. */
@media (pointer:coarse),(hover:none),(max-width:1100px){
  .wp-btns .btn,.wp-chip,.wp-more,.wp-iss .btn,.wp-load,.wp-arrows .btn,.wp-arrows button,.dayrow .wknav,.wknav,
  .pw-sidetabs button,.seg button,.wp-hours,.pw-card .btn.sm,.wp-gang .btn.sm,.gbtn,.tour-gbtn,
  button[onclick="clearSig()"],.demoway-t,.lnk[onclick^="pinHelp"],.lnk[onclick*="#/privacy"],
  .btn[aria-label="Previous week"],.btn[aria-label="Next week"],.pw-rh-b{min-height:44px;min-width:44px}
  .wp-hours,.lnk[onclick^="pinHelp"],.lnk[onclick*="#/privacy"],.demoway-t{display:inline-flex;align-items:center}
  .wp-chk{width:26px;height:26px;margin:9px;flex:none;accent-color:var(--int)}
  .pw-not summary,.wp-issues summary,details.wp-pub summary{min-height:44px;display:flex;align-items:center}
}
/* a tablet: the waiting list goes above the week so the five days fit */
@media (max-width:1000px){.pw,.pw.pw-todoside{grid-template-columns:minmax(0,1fr)}}
/* the signature is ink on paper, whatever the theme: it is filed on white */
.sigpad{background:#fff !important}
.sigpad .hint{color:#5b6475 !important}
/* inspections on the week planner */
.pw-insp{display:flex;flex-direction:column;gap:4px;padding:6px}
.pw-ichip{display:flex;align-items:center;gap:6px;flex-wrap:wrap;text-align:left;font:600 12px/1.2 var(--font-body);padding:6px 8px;border-radius:var(--r2);
  background:var(--int-soft);color:var(--int);border:1px solid transparent;min-height:32px}
.pw-ichip span{font-weight:500;opacity:.8}
.pw-ichip.over{background:var(--risk-bg,#fde8e8);color:var(--risk)}
@media (pointer:coarse),(max-width:1100px){.pw-ichip{min-height:44px}}

/* Erect and dismantle, told apart by colour on the planner (29 Oct 2026):
   yellow and purple, as on Lewis's own Google calendar */
:root{--pw-up:#c99a06;--pw-down:#7b5cc4}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--pw-up:#f2c94c;--pw-down:#a78bfa}}
:root[data-theme="dark"]{--pw-up:#f2c94c;--pw-down:#a78bfa}
.pw-card.pw-up .pw-card-k{color:var(--pw-up)}
.pw-card.pw-down .pw-card-k{color:var(--pw-down)}
.pw-legend{display:inline-flex;gap:12px;align-items:center;font-size:var(--t-small);color:var(--ink-2)}
.pw-legend i{display:inline-block;width:12px;height:12px;border-radius:3px;margin-right:5px;vertical-align:-1px}
.pw-legend .up{background:var(--pw-up)}.pw-legend .down{background:var(--pw-down)}
/* a tappable pill (the training matrix's Exp / Due): a thumb's height on a phone
   (walktest, 9 Nov 2026 merge: Ahmed's pills are 24px) */
html:not(#v2) td button.pill:is(.ok,.warn,.risk,.info):not(.token){box-sizing:content-box;padding-block:12.5px;background-clip:content-box;margin-block:-12.5px}
