/* ============================================================================
   theme.css — Platinum "Minimal Mono" reskin (v2.23), brand refresh v3.1119.0
   Loaded AFTER styles.css. Adds: design tokens (light + dark via html[data-theme]),
   the blue-forward look, the collapsible left sidebar, and the frosted dark sign-in.
   Keeps every existing id/class so app.js + auth.js keep working.

   ---- UI REFRESH v2, "BRAND BLUE" (v3.1119.0, 30 September 2026) --------------------------------
   Darren approved the mock (ui-refresh-v2.html) with "Looks good. Let's do it!". This is rollout
   step 1 plus the app-wide parts, folded into the real rules rather than laid over the top of them.
   The reason for it, in one line: the app had drifted to five blues (a brighter blue in dark, navy heroes,
   navy washes, indigo and teal in the charts), and only one of them is the Platinum Consultants
   blue. Now every blue is #004EFE mixed with white or black, so the hue never moves.

   TOKENS (both themes define every one; test/v31119ui checks)
     brand blue   --b-25 --b-50 --b-100 --b-200 --b-300 --b-400 --b-500 (#004EFE) --b-600 --b-700
                  --b-800 --b-900. In dark, blue TEXT is --b-300 #80A6FE (via --blue-ink); the brand
                  blue stays a FILL (white on it is 5.96:1 in both themes).
     orange       --o-500 #FF6D00 (a fill: ink text on it, never white — 2.8:1), --o-ink #B24C00 (as
                  text), --o-soft. Orange is the ONE attention action or marker on a screen.
     neutrals     from the brand ink #231F20: --ink --ink-2 --muted --grey, never a blue-grey.
     surfaces     --s-bg --s-panel --s-raised --s-sunk --s-line --s-line-2 (light #F6F6F6/#FFF;
                  dark #0E0E0F / #161617 / #1D1D1F), --s-accent-soft, --s-hero (the brand gradient).
     status       --s-ok --s-warn --s-bad and their -soft washes (the pill colours).
     spacing      --sp-1..--sp-8 (4 8 12 16 24 32 48 64)
     radius       --rad-sm 8, --rad-in 10 (inputs, buttons), --rad 14 (cards), --rad-lg 20 (heroes),
                  --rad-pill
     shadow       --sh-1 (cards) --sh-2 (hover, heroes) --sh-3 (pop-overs, toasts)
     type         --fs-xs 12 --fs-sm 13 --fs-md 14.5 --fs-lg 17 --fs-xl 21 --fs-2xl 28 --fs-3xl 40;
                  --font is Inter, served from /fonts/inter/ (no Google Fonts), then system-ui.
     charts       --ch1 blue, --ch2 ink-grey, --ch3 orange (was a bright blue / teal). The value on a bar
                  is white on blue and on the light grey, ink on the dark grey and on orange.
     The app's older names (--blue, --accent, --bg, --panel, --line, --ink, --muted, --tint, --zebra,
     --r, --shadow…) are kept and point at the same values, so the 4,000-odd rules that use them
     moved with the tokens instead of being rewritten.

   COMPONENT CLASSES (the foundation later screens build on)
     .panel / .card        14px card, hairline border, soft shadow. .panel.sub and a panel inside a
                           panel are a grey wash with no border.
     section headings      .panel h2 etc. are ink with no orange bar.
     .pill                 ONE status pill: .pill.ok / .blue / .warn / .bad / .neutral (dot + label).
     .stat / .stat-n / .stat-l   ONE stat tile (also .off-tile, .dsum-t, .cards .card).
     .seg-chips, .sec-bar, .cm-tabs   pill-style segmented tabs, .on = the raised one.
     .chips                (v3.1120.0) filter chips: each a pill, the chosen one the soft brand blue.
     tables                sticky header, zebra, hover wash, .num (and td.num) right-aligned figures.
     fields                10px radius, blue focus ring.
     .search-field         (v3.1120.0) THE search box: magnifier, 40px, 16px text, x to clear, "/"
                           hint, count, spinner. searchFieldHtml() in public/ui.js writes one; its
                           enhancer wraps every existing search box in place.
     .btn.primary          brand blue, white text. .btn.orange / .btn.attn: orange with INK text.
     .empty-state          icon, a bold line, one sentence, one action.
     .toast                ink card, bottom right (dark: light card).
     .page-head            pageHeaderHtml() in public/ui.js: breadcrumb (.crumbs) + h1 + About +
                           one-line summary (.ph-sum) + actions (.view-head-actions).
     sidebar               current item a soft blue pill, group names in sentence case, a group icon
                           and a chevron, only the current group open.
   ========================================================================== */

/* ---------- the font (v3.1119.0) ----------
   Inter, from this app's own /fonts/inter/ (SIL Open Font License, public/fonts/inter/LICENSE.txt):
   the Latin files only, which is every character the app prints. `swap` so text shows in the system
   font at once and changes when Inter lands, rather than being invisible on a slow line. */
@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/inter/inter-latin-400-normal.woff2) format('woff2')}
@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url(/fonts/inter/inter-latin-500-normal.woff2) format('woff2')}
@font-face{font-family:'Inter';font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/inter/inter-latin-600-normal.woff2) format('woff2')}
@font-face{font-family:'Inter';font-style:normal;font-weight:700;font-display:swap;src:url(/fonts/inter/inter-latin-700-normal.woff2) format('woff2')}
@font-face{font-family:'Inter';font-style:normal;font-weight:800 900;font-display:swap;src:url(/fonts/inter/inter-latin-800-normal.woff2) format('woff2')}

/* ---------- tokens ---------- */
:root{
  --blue:#004EFE; --blue-2:#0045E0; --blue-dark:#0038B8; --blue-lt:#EAF0FF;
  --orange:#FF6D00; --orange-soft:#FFF0E5;
  --bg:#F6F6F6; --panel:#FFFFFF; --panel-2:#FAFAFA; --line:#E2E1E1;
  --ink:#231F20; --muted:#625E5F;
  /* v3.1111.0 — `--grey` is TEXT in 62 of its 66 uses (the "← Back to…" links, step counters, the
     sidebar's group names and dots, pills, icon buttons, placeholders), and #9AA0B0 measured 2.4:1
     on --bg. #666B7A is 4.9:1 on --bg and 4.7:1 on --blue-lt, still a step lighter than --muted. */
  --grey:#6A6667; --grey-mid:#C9C7C7; --grey-light:#F1F0F0;
  /* #1f8a4c measured 4.16:1 on --panel-2, which is under AA — close enough to look fine and not
     close enough to read. Darkened to clear it on every surface the app draws on. */
  --ok:#18703F; --danger:#c0392b;   /* kept: chat-widget.js carries #b42318 on other sites' pages, and uipolish forbids a bare copy of --danger */
  /* 0414, v3.742.0 — A RED DARK ENOUGH FOR WHITE TEXT ON TOP OF IT.
     `--danger` is chosen to be read AS TEXT on a panel, and in the dark theme that makes it a
     LIGHT red (#ff6b6b): white on it measures 2.78:1, under AA, on the one message in this
     application that must not be missed. So the failure toast has its own. MEASURED: 5.44:1
     light, 6.93:1 dark. Two tokens because they are two jobs, not because one of them is wrong. */
  --toast-bad:#C0392B;
  /* ---- THE THIRD STATE (v3.287.0) -----------------------------------------------------------
     There were `--ok` and `--danger` and no token for "this worked, but read the small print" — so
     four different ambers were typed by hand across app.js (#b9770e, #c05600, #c47f00, #8a6d1a),
     every one of them a light-mode value that fell to ~4.4:1 or worse on the dark panel. One token,
     two values, and the amber can never again be right in one theme and wrong in the other. */
  --warn:#B24C00;
  --accent:var(--blue); --accent-soft:var(--blue-lt);
  --zebra:#FAFAFA;         /* alternating table row */
  /* v3.443.0 — the soft wash behind sub-panels, highlighted rows and hover hits. styles.css has used
     `var(--tint, #F2F6FF)` in eight places for months; the variable was never defined, so every one of
     them fell to the light fallback in BOTH themes — a white card with near-white text on the customer
     record in dark mode (found in the visual pass). Defined once here, per theme. */
  --tint:#F1F0F0;
  --r:8px;                 /* v3.1119.0: 8px (was 5px, "squared"); cards use --rad 14px */
  --shadow:0 1px 2px rgba(35,31,32,.05), 0 1px 3px rgba(35,31,32,.06);
  /* ---- v3.1111.0 (30 September 2026) — THE BRAND BLUE AS INK, NOT ONLY AS PAINT ---------------
     test/v31111a11y measures every piece of text on every screen against what is actually behind
     it. The one colour that failed most was the dark theme's blue AS TEXT: the old bright blue on the dark
     panel is 3.94:1 (headings, "Hide this", table heads, sidebar labels — 400-odd blocks). The same
     blue is right as a FILL (white on that blue is 4.5:1), so the token cannot simply get lighter.
     Every `color:var(--blue|--accent|--blue-dark|--blue-2)` now reads its `-ink` twin instead:
     in light the twin IS the brand colour (nothing changes), in dark it is a lighter blue measured
     at 5.4–6.8:1 on every dark surface. Fills, borders and gradients keep the brand blue. */
  --blue-ink:var(--blue); --accent-ink:var(--accent); --blue-dark-ink:var(--blue-dark); --blue-2-ink:var(--blue-2);
  /* The brand orange as ink, for the same reason in the other direction: #FF6D00 on white is
     2.9:1 and on --orange-soft 2.6:1. Measured: #A84300 is 5.6:1 on --orange-soft. */
  --orange-ink:#B24C00;
  /* The green wash behind "included with your membership" (.edge-incl, .chip-ok). Used for months as
     `var(--ok-soft,#EAF7EE)` and never defined, so the dark theme drew light text on that light green
     (1.0:1, test/v31111a11y). Defined per theme now. */
  --ok-soft:#E8F5EC;
  /* ---- v3.1119.0 (30 September 2026) — UI REFRESH v2, THE BRAND SCALE -------------------------
     Every blue below is #004EFE mixed with white (tints) or black (shades), so the hue never shifts;
     neutrals are mixed from the brand ink #231F20, not from a blue-grey. See the header of this file
     for the list and test/v31119ui for the checks (both themes, contrast, no stray blue). */
  --b-25:#F5F8FF; --b-50:#EAF0FF; --b-100:#D6E3FF; --b-200:#B2CAFF; --b-300:#80A6FE; --b-400:#4C83FE;
  --b-500:#004EFE; --b-600:#0045E0; --b-700:#0038B8; --b-800:#002A8C; --b-900:#001E61;
  --o-500:#FF6D00; --o-ink:#B24C00; --o-soft:#FFF0E5; --ink-brand:#231F20;
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:24px; --sp-6:32px; --sp-7:48px; --sp-8:64px;
  --rad-sm:8px; --rad-in:10px; --rad:14px; --rad-lg:20px; --rad-pill:999px;
  --sh-1:0 1px 2px rgba(35,31,32,.05), 0 1px 3px rgba(35,31,32,.06);
  --sh-2:0 2px 4px rgba(35,31,32,.04), 0 8px 24px rgba(35,31,32,.08);
  --sh-3:0 12px 40px rgba(35,31,32,.16);
  --fs-xs:12px; --fs-sm:13px; --fs-md:14.5px; --fs-lg:17px; --fs-xl:21px; --fs-2xl:28px; --fs-3xl:40px;
  --font:'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --s-bg:#F6F6F6; --s-panel:#FFFFFF; --s-raised:#FFFFFF; --s-sunk:#F1F0F0; --s-line:#E2E1E1; --s-line-2:#ECEBEB;
  /* v3.1120.0 (30 Sep 2026): a field's border under the pointer (the search boxes). A token, so the rule
     itself holds no colour and dark mode follows — test/v31120sell reads everything after its marker. */
  --s-line-hover:#C9C7C7;
  /* v3.1121.0 (30 Sep 2026): the glass tile on the member's tier hero (the figures on My membership). The brand
     ink and white at an alpha, no hue, because it sits on bronze, silver, gold or platinum and has to be "a
     little darker than whatever is behind me" — darker, so the white figure on it reads at least as well as
     the white headline beside it. The hero is the same in both themes, so dark does not redefine these. */
  --s-glass:rgba(35,31,32,.2); --s-glass-line:rgba(255,255,255,.24);
  --s-ink:#231F20; --s-ink-2:#4A4647; --s-muted:#625E5F; --ink-2:#4A4647;
  --s-link:#004EFE; --s-link-strong:#0038B8;
  --s-hero:linear-gradient(120deg, #004EFE 0%, #0038B8 62%, #002A8C 100%);
  --s-hero-ink:#FFFFFF; --s-hero-muted:#E3EBFF;
  --s-accent-soft:#EAF0FF; --s-ok:#18703F; --s-ok-soft:#E8F5EC; --s-warn:#B24C00; --s-warn-soft:#FFF0E5;
  --s-bad:#B42318; --s-bad-soft:#FDECEA;
  --s-focus:0 0 0 3px rgba(0,78,254,.25);
  --ch1:#004EFE; --ch2:#6A6667; --ch3:#FF6D00;
}
/* ---- THE BROWSER'S OWN WIDGETS HAVE TO KNOW WHICH THEME THEY ARE IN (v3.350.0) --------------
   A date input draws its calendar icon, its dd/mm/yyyy placeholder and its picker panel itself, and
   it colours them from `color-scheme` — NOT from our variables. Without this the 56 date pickers in
   the suite render a black calendar glyph and near-black internal text on a dark panel in dark mode,
   which is illegible and looks like a broken field rather than a themed one. One line, and it also
   fixes the scrollbars and the search box's clear cross. */
html{color-scheme:light}
html[data-theme="dark"]{color-scheme:dark}
html[data-theme="dark"]{
  --blue:#004EFE; --blue-2:#0045E0; --blue-dark:#0038B8; --blue-lt:#12213F;
  --orange:#FF6D00; --orange-soft:#2A1A0C;
  --bg:#0E0E0F; --panel:#161617; --panel-2:#1D1D1F; --line:#2E2D2F;
  --ink:#F5F4F4; --muted:#A7A5A6; --grey:#A7A5A6;   /* v3.1111.0: #6C6F7B was 3.8:1 on --bg. v3.1119.0: the ink-neutral #A7A5A6, 7.9:1 on --bg, 7.1:1 on --zebra */
  --grey-mid:#3A393B; --grey-light:#1D1D1F;
  --ok:#5FD08F; --danger:#FF8A80;
  --toast-bad:#A82B1E;   /* see the light theme's note: white on #ff6b6b is 2.78:1 */
  --warn:#FF9240;
  --accent:#004EFE; --accent-soft:#12213F;
  --zebra:#1A1A1B;         /* v3.1119.0: a step off the #161617 card, no blue cast */
  --tint:#1D1D1F;          /* sub-panel wash — see the light-theme note */
  --shadow:0 1px 2px rgba(0,0,0,.4);
  /* v3.1111.0 — the blue as INK (see the light theme's note): 6.8:1 on --bg, 6.3:1 on --panel,
     5.8:1 on --panel-2, 5.4:1 on --zebra, 5.0:1 on --line. */
  --blue-ink:#80A6FE; --accent-ink:#80A6FE; --blue-dark-ink:#80A6FE; --blue-2-ink:#80A6FE;
  --orange-ink:#FF9240;    /* 7.5:1 on --orange-soft */
  --ok-soft:#10261A;       /* --ink on it is 13:1, --ok on it 7.4:1 */
  /* v3.1119.0 — the brand scale's dark-theme values: neutral near-black surfaces (no blue cast), the
     brand blue stays a fill, blue TEXT is --b-300 (7.6:1 on a card, where the old blue was 4.0:1). */
  --o-soft:#2A1A0C; --o-ink:#FF9240;
  --sh-1:0 1px 2px rgba(0,0,0,.4);
  --sh-2:0 1px 0 rgba(255,255,255,.03) inset, 0 8px 24px rgba(0,0,0,.4);
  --sh-3:0 16px 48px rgba(0,0,0,.6);
  --s-bg:#0E0E0F; --s-panel:#161617; --s-raised:#1D1D1F; --s-sunk:#121213; --s-line:#2E2D2F; --s-line-2:#232224;
  --s-line-hover:#4A4749;   /* v3.1120.0: the search box's hover border, one step lighter than --s-line */
  --s-ink:#F5F4F4; --s-ink-2:#D3D1D2; --s-muted:#A7A5A6; --ink-2:#D3D1D2;
  --s-link:#80A6FE; --s-link-strong:#B2CAFF;
  --s-hero:linear-gradient(120deg, #0038B8 0%, #002A8C 60%, #001E61 100%);
  --s-hero-ink:#FFFFFF; --s-hero-muted:#D6E3FF;
  --s-accent-soft:#12213F; --s-ok:#5FD08F; --s-ok-soft:#10261A; --s-warn:#FF9240; --s-warn-soft:#2A1A0C;
  --s-bad:#FF8A80; --s-bad-soft:#2B1412;
  --s-focus:0 0 0 3px rgba(76,131,254,.45);
  --ch1:#004EFE; --ch2:#A7A5A6; --ch3:#FF9240;
}

/* v3.1129.0 (2 October 2026) — LINE-CHART SERIES. Darren: "line graphs rather than bar graphs please." The lines on
   the KPI screens (public/kpicharts.js `lines`) take their colours from here, brand blue first, so dark mode is the
   tokens and nothing else. A LINE is ink, not a fill: on a dark card the blue fill colour (#004EFE) is 3:1, so the dark
   theme's first series is the lighter brand blue (5.6:1 on --panel); every dark series clears 4.5:1 on --panel. */
/* v3.1133.0 (2 October 2026) — THE LIBRARY FOLDER TONES. Darren: "If there is no image, can you please make the folder
   bigger and, for now, make each folder a different colour? Both the top-level folders and subfolders." Eight tones a
   folder tile can wear: a pale wash with ink on it, picked per folder by app.js libFolderView. Blue is the brand scale;
   the rest are the warm and green families the app already uses, so nothing reads as an off-brand blue. Every ink is
   at least 4.5:1 on its own wash in both themes (the darkest wash in dark mode is the card's own family). */
/* v3.1137.0 (3 October 2026) — THE APP FACE's glass (app.js APP_FACES, the block at the end of this file): white at an alpha
   on the brand band, like the Edge hero's chips. The band is the same in both themes, so dark does not redefine these. */
:root{--ax-glass:rgba(255,255,255,.14); --ax-glass-2:rgba(255,255,255,.26); --ax-glass-soft:rgba(255,255,255,.08);
  --ax-glass-line:rgba(255,255,255,.28); --ax-glass-line-2:rgba(255,255,255,.5); --ax-glow:rgba(255,255,255,.16); --ax-glow-0:rgba(255,255,255,0);
  --ax-tile-sh:0 6px 18px rgba(0,0,0,.14)}
:root{--fd1-bg:#EAF0FF; --fd1-ink:#0038B8; --fd2-bg:#FFF0E5; --fd2-ink:#B24C00; --fd3-bg:#E8F5EC; --fd3-ink:#18703F;
  --fd4-bg:#FDE8EF; --fd4-ink:#9E1F4F; --fd5-bg:#FFF6D6; --fd5-ink:#7A5A00; --fd6-bg:#F1F0F0; --fd6-ink:#4A4647;
  --fd7-bg:#EFF5DC; --fd7-ink:#4D6A00; --fd8-bg:#F6ECE6; --fd8-ink:#7A4A2E; --fd0-bg:#FAFAFA; --fd0-ink:#625E5F}
html[data-theme="dark"]{--fd1-bg:#12213F; --fd1-ink:#B2CAFF; --fd2-bg:#2A1A0C; --fd2-ink:#FFB27A; --fd3-bg:#10261A; --fd3-ink:#8FE0B0;
  --fd4-bg:#2B1220; --fd4-ink:#FF9EC0; --fd5-bg:#2A2208; --fd5-ink:#F2CF5B; --fd6-bg:#1D1D1F; --fd6-ink:#D3D1D2;
  --fd7-bg:#1C2410; --fd7-ink:#C2E07A; --fd8-bg:#2A1D15; --fd8-ink:#E6B596; --fd0-bg:#1A1A1B; --fd0-ink:#A7A5A6}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--fd1-bg:#12213F; --fd1-ink:#B2CAFF; --fd2-bg:#2A1A0C; --fd2-ink:#FFB27A; --fd3-bg:#10261A; --fd3-ink:#8FE0B0;
  --fd4-bg:#2B1220; --fd4-ink:#FF9EC0; --fd5-bg:#2A2208; --fd5-ink:#F2CF5B; --fd6-bg:#1D1D1F; --fd6-ink:#D3D1D2;
  --fd7-bg:#1C2410; --fd7-ink:#C2E07A; --fd8-bg:#2A1D15; --fd8-ink:#E6B596; --fd0-bg:#1A1A1B; --fd0-ink:#A7A5A6}}

:root{--kl1:#004EFE; --kl2:#E35F00; --kl3:#18703F; --kl4:#001E61; --kl5:#80A6FE; --kl6:#B42318; --kl7:#9A6B00; --kl8:#6A6667}
html[data-theme="dark"]{--kl1:#80A6FE; --kl2:#FF9240; --kl3:#5FD08F; --kl4:#D6E3FF; --kl5:#4C83FE; --kl6:#FF8A80; --kl7:#E4B400; --kl8:#A7A5A6}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--kl1:#80A6FE; --kl2:#FF9240; --kl3:#5FD08F; --kl4:#D6E3FF; --kl5:#4C83FE; --kl6:#FF8A80; --kl7:#E4B400; --kl8:#A7A5A6}}

/* ---------- global ---------- */
body{background:var(--bg);color:var(--ink);transition:background .2s,color .2s}
h1{color:var(--ink)}
/* v3.1119.0 — headings are ink (UI refresh v2); blue is for links and the one current thing. */
h2{color:var(--ink)}
.lead{color:var(--muted)}
a{color:var(--accent-ink)}
code{background:var(--panel-2);color:var(--ink)}
.muted{color:var(--muted)}
::placeholder{color:var(--grey)}

/* ---- ONE LOOK FOR EVERY FIELD YOU CAN TYPE IN (v3.350.0) ------------------------------------
   Darren, 2 Sep 2026: *"we need to do something about the UI for this and any other area that has
   search boxes and filters. They all need to be a consistent UI with larger field size and standard
   font and colours... The data entry fields should be the same as the rest of the text on the
   pages."*

   HE WAS LOOKING AT A REAL BUG, NOT A TASTE PROBLEM. The rule below used to enumerate the types it
   covered — text, email, password, number — and an input is only styled if its type is on that
   list. Counted across the suite, that left:

       15  input[type=search]
       56  input[type=date]
      186  <input> written with NO type attribute at all

   257 fields, every one of them falling back to the browser's own control: a different font, a
   different size, a different border, no radius. `input[type=text]` does not match `<input id="x">`
   — the attribute has to actually be there — which is why the bare ones were the biggest group and
   nobody had spotted a pattern.

   POSITIVE LIST, INSIDE :where(). The list says what a "field you can type in" is, which is easier
   to read than eight chained :not()s and cannot accidentally swallow a checkbox or a range slider.
   `:where()` contributes ZERO specificity, so the whole selector weighs exactly as much as bare
   `input` — every existing override in this file and in styles.css still wins without being touched,
   including the table-embedded number boxes and the switcher's own controls.

   THE SIZE IS THE BODY SIZE. body is 14px; a field you type into is now 14px too, in the app's own
   font. `select` was 13.5px and the UA default is about 13.33px in a system font, which is where
   "the data entry fields should be the same as the rest of the text" came from. */
:root{ --field-pad:9px 11px; --field-size:14px; --field-h:39px; }
input,select,textarea{color:var(--ink);background:var(--panel);border-color:var(--line)}
/* v3.1119.0 — fields are 10px round (--rad-in) with a blue focus ring (--s-focus), on the raised surface. */
.field span{color:var(--muted)}
input:where([type=text],[type=email],[type=password],[type=number],[type=search],[type=tel],
  [type=url],[type=date],[type=time],[type=datetime-local],[type=month],[type=week],:not([type])),
select,textarea{
  background:var(--s-raised,var(--panel-2));border:1px solid var(--line);border-radius:var(--rad-in);color:var(--ink);
  font-family:inherit;font-size:var(--field-size);line-height:1.35;padding:var(--field-pad)}
/* The two the browser draws its own furniture inside. Without an explicit height a date box sits a
   couple of pixels shorter than the text box beside it, which is exactly the raggedness in the
   screenshot; without `appearance:none` Safari draws a rounded pill regardless of the radius. */
/* The ones the browser draws its own furniture inside — a calendar button, a clear cross, a spin
   box. Their shadow content sets its own height, so a date box measured 41px beside a 39px text box:
   two pixels, on every filter bar in the suite, which is the raggedness in Darren's screenshot. The
   height is therefore stated rather than left to the widget, and `appearance:none` stops Safari
   drawing a rounded pill over the top of our radius. */
input:where([type=search],[type=date],[type=time],[type=datetime-local],[type=month],[type=week]){
  appearance:none;-webkit-appearance:none;height:var(--field-h)}
input:where([type=text],[type=email],[type=password],[type=number],[type=search],[type=tel],
  [type=url],:not([type])),select{min-height:var(--field-h)}
input:where([type=search])::-webkit-search-cancel-button{cursor:pointer}
.field input,.field select,.field textarea{
  background:var(--s-raised,var(--panel-2));border:1px solid var(--line);border-radius:var(--rad-in);color:var(--ink)}
input:focus,select:focus,textarea:focus{outline:0;border-color:var(--accent);
  box-shadow:var(--s-focus)}
input:disabled,select:disabled,textarea:disabled{opacity:.6;cursor:not-allowed}

/* ---------- buttons ---------- */
/* v3.1119.0 (UI refresh v2) — a raised button with a hairline and a 10px radius. PRIMARY IS THE
   BRAND BLUE WITH WHITE TEXT (5.96:1, and 7.17:1 on the hover shade) — it was orange with ink text.
   Orange is kept for the one attention action on a screen: `.btn.orange` / `.btn.attn` (ink on it,
   5.8:1; white on #FF6D00 would be 2.8:1), and the primary button on the brand header band. */
.btn{background:var(--s-raised,var(--panel));border:1px solid var(--line);color:var(--ink);border-radius:var(--rad-in);font-weight:600;box-shadow:var(--sh-1)}
.btn:hover{border-color:var(--accent);color:var(--accent-ink)}
.btn.primary{background:var(--b-500);border-color:var(--b-500);color:#fff;box-shadow:0 1px 2px rgba(0,78,254,.25),0 4px 12px rgba(0,78,254,.18)}
.btn.primary:hover{background:var(--b-600);border-color:var(--b-600);color:#fff}
.btn.orange,.btn.attn{background:var(--o-500);border-color:var(--o-500);color:#231F20;font-weight:700}
.btn.orange:hover,.btn.attn:hover{background:#FF7A1A;border-color:#FF7A1A;color:#231F20}
.btn.accent{background:var(--blue);border-color:var(--blue);color:#fff}
.btn.accent:hover{background:var(--blue);filter:brightness(1.08);color:#fff}
.btn.ghost{background:transparent;box-shadow:none}
.btn.ghost:hover{background:transparent;border-color:var(--accent);color:var(--accent-ink)}
.btn.danger{background:var(--danger);border-color:var(--danger);color:#fff}
.btn.block{display:block;width:100%;text-align:center}
.linkbtn{color:var(--accent-ink);background:none;border:0;cursor:pointer;font-weight:700;padding:0;font:inherit}

/* ---------- panels / cards / tables ---------- */
/* v3.1119.0 — ONE CARD (UI refresh v2): 14px, a hairline (--s-line-2), the soft --sh-1 shadow. */
.panel{background:var(--panel);border:1px solid var(--s-line-2);border-radius:var(--rad);box-shadow:var(--sh-1)}
.cards .card{background:var(--s-raised);border:1px solid var(--s-line-2);border-radius:var(--rad);box-shadow:var(--sh-1)}
.card .k{color:var(--muted)}
.card .v{color:var(--ink)}
.table-wrap{background:var(--panel);border:1px solid var(--s-line-2);border-radius:var(--rad)}
table{color:var(--ink)}
th,td{border-bottom:1px solid var(--s-line-2)}
thead th{background:var(--panel);color:var(--muted);border-bottom:1px solid var(--line)}
tbody tr:hover{background:var(--s-accent-soft,var(--panel-2))}
table.mini{background:var(--panel);border:1px solid var(--line)}
table.mini td:first-child{color:var(--muted)}
/* build-up: alternating rows + indented asset-group sub-rows */
#d-summary tr:nth-child(even) td{background:var(--zebra)}
#d-summary tr.subrow td{font-size:12.5px;color:var(--muted)}
#d-summary tr.subrow td:last-child{font-weight:600;color:var(--muted)}
td.total{color:var(--accent-ink)}
/* v3.425.0 — a list row you can click to open. The row's own buttons still do their own job. */
tr.row-open{cursor:pointer}
tr.row-open:hover>td{background:var(--hover,rgba(0,0,0,.03))}
/* v3.1137.0 (3 October 2026) — Darren: "we don't really need an open button. Just click on it to open." Every list row
   that opens an item (rowOpenAttrs in app.js) looks and behaves alike: a pointer, the hover tint, and a focus ring
   for the keyboard (Enter or Space opens it). The row's own small buttons keep the ordinary arrow. */
[data-rowopen]{cursor:pointer}
tr[data-rowopen]:hover>td,div[data-rowopen]:hover,li[data-rowopen]:hover{background:var(--panel-2)}
[data-rowopen]:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
[data-rowopen] [data-rowopen-go][hidden]{display:none !important}
.grid-scroll{background:var(--panel);border:1px solid var(--line);border-radius:var(--r)}
#emp-grid input,table.editable input{color:var(--ink)}
#emp-grid input:focus,table.editable input:focus{border-color:var(--accent);background:var(--panel)}
.autosave{color:var(--ok)}
.save-status{color:var(--muted)}

/* ---------- rate hero (results) ---------- */
.rate-hero{background:linear-gradient(135deg,var(--blue),var(--blue-dark));border-radius:var(--r);color:#fff}
html[data-theme="dark"] .rate-hero{background:linear-gradient(135deg,#0038B8,#001E61)}

/* =====================================================================
   APP SHELL — collapsible left sidebar
   ===================================================================== */
#app-shell{min-height:100vh;background:var(--bg)}
.appwrap{display:flex;min-height:100vh;align-items:stretch}
/* 100dvh, not 100vh, on a phone (v3.280.0). `vh` is the viewport WITHOUT the browser's own chrome,
   so on mobile Safari and Chrome the shell is taller than the visible area — which is why the page
   footer sat mid-screen with a band of empty background under it. `dvh` is the height you can
   actually see, and it changes as the address bar hides. */
@media(max-width:900px){ #app-shell,.appwrap{min-height:100dvh} }
.sidebar{width:238px;flex:0 0 auto;background:var(--panel);border-right:1px solid var(--s-line-2);
  display:flex;flex-direction:column;position:sticky;top:0;height:100vh;transition:width .18s}
.sidebar.collapsed{width:64px;overflow:visible}
.sb-top{position:relative;padding:12px 12px 14px;border-bottom:1px solid var(--line)}
.sb-collapse{position:absolute;top:10px;right:10px;width:24px;height:24px;border:1px solid var(--line);background:var(--panel);
  color:var(--muted);font-weight:800;cursor:pointer;border-radius:var(--r);line-height:1;z-index:2}
.sb-collapse:hover{color:var(--accent-ink);border-color:var(--accent)}
/* brand: full stacked logo when the menu is out, logomark when docked — blue in light, white in dark */
.sb-brand{display:flex;justify-content:center;align-items:center;padding-top:20px;min-height:78px}
.sb-brand .lg{display:none}
html[data-theme="light"] .sidebar:not(.collapsed) .lg-full-light{display:block;width:132px;height:auto}
html[data-theme="dark"]  .sidebar:not(.collapsed) .lg-full-dark{display:block;width:132px;height:auto}
html[data-theme="light"] .sidebar.collapsed .lg-icon-light{display:block;width:32px;height:auto}
html[data-theme="dark"]  .sidebar.collapsed .lg-icon-dark{display:block;width:32px;height:auto}
.sidebar.collapsed .sb-top{padding:10px 6px 12px}
.sidebar.collapsed .sb-brand{padding-top:26px;min-height:0}

.sb-scroll{overflow-y:auto;overflow-x:hidden;flex:1;padding-bottom:16px}
.sidebar.collapsed .sb-scroll{overflow:visible}
.sb-grp{padding:10px 10px 2px}
.sb-gl{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:700;padding:6px 8px 6px;white-space:nowrap}
.sidebar.collapsed .sb-gl{opacity:0;height:6px;padding:0;overflow:hidden}
/* ---- THE AREA'S OWN MENU (v3.349.0) ---------------------------------------------------------
   Every item is a job ("New customer"), not a screen ("Contact directory"), so the labels are
   longer than a tab's and the sidebar is ~180px. They wrap rather than truncate: a menu item cut
   off at "Search for an enq…" is a menu item somebody has to hover to read.
   No icons on purpose — six items that all mean "a customer" would need six icons that all mean
   the same thing, and a column of near-identical glyphs is slower to read than a column of words. */
#sb-areamenu:empty{display:none}
#sb-areamenu{margin:2px 0 6px}
/* ---- COLLAPSIBLE GROUPS (v3.350.0) ----------------------------------------------------------
   Darren: *"Please include all menus in a collapsible format so it pops up with submenus and
   doesn't take up the whole left menu."* Nine groups and up to thirty-three items — open, that is
   a wall; closed, it is nine lines and everything is one click away.
   `<details>` does the work, so the arrow, the keyboard behaviour and the state are the browser's. */
.am-grp{border-radius:var(--r)}
.am-grp+.am-grp{margin-top:1px}
/* `align-items:start` rather than centre: "Customers & contacts" is the one title that wraps to two
   lines in a 238px rail, and centred, its arrow and its count float to the middle of the block and
   look detached from the first line. Aligned to the top they stay with the word. */
/* v3.1119.0 (UI refresh v2) — GROUP NAMES IN SENTENCE CASE. They were 9.5px spaced capitals, which
   is how a label that nobody is meant to read looks; these are the menu. Each group carries a stroke
   icon (.am-gi, drawn by app.js decorateAreaMenu) on the left and a chevron (.am-chev) on the right
   that turns when the group opens. Only the group you are in is open (openGroup/markAreaMenu). */
.am-head{display:flex;align-items:center;gap:9px;cursor:pointer;list-style:none;
  padding:7px 10px;margin:1px 0;border-radius:var(--rad-in);color:var(--ink-2);
  font-size:13px;letter-spacing:0;text-transform:none;font-weight:600;user-select:none}
.am-head::-webkit-details-marker{display:none}
.am-head .am-gi{width:18px;height:18px;flex:none;color:var(--muted);stroke:currentColor;fill:none;
  stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.am-head .am-chev{width:14px;height:14px;flex:none;color:var(--muted);stroke:currentColor;fill:none;
  stroke-width:2;stroke-linecap:round;stroke-linejoin:round;transition:transform .15s;margin-left:-2px}
.am-grp[open]>.am-head .am-chev{transform:rotate(90deg)}
.am-grp[open]>.am-head .am-gi{color:var(--blue-ink)}
.am-head:hover{background:var(--s-sunk);color:var(--ink)}
.am-grp[open]>.am-head{color:var(--ink)}
.am-title{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* v3.1133.0 (2 October 2026) — WHAT IS WAITING, NOT HOW MANY ITEMS. Darren: "What I think it should show us is if something
   is new that we need to look at … similar to how the communications have unread communications listed." The same
   orange pill as the Communications unread count (.cm-badge), so the two read as one idea — with the brand ink on it
   rather than white (white on the brand orange is 2.9:1; the ink is 6.6:1, in both themes). Hidden when nothing is. */
.am-badge{flex:0 0 auto;font-size:11px;font-weight:700;color:var(--ink-brand);background:var(--orange);border-radius:999px;
  padding:1px 7px;min-width:20px;text-align:center;letter-spacing:0;line-height:16px}
.am-badge[hidden]{display:none}
.nav-badge{display:inline-block;margin-left:6px;min-width:18px;padding:0 6px;border-radius:999px;background:var(--orange);
  color:var(--ink-brand);font-size:11px;font-weight:700;line-height:17px;text-align:center;vertical-align:middle}
.sidebar.collapsed .tab .nav-badge{position:absolute;top:4px;right:6px;margin:0;min-width:16px;padding:0 4px;font-size:10px;line-height:15px}
.am-items{margin:2px 0 8px 19px;padding:0 0 0 8px;border-left:1px solid var(--line)}
.sb-grp .tab.am-item,.am-items .tab.am-item{padding-left:10px;white-space:normal;line-height:1.25;
  text-align:left;min-height:0;padding-top:6px;padding-bottom:6px;margin:1px 0}
/* The 2px brand-blue guide on the current item, laid over the group's hairline. */
.am-items .tab.am-item.active::after{content:"";position:absolute;left:-9px;top:6px;bottom:6px;width:2px;border-radius:2px;background:var(--b-500)}
.sb-grp .tab.am-item .lab,.am-items .tab.am-item .lab{white-space:normal}
.sidebar.collapsed .tab.am-item{white-space:nowrap}
/* COLLAPSED SIDEBAR: the group headings are the only thing that fits, and an accordion inside a
   72px rail is not usable — so the whole menu stands down and the pinned icon tabs take over. */
.sidebar.collapsed #sb-areamenu{display:none}
/* ---- A FILTER STRIP, THE SAME ON EVERY SCREEN THAT HAS ONE (v3.350.0) ------------------------
   Darren, 2 Sep 2026: *"we need to do something about the UI for this and any other area that has
   search boxes and filters. They all need to be a consistent UI."*

   `.filters` is the compact strip — a search box and two or three narrow controls on one line. Its
   sibling is `.bia-grid` in styles.css, which is the roomy auto-fit grid used by the 38 forms; both
   now take their field look from the same three tokens above, so the only difference between them
   is LAYOUT, which is the only thing that should differ.

   THE CAPTIONS MATCH `.bia-grid`'s — 13.5px, weight 600, in --muted. They were 12px/700 in --grey,
   which is why this strip read as a different kind of furniture from the form directly above it.

   THE SEARCH BOX GROWS AND THE REST DO NOT. `flex:1 1 260px` on the search label and `flex:0 0 auto`
   on the others: a date box has one right width and a search box wants whatever is going spare.
   Before this the labels were all `flex:0 1 auto` and the search box collapsed to its placeholder. */
.filters{display:flex;flex-wrap:wrap;gap:10px 12px;align-items:end;margin:0 0 12px}
.filters label{display:flex;flex-direction:column;gap:4px;flex:0 0 auto;
  font-size:13.5px;font-weight:600;color:var(--muted)}
.filters label:has(input[type=search]),.filters label.filters-wide{flex:1 1 260px;min-width:0}
.filters input,.filters select{min-width:0}
/* A button on a filter strip lines up with the boxes beside it. Left to itself `.btn` is 35px
   against a 39px field, which is the half-pixel raggedness that makes a bar look unfinished. */
.filters .btn{min-height:var(--field-h);align-self:end}
/* The line under the strip that says what the filter matched. Its own class so the strip and its
   answer cannot drift apart on different screens. */
.filters-note{margin:-4px 0 10px;color:var(--muted)}
/* ---- A CONSULTANT'S THREE NUMBERS (v3.349.0) -------------------------------------------------
   Auto-fit rather than four fixed columns: on a phone this is the first thing on the screen and
   two-up beats four squeezed. An unknown tile is toned down and its number is the WORD "unknown" —
   never a dash, which reads as nought to anybody who has seen a spreadsheet. */
.con-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px}
.con-stat{display:flex;flex-direction:column;gap:2px;padding:12px 14px;border:1px solid var(--line);border-radius:12px;background:var(--panel-2)}
.con-stat>b{font-size:26px;line-height:1.1;font-weight:800}
.con-stat-lab{font-size:12px;font-weight:700;color:var(--grey)}
.con-stat-unknown>b{font-size:15px;color:var(--danger)}
/* The capability grid when the form is showing the master's own details: the ticks are real and
   unchangeable, so they are dimmed rather than hidden — somebody looking for them should find them
   and read the reason, not conclude the screen is broken. */
.edge-capgrid.is-locked{opacity:.55}
.edge-capgrid.is-locked .edge-capbox{cursor:not-allowed}
/* ---- THE WAY BACK (v3.349.0) -----------------------------------------------------------------
   Above the screen's own heading, quiet, and it names where it goes. */
/* v3.1119.0 — the way back is now the BREADCRUMB's parent link (UI refresh v2): "Platinum Apps /
   Office / Office dashboard / Quotes", where "Office dashboard" is the button that goes back. On the
   brand band it is the pale blue; the current page is white and bold. */
.area-back{margin:0 0 6px}
.area-back .linkbtn{font-size:13px;font-weight:700;color:var(--grey)}
.area-back .linkbtn:hover{color:var(--ink)}
.crumbs{display:flex;align-items:center;flex-wrap:wrap;gap:4px 6px;font-size:12.5px;font-weight:500;
  color:var(--muted);margin:0 0 6px;letter-spacing:0;list-style:none;padding:0}
.crumbs>span,.crumbs>b,.crumbs>.linkbtn{white-space:nowrap}
.crumbs>i{font-style:normal;opacity:.7}
.crumbs>b{color:var(--ink);font-weight:650}
.crumbs .linkbtn{font-size:12.5px;font-weight:600;color:var(--blue-ink)}
.view-head .crumbs,.view-head .area-back .linkbtn{color:var(--s-hero-muted)}
.view-head .crumbs>b{color:#fff}
/* No underline (Darren's standing rule, v3.405.0): the link is the white, heavier crumb. */
.view-head .crumbs .linkbtn{color:#fff;font-weight:650}
.view-head .crumbs .linkbtn:hover,.view-head .area-back .linkbtn:hover{color:#fff;opacity:.85}

/* ---- MY DAY IS ONE DAY (v3.351.0) -----------------------------------------------------------
   Darren: *"My day should only show one day and obviously give it more space rather than showing
   the whole week."* The five-column desk grid in a 390px phone column wrapped every entry to four
   lines. One day, one column, room to read it. */
.md-daybar{display:flex;align-items:center;gap:10px;margin:0 0 10px}
.md-daybar .btn{flex:0 0 auto;min-width:44px;min-height:44px}   /* a real tap target */
.md-dayname{flex:1 1 auto;min-width:0;text-align:center;font-size:17px;line-height:1.25}
.md-today{display:block;font-size:11px;font-weight:800;letter-spacing:.12em;
  text-transform:uppercase;color:var(--orange)}
.md-day{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}
.md-item{display:flex;gap:12px;align-items:baseline;padding:12px 14px;border:1px solid var(--line);
  border-left:3px solid var(--blue);border-radius:var(--r);background:var(--panel-2)}
/* A diary entry is somebody else's commitment, not ours — quieter, and the row says so. */
.md-item.md-diary{border-left-color:var(--grey-mid);background:transparent}
.md-when{flex:0 0 auto;min-width:72px;font-weight:800;font-variant-numeric:tabular-nums}
.md-what{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:2px}
.md-what b{font-weight:700;line-height:1.3}
@media(max-width:420px){
  .md-item{flex-direction:column;gap:2px}
  .md-when{min-width:0}
}

/* nav item — reuse .tab so app.js showView() still lights it up */
/* v3.1119.0 (UI refresh v2) — a sidebar item is a rounded row; the CURRENT one is a soft blue pill
   (--s-accent-soft) with blue text (5.2:1 light, 6.7:1 dark), not a panel with a 3px left rule. */
.sidebar .tab,.sidebar .navx{display:flex;align-items:center;gap:11px;width:100%;padding:8px 10px;font-size:13px;
  font-weight:500;color:var(--ink-2);background:transparent;border:0;border-left:0;border-radius:var(--rad-in);
  cursor:pointer;white-space:nowrap;position:relative;text-align:left;transition:none;font-family:inherit}
.sidebar .tab:hover,.sidebar .navx:hover{color:var(--ink);background:var(--s-sunk);transform:none;box-shadow:none}
.sidebar .tab svg,.sidebar .navx svg{width:16px;height:16px;flex:0 0 auto;stroke:currentColor;fill:none;
  stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;opacity:.85}
.sidebar .tab .lab,.sidebar .navx .lab{overflow:hidden;text-overflow:ellipsis;white-space:normal;line-height:1.25;text-align:left}
.sidebar .tab.active{color:var(--blue-ink);background:var(--s-accent-soft);font-weight:650;box-shadow:none;transform:none}
.sidebar .tab.active svg{opacity:1}
.sidebar .tab .tab-ico{display:none}         /* hide the old emoji glyphs */
.sidebar .tab .star{margin-left:auto;color:var(--orange);font-size:12px}
/* ---- THE MEMBERSHIP TAB'S PILL AND STAR (v3.188.0) --------------------------------------------
   IN THIS SHEET ON PURPOSE. The pill was written in styles.css, and the rule directly above —
   `.sidebar .tab .star`, in theme.css, which loads later and carries three classes — beat its
   `color:#fff`. So "Join" rendered orange on an orange pill and Darren saw a blank orange oval. Same
   trap as the group headings a few releases ago: the rule existed and lost. Anything that decorates
   .star belongs here, under the rule it has to beat.

   AND THE STAR TAKES THE TIER COLOUR (Darren's suggestion). A member's tab should look like the tier
   they are on — it is the one place the membership is visible from every app. Bronze/silver/gold read
   as themselves; Platinum is the blue of the brand rather than a grey that would look unpaid. */
.sidebar .tab.edge-join .star{color:#fff;background:var(--orange);border-radius:9px;padding:1px 7px;
  font-size:10.5px;font-weight:800;letter-spacing:.02em;line-height:1.5}
/* v3.1139.0 — bronze darkened from #A0662A: 4.16:1 on the active tab's #EAF0FF, under the 4.5:1 the accessibility walk
   (test/v31111a11y) requires. #8F5A22 is 5.04:1 there and still reads as bronze. */
.sidebar .tab.edge-tier-bronze .star{color:#8F5A22}
.sidebar .tab.edge-tier-silver .star{color:#5F6A7D}   /* v3.1139.0 — was #8A94A6 (2.9:1); 4.5:1+ on the active tab */
.sidebar .tab.edge-tier-gold .star{color:#86650B}   /* v3.1139.0 — was #C79A1E (2.4:1); 4.5:1+ on the active tab */
.sidebar .tab.edge-tier-platinum .star{color:var(--accent-ink)}
.sidebar .tab.signout{color:var(--muted);margin:0}
.sidebar .tab.signout:hover{color:var(--danger);background:var(--s-sunk)}
.sidebar.collapsed .tab,.sidebar.collapsed .navx{justify-content:center;gap:0;padding:11px 0}
.sidebar.collapsed .tab .lab,.sidebar.collapsed .navx .lab,.sidebar.collapsed .tab .star{display:none}
.sidebar.collapsed .tab svg,.sidebar.collapsed .navx svg{width:18px;height:18px}
/* Menu explainer card (JS-driven, ui.js) — works expanded AND docked, replaces the old
   CSS attr() tooltip so we never show two tooltips at once. */
.sb-tipcard{position:fixed;z-index:60;max-width:250px;background:var(--panel);border:1px solid var(--line);
  border-left:3px solid var(--accent);padding:9px 11px;border-radius:var(--r);box-shadow:0 8px 24px rgba(0,0,0,.18);
  pointer-events:none;opacity:0;transform:translateX(-4px);transition:opacity .12s ease,transform .12s ease}
.sb-tipcard.on{opacity:1;transform:none}
.sb-tipcard .t{font-size:12px;font-weight:800;color:var(--ink)}
.sb-tipcard .d{font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.45}

/* want-a-hand (grey, not orange) */
.sb-hand{margin:12px 12px 4px;border:1px solid var(--line);background:var(--panel);padding:11px 12px;border-radius:var(--r)}
.sb-hand .t{font-size:12px;font-weight:800;color:var(--ink)}
.sb-hand .d{font-size:11.5px;color:var(--muted);margin:3px 0 9px}
.sb-hand .sb-book{display:block;width:100%;text-align:center;background:var(--panel-2);border:1px solid var(--line);
  color:var(--ink);font-weight:800;font-size:11.5px;padding:8px;border-radius:var(--r);cursor:pointer;font-family:inherit}
.sb-hand .sb-book:hover{border-color:var(--ink)}
.sb-hand .sb-book .bk-ico{display:none}
.sidebar.collapsed .sb-hand{margin:12px 8px;padding:0;border:0;background:none}
.sidebar.collapsed .sb-hand .t,.sidebar.collapsed .sb-hand .d{display:none}
.sidebar.collapsed .sb-hand .sb-book{padding:9px 0;position:relative}
.sidebar.collapsed .sb-hand .sb-book .bk-lab{display:none}
.sidebar.collapsed .sb-hand .sb-book .bk-ico{display:inline-block;width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle}
/* (docked book-a-session tooltip now handled by the .sb-tipcard explainer too) */
.sb-grp.you{border-top:1px solid var(--line);margin-top:8px;padding-top:10px}
/* ---- A GROUP WITH NO HEADING IS A CONTINUATION (v3.280.0) ------------------------------------
   The sidebar went from ten headings to four, and four of those ten had exactly ONE button under
   them — a heading over a single item costs a row of chrome and buys nothing, which is most of why
   the menu read as enormous. Merging the WORDS is only half of it: the groups underneath still
   carried the gap that separates one heading from the next, so the same ten visual blocks remained
   with six of them unlabelled, which is worse than before. `.sb-cont` closes the gap so a run reads
   as one list. */
.sb-grp.sb-cont{margin-top:0;padding-top:0;border-top:0}

/* main column */
.mainwrap{flex:1;min-width:0;display:flex;flex-direction:column}
/* ---- THE TOP BAR IS THE SAME COLOUR AS THE LEFT ONE (0220, v3.593.0) -------------------------
   Darren, 10 September 2026: *"The left bar is good, and I think we should have a bar across the
   top where the title goes. You've got a bar at the left and a bar across the top that is the same
   colour, and depending on if it's light or dark mode, we put white writing or black writing."*

   `--panel-2` is what `.sidebar` is painted in, so this is literally the same colour rather than a
   second one chosen to match — the two cannot drift. Both tokens are theme-aware, so the writing
   follows the mode without a rule per mode: `--ink` is near-black on light and near-white on dark.
   It is set EXPLICITLY here rather than inherited, because a bar that changes its background has to
   own its foreground or a theme change moves one and not the other. */
/* v3.1119.0 — FROSTED (UI refresh v2): the page shows faintly through the bar as it scrolls under,
   so the bar reads as part of the page rather than a second band. Still the sidebar's own token
   (--panel, see the note above), and a browser with no backdrop-filter gets it opaque. */
.apptop{display:flex;align-items:center;gap:12px;padding:12px 22px;border-bottom:1px solid var(--s-line-2);background:var(--panel);color:var(--ink);position:sticky;top:0;z-index:15}
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .apptop{background:color-mix(in srgb,var(--panel) 86%,transparent);-webkit-backdrop-filter:saturate(1.4) blur(10px);backdrop-filter:saturate(1.4) blur(10px)}
}
.apptop .btn{border-radius:var(--rad-pill)}
.apptop-title{font-weight:800;font-size:15px;color:var(--ink)}
/* ---- "HIDE THIS" AND THE `?` IT GOES INTO (v3.587.0) --------------------------------------------
   Darren: *"it then grabs it and pushes it into a question mark up the top near the title."* The dot
   is `hidden` until this screen actually has something in it — a `?` that opens on "nothing is
   hidden" is furniture, and this bar has been trimmed twice already for exactly that. */
.apptop-help{width:22px;height:22px;flex:0 0 auto;margin-left:8px;border-radius:50%;
  border:1px solid var(--line);background:var(--panel);color:var(--blue-dark-ink);
  font-weight:800;font-size:12px;line-height:1;cursor:pointer;display:inline-flex;
  align-items:center;justify-content:center}
.apptop-help:hover{background:var(--accent);color:#fff;border-color:var(--accent)}
/* The bar sits UNDER the prose it belongs to and is quiet: a control offering to remove text must
   not compete with the text for attention. Right-aligned so it does not sit under the reader's eye
   at the start of the next line. */
.xpl-bar{display:flex;justify-content:flex-end;margin:-2px 0 6px}
.xpl-hide{font-size:12px;font-weight:600;opacity:.62}
.xpl-hide:hover{opacity:1;text-decoration:underline}
.xpl-wrap{margin:0}
/* Inside the `?`: each hidden block under the panel it came from, each with its own way back. */
.xpl-item{border-top:1px solid var(--line);padding:10px 0}
.xpl-item:first-child{border-top:0;padding-top:0}
.xpl-item-h{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
.xpl-item-b{margin-top:4px}
.xpl-item-b > :last-child{margin-bottom:0}
/* ---- THE SWITCH ROW IS NOT ONE OF THE HIDDEN BLOCKS (0239, v3.612.0) -------------------------
   It sits in the same list so both answers are in one place, and it has to read as a control rather
   than as a fourth paragraph somebody hid. A bottom rule and a little breathing room does it; the
   rest of the list keeps its top rules, so the two groups are visibly separate without a heading
   that would say "Settings" over a list of one thing.
   ITS OWN `.hint` IS NEVER `.expl`. The sentence explaining the switch that hides the sentences
   cannot be one of the things it hides — press it once and the button would lose its label's
   explanation with no way to read what it does. */
.xpl-switch{border-top:0;border-bottom:1px solid var(--line);padding-bottom:12px;margin-bottom:4px}
.xpl-switch + .xpl-item{border-top:0}
/* ---- 0592, v3.831.0 — THE ONE SEARCH BOX -------------------------------------------------------
   Darren: *"in the top bar, in the empty space, can we have a search box."* It takes the empty
   space and gives it back: `flex:1 1 auto` with a `max-width`, so it grows into the gap on a wide
   screen without pushing the workspace switcher off a narrow one. The results panel is `absolute`
   under it and the bar is `position:relative` already. */
.apptop-find{position:relative;flex:1 1 320px;min-width:0;max-width:520px;margin-left:18px;
  display:flex;align-items:center;gap:10px}
.find-q{flex:1 1 auto;min-width:0;padding:7px 12px;border:1px solid var(--line);border-radius:999px;
  background:var(--panel);color:var(--ink);font:inherit;font-size:13px}
.find-q:focus{outline:2px solid var(--blue);outline-offset:1px;border-color:var(--blue)}
.find-exact{display:flex;align-items:center;gap:4px;font-size:12px;color:var(--muted);
  white-space:nowrap;cursor:pointer}
/* ---- FIXED, NOT ABSOLUTE, AND THE REASON IS ON THE BAR ABOVE IT ------------------------------
   `.apptop` is `overflow:hidden` below 900px (added in v3.280.0 to stop the workspace switcher
   running off the right edge), and a box that scrolls or clips in one axis clips in BOTH. An
   absolutely-positioned panel inside it is cut off at the bar — which is precisely the fault 0525
   spent a release on for the quotes row menu: *"the dropdown options show behind the scroll."*
   So the panel is pinned to the viewport from the input's own rect (see `findPin` in app.js), which
   no ancestor's overflow can reach. */
.find-pop{position:fixed;z-index:60;max-height:70vh;
  overflow:auto;background:var(--panel);border:1px solid var(--line);border-radius:12px;
  box-shadow:0 12px 32px rgba(0,0,0,.18);padding:6px}
.find-group{padding:8px 10px 2px;font-size:11px;font-weight:800;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted)}
.find-hit{display:block;width:100%;text-align:left;padding:7px 10px;border:0;border-radius:8px;
  background:none;color:inherit;font:inherit;cursor:pointer}
.find-hit:hover,.find-hit.on{background:var(--blue-lt)}
.find-hit b{display:block}
.find-hit .hint{font-size:12px}
.find-note{padding:10px;color:var(--muted);font-size:13px}
/* A source that did not answer is LOUD. A search missing one of its five kinds silently is how
   somebody concludes a record does not exist. */
.find-missing{padding:8px 10px;margin:4px;border-radius:8px;background:var(--orange-soft);
  color:var(--ink);font-size:12px}
.apptop-right{margin-left:auto;display:flex;align-items:center;gap:10px;position:relative}
/* ---- HEADER ORDER, LEFT TO RIGHT (v3.396.0) --------------------------------------------------
   Darren, 5 Sep 2026: *"Move the phone next to the light/dark toggle. To the left of the phone, the
   app switcher; to the left of that, the workspace selector."* The buttons are inserted at different
   times (ws-switch is static, the app switcher and the phone add themselves), so the order is set
   here rather than fought over at insertion: workspace · app switcher · phone · theme. */
.apptop-right>*{order:5}
.apptop-right .run-flag,.apptop-right .run-flag-menu{order:0}
.apptop-right .ws-switch{order:1}
.apptop-right .apps-btn{order:2}
.apptop-right .ph-btn{order:3}
.apptop-right .theme-toggle{order:4}

/* "Did you know?" rotating tip strip — sits just under the top bar. */
/* v3.1119.0 — a quiet line under the bar, not a second header. */
.tipbar{display:flex;align-items:center;gap:9px;padding:6px 22px;font-size:var(--fs-sm);
  background:transparent;border-bottom:1px solid var(--s-line-2);color:var(--muted)}
.tipbar .tb-emoji{flex:none}
.tipbar .tb-lbl{flex:none;font-weight:700;color:var(--muted);text-transform:uppercase;font-size:10.5px;letter-spacing:.06em}
.tipbar .tb-body{flex:1;min-width:0;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tipbar .tb-body .linkbtn{font-size:12.5px}
.tipbar .tb-ctl{flex:none;display:flex;gap:2px}
.tipbar .tb-ctl button{background:none;border:0;color:var(--muted);cursor:pointer;font-size:12px;padding:2px 6px;border-radius:var(--r);font-family:inherit}
.tipbar .tb-ctl button:hover{color:var(--ink);background:var(--panel)}
@media print{.tipbar{display:none}}

/* chart value labels (what-if bars/sweep) */
.ch-val{font-size:12px;font-weight:800;fill:var(--ink)}

/* Company chip — the "which Simpro company am I touching?" highlight. Deliberately loud. */
.co-chip{display:inline-flex;align-items:center;gap:6px;background:var(--orange-soft);border:1.5px solid var(--orange);
  color:var(--orange);font-weight:800;font-size:14px;padding:5px 12px;border-radius:var(--r);white-space:nowrap}
.co-chip.sm{font-size:12px;padding:2px 8px;border-width:1px}
.co-banner{border-left:4px solid var(--orange)}
.co-banner-line{margin:0 0 10px;display:flex;align-items:center;gap:10px;flex-wrap:wrap}
/* Selects everywhere inherit the app font (a raw <select> falls back to the UA font). This rule sits
   BELOW the shared field rule and weighs the same, so it used to win and hold every dropdown in the
   suite at 13.5px/8px — 35px tall next to a 39px input. It now defers to the same three tokens, and
   only says the things that are genuinely select-specific. */
select{font-family:inherit;font-size:var(--field-size);color:var(--ink);background:var(--panel-2);
  border:1px solid var(--line);border-radius:var(--r);padding:var(--field-pad)}
/* ---- EVERY DROPDOWN HOLDS STILL (v3.1137.0, 3 October 2026) ------------------------------------------------------
   Darren, on the Enquiries bulk bar: "This is also doing the rapid expansion/contraction thing. Can you go through all
   the areas and make sure every dropdown doesn't do this anymore? This is the second one." (The first was the Health
   Check's "Which health check", v3.1134.0.) ONE RULE FOR ALL OF THEM: a select never animates, never changes its box
   when it is focused, pressed or hovered (the focus ring is a box-shadow, which takes no room), sizes to a fixed box
   rather than to what is chosen (`field-sizing:fixed` — the default today, stated so a future default cannot change
   it under us), and a long option is cut with an ellipsis inside the box instead of widening the row. The sweep of every
   select on every screen is docs/handover/DROPDOWNS-v3.1137.md. */
select,select:focus,select:focus-visible,select:hover,select:active{transition:none;animation:none;transform:none;
  field-sizing:fixed;text-overflow:ellipsis;box-sizing:border-box}
select:focus,select:focus-visible{outline-offset:0}
/* The directory's tag filter fills itself from the server: one width, so the list arriving cannot widen the row. */
#dir-tag{width:13em;max-width:100%}
.co-select{padding:4px 8px}
.co-select.big{font-size:15px;font-weight:700;padding:10px 12px}
/* ---- AND THE OPTIONS INSIDE THEM, WHICH color-scheme DOES NOT REACH (v3.489.0) --------------
   Darren, 8 Sep 2026, with the enquiry picker open in dark mode: *"when in dark mode and selecting
   an inquiry, we can't read them."* Exactly right — a white dropdown panel with near-white text on
   it, so the six enquiries were invisible.
   `html[data-theme="dark"]{color-scheme:dark}` above is what fixes a date picker's own panel, and it
   is NOT enough here: once a <select> carries an author `background`, Chrome paints the popup with
   the AUTHOR's colours rather than the scheme's — and the only author colour it had was the
   inherited near-white `--ink` on the UA's default white. Two properties, and every native dropdown
   in the app is right in both themes.
   `--panel` and not `--panel-2`: the popup floats above the field, and the field is already
   `--panel-2`, so the lighter of the two reads as a layer on top rather than as more of the same. */
option,optgroup{font-family:inherit;background:var(--panel);color:var(--ink)}

/* custom company switcher dropdown (replaces the native <select> popup) */
.co-switch-btn{background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r);color:var(--ink);
  font-family:inherit;font-size:12px;font-weight:700;padding:4px 10px;cursor:pointer}
.co-switch-btn:hover{border-color:var(--accent);color:var(--accent-ink)}
.co-menu{position:fixed;z-index:80;min-width:260px;max-height:340px;overflow:auto;background:var(--panel);
  border:1px solid var(--line);border-radius:var(--r);box-shadow:0 14px 40px rgba(0,0,0,.28);padding:5px}
.co-menu .co-row{display:flex;align-items:center;gap:9px;width:100%;text-align:left;background:none;border:0;
  padding:9px 11px;border-radius:var(--r);cursor:pointer;font-family:inherit;font-size:13.5px;color:var(--ink)}
.co-menu .co-row:hover{background:var(--panel-2)}
.co-menu .co-row.cur{background:var(--accent-soft)}
.co-menu .cn{flex:1;font-weight:600}
.co-menu .ck{font-size:11px;font-weight:800;color:var(--accent-ink);white-space:nowrap}

/* connect-flow visible company list */
.co-list{display:flex;flex-direction:column;gap:6px}
.co-option{display:flex;align-items:center;gap:10px;border:1px solid var(--line);background:var(--panel);
  border-radius:var(--r);padding:10px 12px;cursor:pointer;font-size:14px}
.co-option:hover{border-color:var(--accent)}
.co-option:has(input:checked){border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}
.co-option input{accent-color:var(--accent);flex:none}
.co-option .cn{font-weight:700;color:var(--ink)}

/* App switcher (▦ + label in the top bar) */
.apps-btn{border:1px solid var(--line);background:var(--panel);border-radius:var(--r);cursor:pointer;
  height:32px;padding:0 11px;display:inline-flex;align-items:center;gap:7px;color:var(--muted);font-family:inherit}
.apps-btn .apps-ico{font-size:15px;line-height:1}
.apps-btn .apps-lab{font-size:12px;font-weight:700;white-space:nowrap}
.apps-btn:hover{color:var(--accent-ink);border-color:var(--accent)}
@media(max-width:760px){.apps-btn .apps-lab{display:none}}
/* The menu grows with the app registry and is position:fixed, so past a certain number of
   apps it simply ran off the bottom of the screen with nothing to scroll — on a 720px-tall
   window the last two rows (both FREE apps, including the Catalogue Health Check front door)
   were invisible and unreachable. Height is capped to the space below the button by
   JS (--apps-max), with this as the floor-level fallback. */
.apps-menu{position:fixed;z-index:70;width:330px;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  box-shadow:0 14px 40px rgba(0,0,0,.25);padding:6px;
  max-height:var(--apps-max,calc(100vh - 72px));overflow-y:auto;overscroll-behavior:contain}
/* color:var(--ink) is not decoration — a <button> with no explicit colour inherits the UA
   default `buttontext` (black), which is what made the monochrome grid icon invisible on the
   dark panel while the emoji rows beside it looked fine. Anything drawn in currentColor
   inside a row needs this. */
.apps-menu .app-row{display:flex;align-items:center;gap:10px;width:100%;text-align:left;background:none;border:0;
  padding:10px;border-radius:var(--r);cursor:pointer;font-family:inherit;color:var(--ink)}
.apps-menu .app-row:hover{background:var(--panel-2)}
.apps-menu .app-row.here{background:var(--accent-soft)}
.apps-menu .ai{font-size:20px;flex:none;line-height:1}
/* The grid mark is the only non-emoji icon in the list, so give it the accent colour: it
   reads clearly in both themes and marks the "back to all apps" row as the odd one out
   on purpose rather than by accident. */
.apps-menu .ai-grid{display:inline-flex;align-items:center;justify-content:center;width:20px;color:var(--accent-ink)}
.apps-menu .app-row.here .ai-grid{color:var(--ink)}
/* The header switcher button already sets color:var(--muted) on itself, so its copy of the
   grid only needs sizing — the SVG carries width/height attributes that would otherwise
   ignore the old font-size:15px and render a couple of pixels large. */
.apps-btn .apps-ico{display:inline-flex;align-items:center;justify-content:center;line-height:1}
.apps-btn .apps-ico svg{width:15px;height:15px}
.apps-menu .ab{flex:1;min-width:0}
.apps-menu .ab b{display:block;font-size:13px;color:var(--ink)}
.apps-menu .ab small{display:block;font-size:11.5px;color:var(--muted);line-height:1.35;margin-top:1px}
.apps-menu .as{flex:none;font-size:11px;font-weight:800;color:var(--accent-ink);white-space:nowrap}
.apps-menu .apps-foot{font-size:10.5px;color:var(--muted);padding:7px 10px 4px;border-top:1px solid var(--line);margin-top:4px}

/* ▦ Apps home — the hub's launcher landing */
.apps-home{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px;margin-top:6px}
.apph-card{display:flex;flex-direction:column;align-items:flex-start;gap:6px;text-align:left;cursor:pointer;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:18px 16px 14px;font-family:inherit;
  transition:transform .12s ease,border-color .12s ease,box-shadow .12s ease}
.apph-card:hover{transform:translateY(-2px);border-color:var(--accent);box-shadow:0 10px 26px rgba(0,0,0,.14)}
.apph-card .apph-ico{font-size:30px;line-height:1}
.apph-card .apph-name{font-size:15px;font-weight:800;color:var(--ink)}
.apph-card .apph-desc{font-size:12.5px;color:var(--muted);line-height:1.45;flex:1}
.apph-card .apph-go{font-size:12px;font-weight:800;color:var(--accent-ink);margin-top:4px}
.apph-card.locked{opacity:.85}
.apph-card.locked .apph-go{color:var(--muted)}

/* 🚐 vehicle-fleet tick on an asset group */
.asset-group-head .ag-veh{font-size:11.5px;color:var(--muted);white-space:nowrap;display:inline-flex;align-items:center;gap:5px;margin:0}
.asset-group-head .ag-veh input{accent-color:var(--accent)}
#app-shell main{padding:22px 24px}   /* full width — no cap */

/* workspace switch (kept #ws-switch) */
.ws-switch .ws-btn{background:var(--panel);border:1px solid var(--line);color:var(--ink);border-radius:var(--r);font-weight:700}

/* ---------- theme toggle ---------- */
.theme-toggle{display:inline-flex;align-items:center;gap:0;border:1px solid var(--line);background:var(--panel);
  border-radius:var(--r);cursor:pointer;padding:0;overflow:hidden;height:32px}
.theme-toggle .tt-ico{width:34px;height:30px;display:flex;align-items:center;justify-content:center;font-size:14px;color:var(--muted)}
html[data-theme="light"] .theme-toggle .tt-sun{background:var(--accent);color:#fff}
html[data-theme="dark"] .theme-toggle .tt-moon{background:var(--accent);color:#0E0F13}

/* =====================================================================
   SIGN-IN / SUBSCRIBE — frosted dark, matches the website
   ===================================================================== */
.login-gate{background:var(--bg);min-height:100vh;display:flex;flex-direction:column;align-items:center;
  justify-content:center;padding:28px 20px;position:relative}
.login-gate .theme-toggle{position:fixed;top:18px;right:18px;z-index:30}
.login-shell{display:grid;grid-template-columns:1.05fr .95fr;width:100%;max-width:1060px;border:1px solid var(--line);
  background:var(--panel);overflow:hidden;border-radius:var(--r);box-shadow:0 20px 60px rgba(0,0,0,.18);flex-shrink:0}

/* hero pane — blue brand in light, dark glow in dark */
.login-hero{padding:36px 34px;position:relative;min-height:520px;display:flex;flex-direction:column;color:#EAF0FF;border:0}
html[data-theme="light"] .login-hero{background:linear-gradient(160deg,#004EFE,#002A8C)}
html[data-theme="dark"] .login-hero{background:
  radial-gradient(120% 90% at 0% 0%, rgba(0,78,254,.30), transparent 55%),
  radial-gradient(120% 120% at 100% 100%, rgba(255,109,0,.16), transparent 50%), #0E0F13}
.lh-brand{display:flex;align-items:center;gap:12px;margin-bottom:22px}
.lh-logochip{width:40px;height:40px;background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.25);
  display:flex;align-items:center;justify-content:center;border-radius:var(--r)}
.lh-logochip img{max-width:26px;max-height:26px}
.lh-kicker{font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:#D6E3FF;font-weight:800}
html[data-theme="dark"] .lh-kicker{color:#6C6F7B}
.lh-rotator{position:relative;flex:1}
.lh-slide{position:absolute;inset:0;opacity:0;transition:opacity .5s;pointer-events:none;display:flex;flex-direction:column}
.lh-slide.is-active{opacity:1;position:relative;pointer-events:auto}
.lh-tag{font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:#D6E3FF;font-weight:800}
.lh-headline{font-size:27px;line-height:1.14;letter-spacing:-.02em;margin:10px 0 0;font-weight:800;color:#fff}
.lh-headline em{font-style:italic;color:var(--orange)}
.lh-sub{color:#D6E3FF;font-size:14px;margin:12px 0 0;max-width:360px}
html[data-theme="dark"] .lh-sub{color:#9A9DA9}
/* frosted screenshot box */
.lh-shot{margin:18px 0 0;padding:12px;border:1px solid rgba(255,255,255,.30);background:rgba(255,255,255,.12);
  backdrop-filter:blur(3px);border-radius:var(--r)}
html[data-theme="dark"] .lh-shot{border-color:rgba(255,255,255,.14);background:rgba(255,255,255,.06)}
.lh-shot img{width:100%;display:block;border-radius:3px;opacity:.96}
.lh-shot figcaption{font-size:11.5px;color:#E3EBFF;margin-top:8px}
html[data-theme="dark"] .lh-shot figcaption{color:#9A9DA9}
.lh-dots{display:flex;gap:6px;margin-top:16px}
.lh-dot{width:20px;height:3px;background:rgba(255,255,255,.3);border:0;padding:0;cursor:pointer;border-radius:0}
.lh-dot.on{background:#fff}
html[data-theme="dark"] .lh-dot.on{background:var(--blue-2)}
.lh-foot{margin-top:16px}
.lh-stats{font-size:12px;color:#D6E3FF;display:flex;gap:8px;flex-wrap:wrap}
html[data-theme="dark"] .lh-stats{color:#9A9DA9}
.lh-stats b{color:#fff}
.lh-stats .dot{opacity:.5}
.lh-tagline{font-size:12px;color:#D6E3FF;margin-top:6px;font-weight:700}

/* card pane — follows theme */
.login-card{background:var(--panel);padding:38px 34px;display:flex;flex-direction:column;justify-content:center;overflow-y:auto}
.login-logo{display:none}                 /* logo already on the hero pane */
.login-card h1{font-size:20px;font-weight:800;color:var(--ink);margin:0 0 4px}
.login-sub{color:var(--muted);font-size:13px;margin:0 0 18px}
.login-card .field{margin-bottom:13px;display:block}
.login-card .field span{display:block;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--grey);font-weight:800;margin-bottom:6px}
/* …but NOT inside the plan picker — those are card content, not field labels/inputs.
   Two inherited rules broke the mobile signup: `.login-card .field input{width:100%;padding}`
   stretched each RADIO into a giant empty box, and `.login-card .field span{block;uppercase}`
   turned the card text into uppercase columns spilling off-screen. Undo both, then restyle. */
.login-card .field .plan-picker input{width:auto;padding:0;margin:3px 0 0;flex:0 0 auto}
.login-card .field .plan-picker span{display:inline;font-size:13px;letter-spacing:normal;text-transform:none;font-weight:400;color:var(--ink);margin:0}
.login-card .field .plan-picker .plan-opt{display:flex;align-items:flex-start;gap:10px}
.login-card .field .plan-picker .plan-opt-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}
.login-card .field .plan-picker .plan-opt-name{font-weight:800;font-size:14px;color:var(--ink)}
.login-card .field .plan-picker .plan-plus{display:inline-block;background:var(--orange);color:#fff;font-size:10px;font-weight:800;letter-spacing:.04em;padding:1px 6px;border-radius:var(--r);vertical-align:middle}
.login-card .field .plan-picker .plan-opt-price{font-size:13.5px;color:var(--ink);font-weight:600}
.login-card .field .plan-picker .plan-opt-then{color:var(--muted);font-weight:400}
.login-card .field .plan-picker .plan-opt-blurb{font-size:12px;color:var(--muted)}
.login-card .field .plan-picker .plan-perk{display:inline-block;align-self:flex-start;margin-top:6px;background:var(--orange-soft);color:var(--orange);font-weight:700;font-size:12px;padding:4px 9px;border-radius:var(--r);border:1px solid rgba(255,109,0,.28)}
.login-card .field .plan-picker .plan-opt.sel{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}
.login-card .field input{width:100%;padding:11px 13px;font-size:14px}
.login-actions{display:flex;gap:10px;margin-top:6px}
.login-actions .btn{flex:1}
.login-msg{font-size:13px;margin:12px 0 0}
.login-fine{font-size:12px;color:var(--muted);margin-top:12px}
.login-welcome{border:1px solid var(--line);background:var(--panel-2);padding:12px 14px;border-radius:var(--r);margin-bottom:14px;font-size:13px;color:var(--ink)}
.or-divider{display:flex;align-items:center;text-align:center;color:var(--grey);font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:800;margin:18px 0 14px;gap:12px}
.or-divider::before,.or-divider::after{content:"";flex:1;height:1px;background:var(--line)}
.signup-toggle .st-line{font-size:13px;color:var(--muted);margin:6px 0 10px}
.signup-toggle .st-fine{font-size:12px;color:var(--grey);margin-top:8px}
.login-footer{margin-top:22px;color:var(--muted);font-size:12px;text-align:center}
.login-footer a{color:var(--accent-ink)}

/* plan picker */
.plan-opt{border:1px solid var(--line);background:var(--panel);border-radius:var(--r);color:var(--ink)}
.plan-opt.sel{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}
.plan-plus{background:var(--orange);color:#151316}
.plan-perk{display:inline-block;align-self:flex-start;margin-top:6px;background:var(--orange-soft);color:var(--orange);
  font-weight:700;font-size:12px;padding:4px 9px;border-radius:var(--r);border:1px solid rgba(255,109,0,.28)}
.plan-solo{border:1px solid var(--line);background:var(--panel-2);border-radius:var(--r);padding:12px;color:var(--ink)}

/* responsive: stack the panes */
@media(max-width:820px){
  .login-shell{grid-template-columns:1fr;max-width:460px;box-shadow:none}
  .login-hero{min-height:0;padding:28px 26px}
  .lh-rotator{min-height:230px}
}
/* The hamburger exists in the markup at every width and is shown only where it is needed — a button
   that is created by JavaScript is a button that is missing when the JavaScript fails.
   THIS BLOCK MUST STAY ABOVE THE 900px MEDIA QUERY BELOW. Both rules have the same specificity, so
   the later one wins: with this underneath, `display:none` beat the `display:inline-flex` inside the
   query and the button was invisible on precisely the screens it exists for. */
.sb-burger{display:none;align-items:center;justify-content:center;width:40px;height:40px;
  flex:0 0 auto;border:1px solid var(--line);background:var(--panel);border-radius:8px;
  cursor:pointer;color:var(--ink);font-size:18px;line-height:1;padding:0}
.sb-burger:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
.sb-backdrop{display:none}

/* ================================================================================================
   THE SIDEBAR ON A PHONE (v3.280.0) — OFF-CANVAS, NOT ON TOP OF THE PAGE.
   ------------------------------------------------------------------------------------------------
   Darren, 30 Aug 2026, opening /me on his phone: "we can't see the page properly."

   He was right and the previous fix was half a fix. Below 900px the sidebar became `position:fixed`,
   which takes it OUT of the flex row — so .mainwrap correctly claims the full width and the sidebar
   is then painted straight over the top of it. Nothing offset the content, so:

     * expanded, an opaque 238px nav covered ~61% of a 390px screen;
     * COLLAPSED IT WAS STILL WRONG — 64px of opaque bar over the left edge of every page, which is
       where the content starts. v3.278.0 made it start collapsed on a narrow screen and that turned
       a big overlay into a small one rather than removing it.

   And the collapse cookie made it worse, not better: somebody who has used the app on a laptop
   carries `oc_nav=full`, so the "start collapsed" rule never fired for the very people most likely
   to open /me. A desktop preference must not decide a phone layout.

   So below 900px the sidebar is OFF-CANVAS — translated fully out of view, no overlay at all — and
   comes in over a backdrop when the hamburger is pressed. `collapsed` is explicitly overridden to
   full width here: a 64px icon rail is a desktop idea, and sliding one in over a phone gives you
   icons with no labels, which is the worst of both. */
@media(max-width:900px){
  .sidebar{
    position:fixed;z-index:40;left:0;top:0;height:100dvh;
    /* Off the screen entirely. `visibility` follows the transform so it cannot be tabbed into while
       it is out of view — an invisible focus trap is how a keyboard user gets lost. */
    transform:translateX(-100%);visibility:hidden;
    transition:transform .2s ease,visibility .2s;
    box-shadow:0 0 40px rgba(0,0,0,.3);
  }
  /* Whatever the desktop cookie says, a phone gets the full-width panel when it slides in. */
  .sidebar,.sidebar.collapsed{width:min(84vw,300px)}
  .sidebar.open{transform:none;visibility:visible}
  /* The collapse chevron is a desktop control — on a phone the backdrop and the X do the job. */
  .sidebar .sb-collapse{display:none}
  .sb-burger{display:inline-flex}
  .sb-backdrop{display:block}
  .sb-backdrop{position:fixed;inset:0;z-index:39;background:rgba(0,0,0,.45);
    opacity:0;pointer-events:none;transition:opacity .2s}
  .sb-backdrop.on{opacity:1;pointer-events:auto}
  #app-shell main{padding:18px 16px}
  /* The top bar is the only way back to the menu now, so it must not be crowded off the screen. */
  .apptop{padding:10px 12px;gap:8px}
  /* ---- WHAT GETS THE ROOM (v3.280.0) ---------------------------------------------------------
     Seen on a real 390px screen, not reasoned about: the title rendered as "O.." — two characters
     and an ellipsis, which tells nobody anything and still costs the width of a word. Everything in
     this bar was competing on equal terms, so the item with no flex rule lost.
     The order of value on a phone: the way back to the menu, then WHICH SCREEN AM I ON, then which
     workspace. So the title is the one thing allowed to grow, and the workspace switcher is the one
     told to give way. */
  .apptop-title{flex:1 1 auto;min-width:0;font-size:14px;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .apptop-right{gap:6px;min-width:0;flex:0 1 auto}
  /* 0592 — ON A PHONE THE SEARCH GETS ITS OWN LINE. Measured on 390px: sharing the bar with the
     burger, the title and the switcher left it about four characters wide, which is not a search
     box. The bar wraps and it takes the second row whole; the tick sits beside it. */
  .apptop{flex-wrap:wrap}
  .apptop-find{order:9;flex:1 0 100%;max-width:none;margin:2px 0 0}
  .apptop-right .ws-switch{min-width:0;overflow:hidden}
  /* Belt AND braces: the switcher is rendered by JavaScript and its inner element has changed shape
     before, so the width is capped on the CONTAINER as well as on whatever it puts inside. Without
     the container rule it ran clean off the right edge of the viewport — cut by the screen, not by
     an ellipsis, which reads as a broken page rather than as a shortened label. */
  .apptop{overflow:hidden}
  .apptop-right .ws-switch{max-width:38vw}
  .apptop-right .ws-switch *{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* The light/dark toggle is a preference somebody sets once. On a phone it was taking the width of
     two words in the one bar that has to stay usable, and it is still in the menu. */
  .apptop-right .theme-toggle{display:none}
  /* The "did you know?" rotator is a marketing surface. On a phone it took a whole row above the
     content and truncated its own sentence mid-word — "Stuck, or want a pro to w…" — which is not a
     tip, it is a line of noise on the screen a consultant opens standing in a car park. */
  .tipbar{display:none}
  /* ---- THE BRAND BLOCK IS NOT THE MENU (v3.280.0) ---------------------------------------------
     Seen on a real 390px screen: the logo took roughly the top THIRD of the drawer before the first
     menu item, so opening the menu on a phone showed you mostly logo. It is our own logo on our own
     product inside an app somebody has already signed in to — it is not doing any work there, and
     the space it costs is the space the navigation needed. A third of the size, and the padding with
     it. On a desktop, where there is a whole column to spare, it stays exactly as it was. */
  .sb-brand{padding-top:4px;min-height:0}
  html[data-theme="light"] .sidebar:not(.collapsed) .lg-full-light,
  html[data-theme="dark"]  .sidebar:not(.collapsed) .lg-full-dark{width:104px}
  .sb-top{padding:8px 12px 10px}
}

/* =====================================================================
   Proactive help nudge (used by ui.js)
   ===================================================================== */
.nudge{border:1px solid var(--accent);background:var(--accent-soft);padding:12px 14px;border-radius:var(--r);
  font-size:13px;position:relative;margin:14px 0;color:var(--ink)}
.nudge .nt{font-weight:800;color:var(--accent-ink);margin-bottom:3px}
.nudge .nx{position:absolute;top:8px;right:10px;color:var(--grey);font-weight:800;cursor:pointer;background:none;border:0;font-size:14px}
.nudge a,.nudge .linkbtn{font-weight:800}
.nudge .nudge-more{margin-top:8px}
/* 0385 — "a little expansion for more detail". The summary is a quiet link-weight line, not a
   heading: it must read as something you may open, never as something you have to. */
.nudge .nudge-more>summary{list-style:none;cursor:pointer;color:var(--grey);font-size:13px;
  font-weight:700;display:inline-block}
.nudge .nudge-more>summary::-webkit-details-marker{display:none}
.nudge .nudge-more>summary::before{content:'\25B8\00a0'}
.nudge .nudge-more[open]>summary::before{content:'\25BE\00a0'}
.nudge .nudge-more>summary:hover{color:var(--accent-ink)}
.nudge .nudge-more>summary:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.nudge .nudge-more p{font-size:13px;color:var(--grey)}

/* Tour spotlight + popover */
.tour-mask{position:fixed;inset:0;background:rgba(8,10,20,.55);z-index:2000;pointer-events:auto}
.tour-pop{position:fixed;z-index:2001;max-width:300px;background:var(--panel);border:1px solid var(--accent);
  border-radius:var(--r);box-shadow:0 20px 60px rgba(0,0,0,.35);padding:16px 18px}
.tour-pop .tp-h{font-weight:800;color:var(--accent-ink);margin-bottom:4px;font-size:14px}
.tour-pop .tp-b{font-size:13px;color:var(--ink)}
.tour-pop .tp-step{font-size:11px;color:var(--muted);margin-top:10px}
.tour-pop .tp-nav{display:flex;gap:8px;margin-top:12px}
.tour-pop .tp-nav .btn{padding:7px 13px;font-size:13px}
.tour-hi{position:relative;z-index:2001;box-shadow:0 0 0 3px var(--orange),0 0 0 7px rgba(255,109,0,.3);border-radius:var(--r)}

/* ---------- consultant view ---------- */
.consult-pair{display:grid;grid-template-columns:1fr 1fr;gap:18px;align-items:stretch;max-width:760px;margin-top:8px}
@media(max-width:620px){.consult-pair{grid-template-columns:1fr}}
.consult-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:22px 22px 20px;display:flex;flex-direction:column}
.consult-card.base{border-top:3px solid var(--blue)}
.consult-card.up{border-top:3px solid var(--orange);background:var(--panel-2)}
.consult-flag{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--orange);font-weight:800;margin-bottom:4px}
.consult-label{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);font-weight:800}
.consult-price{font-size:34px;font-weight:800;letter-spacing:-.02em;color:var(--ink);margin:8px 0 2px}
.consult-price small{font-size:13px;color:var(--grey);font-weight:600}
.consult-perks{list-style:none;padding:0;margin:14px 0 18px;font-size:14px;color:var(--muted);flex:1}
.consult-perks li{padding:4px 0}
.consult-perks li::before{content:"";display:inline-block;width:5px;height:5px;background:var(--blue);border-radius:50%;margin:0 10px 2px 0;vertical-align:middle}
.consult-card.up .consult-perks li::before{background:var(--orange)}
.consult-note{font-size:12.5px;color:var(--muted);margin-top:14px}
.whatif-copied{font-size:12px;font-weight:700;color:var(--ok);background:rgba(62,207,142,.10);border:1px solid rgba(62,207,142,.3);padding:6px 10px;border-radius:var(--r);white-space:nowrap}

/* benchmark distribution band */
.bench-bar{position:relative;height:34px;background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r);margin-top:14px}
.bench-iqr{position:absolute;top:0;bottom:0;background:var(--accent-soft);border-left:1px solid var(--accent);border-right:1px solid var(--accent)}
.bench-med{position:absolute;top:-4px;bottom:-4px;width:2px;background:var(--accent)}
.bench-you{position:absolute;top:50%;transform:translate(-50%,-50%);width:14px;height:14px;border-radius:50%;background:var(--orange);box-shadow:0 0 0 4px rgba(255,109,0,.25);z-index:2}
.bench-you span{position:absolute;top:-26px;left:50%;transform:translateX(-50%);font-size:11px;font-weight:800;color:var(--orange);white-space:nowrap}
.bench-scale{position:absolute;left:8px;right:8px;bottom:-22px;display:flex;justify-content:space-between;font-size:11.5px;color:var(--grey);font-variant-numeric:tabular-nums}
.bench-scale .bs-med{color:var(--accent-ink);font-weight:700}
.bench-bar + .hint{margin-top:28px !important}

/* =====================================================================
   PRINT — a branded PCA report regardless of screen theme.
   Forces light tokens, drops the app chrome, keeps the blue hero + charts.
   ===================================================================== */
@media print{
  html, html[data-theme="dark"]{
    --bg:#fff; --panel:#fff; --panel-2:#FAFAFA; --line:#E2E1E1;
    --ink:#231F20; --muted:#625E5F; --grey:#6A6667;
    --accent:#004EFE; --accent-soft:#EAF0FF; --zebra:#FAFAFA;
    --blue:#004EFE; --blue-dark:#0038B8; --orange:#FF6D00; --orange-soft:#FFF0E5;
    --blue-ink:#004EFE; --accent-ink:#004EFE; --blue-dark-ink:#0038B8; --blue-2-ink:#0045E0; --orange-ink:#B24C00;
    --ch1:#004EFE; --ch2:#6A6667; --ch3:#FF6D00;
    --s-bg:#fff; --s-panel:#fff; --s-raised:#fff; --s-sunk:#F1F0F0; --s-line:#E2E1E1; --s-line-2:#ECEBEB;
    --s-ink:#231F20; --s-ink-2:#4A4647; --s-muted:#625E5F; --s-accent-soft:#EAF0FF;
    --s-hero:linear-gradient(120deg, #004EFE 0%, #0038B8 62%, #002A8C 100%);
  }
  *{-webkit-print-color-adjust:exact !important; print-color-adjust:exact !important}
  .sidebar,.apptop,.support-bar,.nudge,.theme-toggle,.hero-actions,.ws-switch,
  .adv .adv-head input,#d-print,.sb-hand{display:none !important}
  .appwrap{display:block}
  .mainwrap{display:block}
  #app-shell main{max-width:none;padding:0}
  body{background:#fff}
  .print-only{display:flex !important;align-items:center;gap:14px}
  .print-header img{height:52px}
  .print-header .ph-title{font-size:19px;font-weight:800;color:#004EFE}
  .print-header .ph-sub{font-size:12px;color:#5B6070}
  /* the hero prints in full brand blue */
  .rate-hero{background:linear-gradient(135deg,#004EFE,#0038B8) !important;color:#fff !important;border:0}
  .rate-hero .hero-label,.rate-hero .hero-note{color:#D6E3FF !important}
  .rate-hero .hero-rate{color:#fff !important}
  .panel,.card,.table-wrap,.chart-fig{break-inside:avoid;box-shadow:none}
  .view-head-actions{display:none !important}
  /* v3.1119.0 — the brand band prints as a plain title: no gradient behind the words on paper. */
  .view-head{background:none !important;box-shadow:none;padding:0;color:#231F20}
  .view-head h1{color:#231F20}
  .view-head .lead,.view-head p,.view-head .crumbs{color:#625E5F}
  @page{margin:12mm}
}

/* ---------- charts (SVG) — palettes validated for each surface ---------- */
/* v3.1119.0 — brand-only series: blue, ink-grey, orange (was a bright blue / teal / orange). The
   dark values are the tints that read on #161617. Defined with the other tokens at the top too. */
:root{--ch1:#004EFE;--ch2:#6A6667;--ch3:#FF6D00}                 /* pay / on-costs / overhead */
html[data-theme="dark"]{--ch1:#004EFE;--ch2:#A7A5A6;--ch3:#FF9240}
/* The value printed ON a bar: white on the blue (5.96:1) and on the light theme's grey (5.6:1); the dark
   theme's grey is pale, so it takes ink (7.1:1), like the orange does in both. Measured, test/v31111a11y. */
html[data-theme="dark"] .hb-ch2 .hb-val{color:#231F20}
.chart-fig{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:16px 18px;margin:10px 0}
.chart-fig svg{display:block;width:100%;height:auto}
.chart-legend{display:flex;gap:16px;flex-wrap:wrap;margin:2px 0 10px;font-size:12.5px;color:var(--muted)}
.chart-legend .sw{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:6px;vertical-align:middle}
/* ---- STACKED BARS IN HTML (v3.182.0) ----------------------------------------------------------
   Was an SVG with a viewBox, which cannot respond to page zoom: zoom shrinks the container in CSS px
   by the same factor it grows each px, so a width:100% viewBox renders at a constant physical size
   while every word around it grows. These are divs, so the labels are real text at a real font size
   and they zoom and wrap like the rest of the page.
   The row is a grid: label / track / total. The label column is sized in `ch` so it fits the longest
   labour type at whatever the reader's font size is, and collapses on a narrow screen where the
   label moves above its bar instead of stealing half the width from it. */
/* ONE grid for the whole chart, not a grid per row (2026-09-10). When each row was its own grid the
   `auto` total column sized itself to THAT row's total, so a row with a shorter total ("$98,812.80/yr"
   against "$278,280.80/yr · +$179,468.00") got a wider track and its bar ran further right than the
   rest. Making `.hbars` the grid and the rows `display:contents` puts every row in the same three
   columns, so the label column, the track and the total all line up down the chart. */
.hbars{display:grid;grid-template-columns:minmax(0,14ch) minmax(0,1fr) auto;
  column-gap:12px;row-gap:14px;align-items:center;margin:4px 0 0}
.hbar-row{display:contents}
.hbar-lab{text-align:right;font-size:15px;color:var(--muted);overflow-wrap:anywhere}
.hbar-track{display:flex;gap:3px;min-width:0;border-left:1px solid var(--line);padding-left:3px}
.hb-seg{display:flex;align-items:center;justify-content:center;min-width:0;height:34px;
  border-radius:3px;overflow:hidden}
/* The figure hides itself rather than spilling: a number wider than its own block is worse than no
   number, and the title attribute carries it either way. */
.hbar-track .hb-val{font-size:13px;font-weight:800;color:#fff;white-space:nowrap;padding:0 4px}
.hbar-tot{font-size:16px;font-weight:800;color:var(--ink);white-space:nowrap}
.hb-ch1{background:var(--ch1)}
.hb-ch2{background:var(--ch2)}
.hb-ch3{background:var(--ch3)}
.chart-legend .sw.hb-ch1,.chart-legend .sw.hb-ch2,.chart-legend .sw.hb-ch3,
.chart-legend .sw.hb-profit{width:10px;height:10px}
/* ---- THE PROFIT BLOCK, AND THE SLIDER THAT DRIVES IT (2026-09-02) --------------------------- */
/* Darren asked the What-if sweep to end at the SELL rate rather than at break-even, with a slider
   for the margin. The profit block is the only one on the bar that is a CHOICE rather than a cost,
   so it is drawn differently on purpose: hatched over the accent, not another flat colour that reads
   as a fourth kind of cost. */
.hb-profit{background:repeating-linear-gradient(135deg,var(--blue),var(--blue) 6px,
  var(--blue-dark,#0038B8) 6px,var(--blue-dark,#0038B8) 12px)}
.chart-fig .wif-sell{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:0 0 12px}
.chart-fig .wif-sell .adv-lbl{font-size:13.5px;font-weight:700;color:var(--ink);white-space:nowrap}
.chart-fig .wif-sell b{color:var(--orange);font-size:15px;white-space:nowrap}
.chart-fig .wif-sell input[type=range]{flex:1;min-width:150px;max-width:300px;accent-color:var(--orange)}
@media(max-width:640px){
  /* One column: the label reads above its bar, and the bar keeps the full width. The chart goes back
     to a row-per-grid here on purpose — the label sits ABOVE its own bar, so there is no column to
     share down the chart and the two-line shape is per row. */
  /* `align-items` has to go back to stretch as well: the desktop grid centres its items in the row
     box, and in a flex COLUMN that same value shrink-wraps each row to its own content and centres
     it, which is how the shortest row ended up inset from the rest. */
  .hbars{display:flex;flex-direction:column;gap:14px;align-items:stretch}
  .hbar-row{display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-areas:'lab lab' 'track tot';
    column-gap:12px;align-items:center}
  .hbar-lab{grid-area:lab;text-align:left;font-weight:700;color:var(--ink)}
  .hbar-track{grid-area:track}
  .hbar-tot{grid-area:tot}
}
.chart-fig .bar-lab{font-size:12px;fill:var(--muted)}
.chart-fig .bar-val{font-size:12px;font-weight:700;fill:var(--ink)}
.chart-fig .seg-val{font-size:10.5px;font-weight:700;fill:#fff}
.chart-fig .axis{stroke:var(--line);stroke-width:1}
.chart-fig .hist-col{fill:var(--ch1)}
.chart-fig .hist-col:hover{opacity:.85}
.chart-fig .hist-count{font-size:10.5px;fill:var(--muted)}
.chart-fig .you-line{stroke:var(--orange);stroke-width:2}
.chart-fig .you-lab{font-size:11px;font-weight:800;fill:var(--orange)}
.chart-fig .seg:hover{opacity:.88}

/* charge-out advisor */
.adv .adv-head{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:12px}
.adv .adv-lbl{font-size:13.5px;font-weight:700;color:var(--ink);white-space:nowrap}
.adv .adv-lbl b{color:var(--orange);font-size:15px}
.adv input[type=range]{flex:1;min-width:160px;max-width:320px;accent-color:var(--orange)}
.consult-pay{max-width:760px;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:18px 20px;margin-top:8px}
.consult-pay-head{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:12px}
#consult-checkout{min-height:320px;background:#fff;border-radius:var(--r)}   /* Stripe's iframe is light */

/* =====================================================================
   Higher-specificity + missed-component overrides (win over styles.css)
   ===================================================================== */
.login-shell{background:var(--panel)}
.login-shell .login-card{background:var(--panel);color:var(--ink);max-width:none;border-radius:0}
.login-shell .login-card h1{color:var(--ink);font-size:20px}

/* home action cards + onboarding + banners */
.action-card{background:var(--panel);border-color:var(--line)}
.action-card .action-title{color:var(--ink)}
.action-card .action-desc{color:var(--muted)}
.action-icon{background:var(--panel-2);color:var(--accent-ink);border-radius:var(--r)}
.action-card.featured .action-icon{background:var(--orange-soft);color:var(--orange)}
.action-card:not(.featured) .action-cta{color:var(--accent-ink)}
.onboard{background:var(--panel);border-color:var(--line)}
.onboard-step{background:var(--panel-2);border-color:var(--line);color:var(--ink)}
.onboard-step.done{background:var(--accent-soft);border-color:var(--line)}
.home-banner.ok{background:var(--accent-soft);border-color:var(--line);color:var(--ink)}
.home-banner.ok .linkbtn{color:var(--accent-ink)}
.home-banner.tip{background:var(--orange-soft);border-color:var(--line);color:var(--ink)}

/* wizard */
.wizard{background:var(--panel);border-color:var(--line)}
.wizard-steps{background:var(--panel-2);border-color:var(--line)}
.wizard-body h3{color:var(--accent-ink)}
.wizard-body label.q span{color:var(--muted)}
.wizard-body input,.wizard-body select{background:var(--panel-2);border-color:var(--line);color:var(--ink)}
/* step pills */
.wstep{background:var(--panel);border-color:var(--line);color:var(--muted)}
.wstep.active{background:var(--orange);border-color:var(--orange);color:#151316}
.wstep.done{background:var(--panel-2);border-color:var(--line);color:var(--accent-ink)}
/* jump cards + preview tiles */
.wiz-steps-preview>div{background:var(--accent-soft);color:var(--ink)}
.wiz-steps-preview>div span{background:var(--blue);color:#fff}
.wiz-group{background:var(--panel-2);border-color:var(--line)}
.wiz-group-title{color:var(--accent-ink)}
.wiz-connect-box{background:var(--accent-soft);border-color:var(--line)}
.wiz-check{background:var(--panel);border-color:var(--line);color:var(--muted)}
.wiz-check.on{background:var(--panel-2);border-color:var(--line);color:var(--ok)}
/* cream tip callouts → theme-aware */
.wiz-tip{background:var(--orange-soft);border-color:rgba(255,109,0,.28);border-left-color:var(--orange);color:var(--ink)}
.wiz-tip b{color:var(--orange-ink)}
html[data-theme="dark"] .wiz-tip{background:#241505}
/* ---- THE "SOMETHING IS NOT SET UP" TIP (v3.287.0) -------------------------------------------
   Four of these carried an INLINE `background:#fdeeee` — the light-mode pink — which beat the dark
   rule above on specificity. In dark mode `--ink` is #F3F4F7, so the four panels that say Xero /
   MYOB / ServiceM8 sign-in is not configured, and the one that says the vault is locked, rendered
   as near-white text on near-white pink: a measured 1.02:1. A customer on the Connect screen at
   night saw a blank pink box and could not find out why their accounting system would not connect.
   A class, so both themes get a value and neither can be overridden by an attribute. */
.wiz-tip.danger{background:var(--danger-soft,#FDEEEE);border-color:rgba(192,57,43,.28);
  border-left-color:var(--danger)}
.wiz-tip.danger b{color:var(--danger)}
html[data-theme="dark"] .wiz-tip.danger{background:#2A1210;border-color:rgba(255,107,107,.30)}

/* workspace switch */
.ws-btn{background:var(--panel-2);border:1px solid var(--line);color:var(--ink)}
.ws-btn:hover{border-color:var(--accent)}
.ws-menu{background:var(--panel);border:1px solid var(--line)}
.ws-item{color:var(--ink);background:transparent}
.ws-item:hover{background:var(--panel-2)}
.ws-item.active{background:var(--accent-soft);color:var(--accent-ink)}
.ws-tag{background:var(--panel-2);color:var(--muted)}
.ws-row{border-color:var(--line)}
.ws-menu{min-width:260px}
.ws-list{max-height:320px;overflow-y:auto}          /* scales to 30–40 companies */
.ws-search{display:block;width:calc(100% - 16px);margin:4px 8px 8px;padding:8px 10px;font-size:13px;
  background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r);color:var(--ink)}
.ws-search:focus{outline:0;border-color:var(--accent)}
.ws-quota{font-size:11.5px;color:var(--muted);padding:8px 10px 6px;border-top:1px solid var(--line);margin-top:6px}
.ws-line{display:flex;align-items:center;gap:2px}
.ws-line .ws-item{flex:1;min-width:0}
.ws-mini{flex:0 0 auto;width:26px;height:26px;border:0;background:none;color:var(--grey);cursor:pointer;font-size:13px;border-radius:var(--r);opacity:0;transition:opacity .12s}
.ws-line:hover .ws-mini{opacity:1}
.ws-mini:hover{color:var(--accent-ink);background:var(--panel-2)}
.ws-mini.ws-del:hover{color:var(--danger)}

/* misc panels rendered into view bodies */
.toggles label,.col-menu,.bulk-bar{color:var(--ink)}
.col-menu{background:var(--panel);border:1px solid var(--line)}
.bulk-bar{background:var(--panel-2);border:1px solid var(--line)}
.bulk-bar select,.bulk-bar input,.whatif-row select,.whatif-row input{background:var(--panel-2);border-color:var(--line);color:var(--ink)}

/* zebra striping — two clearly different row colours in both themes */
table.editable tbody tr:nth-child(odd) td,
#d-emps tbody tr:nth-child(odd) td,
#d-labour tbody tr:nth-child(odd) td,
#d-simpro tbody tr:nth-child(odd) td,
.asset-group .ag-items tbody tr:nth-child(odd) td{background:var(--panel)}
table.editable tbody tr:nth-child(even) td,
#d-emps tbody tr:nth-child(even) td,
#d-labour tbody tr:nth-child(even) td,
#d-simpro tbody tr:nth-child(even) td,
.asset-group .ag-items tbody tr:nth-child(even) td{background:var(--zebra)}
.asset-group{background:var(--panel);border-color:var(--line)}
.asset-group-head .ag-name:focus{background:var(--panel);border-color:var(--accent)}
.modal{background:var(--panel);color:var(--ink)}
html[data-theme="dark"] .card.card-good{background:rgba(62,207,142,.12);border-color:rgba(62,207,142,.32)}
html[data-theme="dark"] .card.card-bad{background:rgba(255,107,107,.12);border-color:rgba(255,107,107,.32)}
html[data-theme="dark"] .autosave{background:rgba(62,207,142,.12);border-color:rgba(62,207,142,.32);color:var(--ok)}
html[data-theme="dark"] .btn.danger,html[data-theme="dark"] .btn.small.danger{background:rgba(255,107,107,.15);color:#ff8f8f;border-color:rgba(255,107,107,.35)}

/* remaining dark stragglers */
.toggle span{color:var(--ink)}
.float-tip{background:var(--panel);color:var(--ink);border-color:var(--line)}
.snap-row{background:var(--panel);border-color:var(--line)}
.usage-tile,.an-kpi,.modal-card,.paywall-card{background:var(--panel);border-color:var(--line);color:var(--ink)}
.lt-row input,.lt-row select{background:var(--panel-2);border-color:var(--line);color:var(--ink)}
/* editable cost tables — one consistent input style: BIG, readable, boxed */
table.editable input,.ag-items input{background:var(--panel-2);border:1px solid var(--line);color:var(--ink);
  border-radius:var(--r);padding:11px 12px;font-size:15px;font-family:inherit;width:100%;min-width:0}
/* Proportioned columns: Item gets the room, number columns stay sensible.
   THESE USED TO BE POSITIONAL (`th:nth-child(1){width:44%}` and friends). When the
   drag-handle column was added at the front, every rule shifted one place: the handle
   took the 44% meant for Item (measured at 567px of a 1314px table), Item got 11%, and
   the "number columns" alignment rule landed on Item — which is why the headings looked
   detached from their data. Positional sizing can't survive a new column; keyed sizing
   can, so these are matched by CLASS on the header and data-key on the cell. Adding a
   column now costs nothing. */
table.editable, .ag-items{table-layout:fixed;width:100%}
table.editable th.col-drag, table.editable td.col-drag,
.ag-items th.col-drag, .ag-items td.col-drag{width:26px;padding-left:4px;padding-right:0}
table.editable th.c-label, table.editable td[data-key="label"]{width:auto}
/* The Easy Start's gear table leads with a Group column, and a group name is text like an item name,
   so it gets the same treatment (2026-08-25). Without this its header hung off the right-hand edge of
   a left-aligned input, which is the misalignment Darren spotted on the labour-types table. */
table.editable th.c-block, table.editable td[data-key="block"]{width:auto}
table.editable th.c-qty,   table.editable td[data-key="qty"]{width:8%;min-width:74px}
table.editable th.c-cost,  table.editable td[data-key="cost"]{width:15%;min-width:120px}
table.editable th.c-yrs,   table.editable td[data-key="replaceYears"]{width:9%;min-width:78px}
table.editable th.c-pct,   table.editable td[data-key="appliedPct"]{width:11%;min-width:96px}
table.editable th.r,       table.editable td.r{width:96px}
table.editable th:last-child, table.editable td:last-child{width:52px}
/* Two buttons live here (duplicate + delete), so it needs more room than the P&L tables'
   single ✕. Scoped through .asset-group because the ag-items table now also carries the
   `editable` class, and `table.editable td:last-child{width:52px}` (0,1,3) would otherwise
   outrank a bare `.ag-items td:last-child` (0,1,2) and clip the delete button off. */
.asset-group .ag-items th:last-child, .asset-group .ag-items td:last-child{width:92px}
.ag-items input[style]{width:100% !important}   /* older rows carry inline widths — normalise */
.row-actions{white-space:nowrap;text-align:right}
.row-actions .icon-btn{margin-left:4px}
/* Grouped fields are type=text (a number input rejects commas), so match on data-numeric
   as well or every thousands-separated figure silently loses its right alignment. */
table.editable input[type=number],.ag-items input[type=number],
table.editable input[data-numeric],.ag-items input[data-numeric]{text-align:right}
table.editable input:focus,.ag-items input:focus{border-color:var(--accent);background:var(--panel)}
table.editable td,.ag-items td{font-size:14px}
/* Headers align with their data: text columns left, number columns right. Keyed, not
   positional, for the same reason as the widths above. */
table.editable th, .ag-items th{text-align:right}
table.editable th.col-drag, table.editable th.c-label, table.editable th.c-block,
.ag-items th.col-drag, .ag-items th.c-label{text-align:left}
table.editable td[data-key="label"] input, table.editable td[data-key="block"] input,
.ag-items td[data-key="label"] input{text-align:left}
.ag-name{background:transparent;border:1px solid transparent;color:var(--ink);font-weight:800;font-size:15px}
/* big single fields (P&L total etc.) */
.panel .field input,#pnl-total{font-size:16px;padding:12px 14px}
/* employee grid — bigger, boxed cells */
#emp-grid input{background:var(--panel-2);border:1px solid var(--line);font-size:14.5px;padding:9px 10px;border-radius:var(--r)}
#emp-grid input:focus{border-color:var(--accent);background:var(--panel)}
#emp-grid select{font-size:14px;padding:8px 9px;border-radius:var(--r)}
#emp-grid td,#emp-grid th{padding:5px 6px}
#emp-grid{font-size:14px}
/* compact headers: wrap to two lines instead of stretching the column */
/* Centred, not right-aligned. A two-line heading like "Office Admin %" ragged hard to
   the right and read as belonging to the column beside it; centring makes the wrap stack
   under its own data. The digits inside each input stay right-aligned (see below) — that
   is what keeps a column of numbers scannable — but the heading and the value box now
   share a centre line. */
#emp-grid thead th{white-space:normal;line-height:1.25;font-size:12px;vertical-align:bottom;max-width:110px;text-align:center}
#emp-grid thead th.col-name,#emp-grid thead th[data-col="position"],
#emp-grid thead th[data-col="labourType"],#emp-grid thead th[data-col="email"],
#emp-grid thead th.col-chk{text-align:left;max-width:none}
/* body cells align the same way, so inputs sit flush under their headers */
/* ---- WHY :not(.empgrp), AND WHY THIS FILE (v3.181.0) -----------------------------------------
   Darren reported the group headings still sitting in the middle of the row AFTER v3.177.0 set
   `text-align:left` on the band's cell. The rule was right and it was in the WRONG SHEET.
   theme.css loads AFTER styles.css, and `#emp-grid tbody td` carries an id — so it beat the band's
   class-scoped rule on both load order and specificity, every time, and no amount of classes can
   out-specify an id. The only fix that works is for the centring rule to stop matching the band at
   all. Hence :not(.empgrp) here, and the band's own alignment stated below, in this file, where the
   comment in styles.css has always said grid alignment belongs. */
#emp-grid tbody tr:not(.empgrp) td{text-align:center}
/* The band is not a row of data cells: it is one colSpan cell holding a name and its controls, and it
   reads from the left like the asset group bars do. Class-scoped so every grid that draws a band gets
   it — and nothing above targets a band cell any more, so there is nothing left to fight. */
tbody tr.empgrp td{text-align:left}
tbody tr.empgrp td.r{text-align:right}
#emp-grid tbody td.col-chk,#emp-grid tbody td[data-col="name"],
#emp-grid tbody td[data-col="position"],#emp-grid tbody td[data-col="labourType"],
#emp-grid tbody td[data-col="email"]{text-align:left}
/* column sizing: text gets the room, numbers stay tight.
   %-fields fit 4 digits; day-count fields fit 2; money a little more. */
#emp-grid input[data-key="name"],
#emp-grid input[data-key="position"],
#emp-grid input[data-key="email"]{min-width:180px}
#emp-grid input[data-key$="Pct"]{width:66px;min-width:0}                 /* 100.5 */
#emp-grid input[data-key="publicHolidays"],
#emp-grid input[data-key="annualLeaveDays"],
#emp-grid input[data-key="sickDays"],
#emp-grid input[data-key="tafeDays"],
#emp-grid input[data-key="rdoDays"],
#emp-grid input[data-key="unpaidLeaveDays"],
#emp-grid input[data-key="extraDaysPerYear"],
#emp-grid input[data-key="daysPerWeek"]{width:56px;min-width:0;padding-left:6px;padding-right:8px}  /* 2 digits */
#emp-grid input[data-key="stdHoursPerDay"],
#emp-grid input[data-key="lostHoursPerWeek"],
#emp-grid input[data-key="overtimeHrsPerWeek"]{width:58px;min-width:0}   /* 7.6 */
#emp-grid input[data-key="payRate"],
#emp-grid input[data-key^="allowance"]{width:88px;min-width:0}           /* $123.45 */
/* employee grid tinted column groups — dark equivalents for all five groups
   (info=slate, work=blue, leave=orange, oncost=green, allow=purple) */
html[data-theme="dark"] #emp-grid thead th.grp-info,
html[data-theme="dark"] #emp-grid thead .col-chk{background:#1D1D1F;color:var(--accent-ink)}
html[data-theme="dark"] #emp-grid tbody tr:nth-child(odd) td.grp-info,
html[data-theme="dark"] #emp-grid tbody tr:nth-child(odd) .col-chk{background:var(--panel)}
html[data-theme="dark"] #emp-grid tbody tr:nth-child(even) td.grp-info,
html[data-theme="dark"] #emp-grid tbody tr:nth-child(even) .col-chk{background:#1A1A1B}
html[data-theme="dark"] #emp-grid thead th.grp-work{background:#12213F;color:#80A6FE}
html[data-theme="dark"] #emp-grid tbody tr:nth-child(odd) td.grp-work{background:#141C2E}
html[data-theme="dark"] #emp-grid tbody tr:nth-child(even) td.grp-work{background:#12213F}
html[data-theme="dark"] #emp-grid tbody tr:nth-child(odd) td.grp-leave{background:#241a0c}
html[data-theme="dark"] #emp-grid tbody tr:nth-child(even) td.grp-leave{background:#2a1e0e}
html[data-theme="dark"] #emp-grid thead th.grp-oncost{background:#122a1a;color:#5bd68a}
html[data-theme="dark"] #emp-grid tbody tr:nth-child(odd) td.grp-oncost{background:#101f14}
html[data-theme="dark"] #emp-grid tbody tr:nth-child(even) td.grp-oncost{background:#142718}
html[data-theme="dark"] #emp-grid thead th.grp-allow{background:#241a38;color:#b794f6}
html[data-theme="dark"] #emp-grid tbody tr:nth-child(odd) td.grp-allow{background:#1c1530}
html[data-theme="dark"] #emp-grid tbody tr:nth-child(even) td.grp-allow{background:#221a38}
html[data-theme="dark"] #emp-grid .col-name{box-shadow:2px 0 0 rgba(0,0,0,.5)}
#d-simpro tbody tr:hover td{background:var(--accent-soft)}
/* warning yellows → theme aware */
html[data-theme="dark"] .support-bar{background:#2b2205;border-color:#4a3d0d;color:#e8d48b}
html[data-theme="dark"] .billing-strip.warn{background:#2b2205;border-color:#4a3d0d;color:#e8d48b}
html[data-theme="dark"] .autosave.saving{background:#2b2205;border-color:#4a3d0d;color:#e8d48b}
/* employee grid tinted column groups (info blue / leave orange / checkbox col) */
html[data-theme="dark"] #emp-grid thead th.grp-leave{background:#33210a;color:#ffb267}
html[data-theme="dark"] #emp-grid tbody tr:nth-child(odd) td.grp-leave{background:#241a0c}
html[data-theme="dark"] #emp-grid tbody tr:nth-child(even) td.grp-leave{background:#2a1e0e}
html[data-theme="dark"] #emp-grid tbody tr:nth-child(odd) td.grp-info,
html[data-theme="dark"] #emp-grid tbody tr:nth-child(odd) .col-chk{background:var(--panel)}
/* the bench gradient band stays as-is (colour scale is meaningful) */

/* Rate-table inputs: same width, right-aligned, so the Custom-rate row and the
   Markup-override row line up dead under the Cost / Markup / Sell columns. */
.rate-in{display:inline-flex;align-items:center;justify-content:flex-end;gap:3px;white-space:nowrap}
.rate-in input{width:96px;text-align:right}
.rate-in .rp,.rate-in .rs{color:var(--muted);font-size:12px;font-weight:700}
.rate-in .rp{order:-1}
tfoot .pb-override-row td{border-top:1px solid var(--line);padding-top:10px}

/* ============================================================================
   "While you wait" services pane (public/ads.js)
   Long Simpro runs give us 2-40 minutes of a customer's undivided attention.
   Rather than a spinner staring back at them, we rotate through what else
   Platinum does. Brand blue plate + orange accent, light/dark via the tokens.
   ========================================================================== */
.wait-ads{max-width:820px;margin:14px 0 0}
.pad-wrap{
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:14px 16px 12px;box-shadow:var(--shadow);position:relative;overflow:hidden}
.pad-wrap::before{                      /* thin brand rule across the top */
  content:"";position:absolute;left:0;right:0;top:0;height:3px;
  background:linear-gradient(90deg,var(--blue) 0%,var(--blue-2) 55%,var(--orange) 100%)}
.pad-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}
.pad-eyebrow{font-size:11px;font-weight:800;letter-spacing:.6px;text-transform:uppercase;color:var(--muted)}
.pad-nav{display:flex;gap:6px}
.pad-arrow{
  width:24px;height:24px;line-height:1;padding:0;cursor:pointer;font-size:16px;font-weight:800;
  background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r);color:var(--muted)}
.pad-arrow:hover{border-color:var(--accent);color:var(--accent-ink)}

.pad-card{display:grid;grid-template-columns:104px 1fr;gap:16px;align-items:start;
  opacity:1;transform:translateY(0);transition:opacity .22s ease,transform .22s ease}
.pad-card.fading{opacity:0;transform:translateY(6px)}

/* artwork ------------------------------------------------------------------ */
.pad-art{width:104px}
.pad-art svg{display:block;width:104px;height:104px}
.pa-plate{fill:var(--blue-lt)}
.pa-plate2{fill:var(--panel)}
.pa-plate2-s{stroke:var(--panel);stroke-width:3;stroke-linecap:round}
.pa-fill{fill:var(--blue)}
.pa-fill2{fill:var(--orange)}
.pa-stroke{stroke:var(--panel);stroke-width:3;stroke-linecap:round}
.pa-stroke2{stroke:var(--orange);stroke-width:4;stroke-linecap:round;stroke-linejoin:round}
html[data-theme="dark"] .pa-plate{fill:#12213F}
html[data-theme="dark"] .pa-plate2,html[data-theme="dark"] .pa-plate2-s{fill:#0E0F13;stroke:#0E0F13}

/* copy --------------------------------------------------------------------- */
.pad-body{min-width:0}
.pad-kicker{display:inline-block;font-size:10px;font-weight:800;letter-spacing:.7px;text-transform:uppercase;
  color:var(--blue-ink);background:var(--blue-lt);border-radius:99px;padding:2px 9px}
.pad-title{margin:7px 0 4px;font-size:17px;line-height:1.25;color:var(--ink)}
.pad-hook{margin:0;font-size:13px;line-height:1.5;color:var(--muted)}
.pad-points{margin:8px 0 0;padding:0;list-style:none;display:flex;flex-wrap:wrap;gap:6px 8px}
.pad-points li{font-size:12px;font-weight:600;color:var(--ink);
  background:var(--panel-2);border:1px solid var(--line);border-radius:99px;padding:3px 10px}
.pad-points li::before{content:"✓ ";color:var(--ok);font-weight:800}
.pad-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:11px;flex-wrap:wrap}
.pad-price{font-size:13px;font-weight:800;color:var(--blue-dark-ink)}
html[data-theme="dark"] .pad-price{color:var(--blue-2-ink)}
.pad-cta{text-decoration:none;display:inline-block;padding:5px 12px;font-size:12px}
.pad-cta:hover{color:#151316}

/* dots + footer ------------------------------------------------------------ */
.pad-dots{display:flex;gap:5px;justify-content:center;margin:12px 0 0;flex-wrap:wrap}
.pad-dots i{width:6px;height:6px;border-radius:99px;background:var(--grey-mid);cursor:pointer;transition:background .2s,width .2s}
.pad-dots i.on{width:16px;background:var(--blue)}
.pad-foot-note{margin:8px 0 0;text-align:center;font-size:11px;color:var(--grey)}

@media (max-width:560px){
  .pad-card{grid-template-columns:1fr}
  .pad-art{width:72px}
  .pad-art svg{width:72px;height:72px}
}
@media (prefers-reduced-motion:reduce){
  .pad-card,.pad-dots i{transition:none}
}

/* ---- QRS: rules notes, group-choice cards, tree previews ---- */
.qrs-rules summary{cursor:pointer;color:var(--accent-ink);font-weight:600}
.qrs-rules td:first-child{width:34%;white-space:normal}
.qrs-rules code{font-size:12px;padding:1px 5px;border-radius:3px}
.qrs-choice{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:6px}
.qrs-opt{display:flex;gap:10px;align-items:flex-start;padding:12px;cursor:pointer;
  background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r)}
.qrs-opt:hover{border-color:var(--accent)}
.qrs-opt:has(input:checked){border-color:var(--accent);background:var(--accent-soft)}
.qrs-opt input{margin-top:3px;flex:0 0 auto}
.qrs-opt .hint{display:block;margin-top:6px}
.qrs-tree{margin:8px 0 0;padding:10px 12px;background:var(--panel);border:1px solid var(--line);
  border-radius:var(--r);font-size:12px;line-height:1.55;white-space:pre;overflow-x:auto;color:var(--ink)}
/* The RADIO keeps its cap; the PICKER does not. A 250px cap on the supplier dropdown truncated the very
   names it exists to let somebody choose between — "Voltex Electrical Accessories Pty Lt…" — and a
   truncated name in a picker is the one thing on this screen a person could get wrong. The column is
   sized for it in styles.css; the cap here was silently overriding that. Found 2026-08-23 by measuring
   the computed width in Chromium after the layout still looked wrong. */
.qrs-sup-act{max-width:250px}
.qrs-sup-vendor{max-width:100%}
@media (max-width:720px){ .qrs-choice{grid-template-columns:1fr} }

/* ---- ads pane: richer artwork tones, testimonial, credibility strip ---- */
.pa-blob{fill:var(--blue);opacity:.10}
html[data-theme="dark"] .pa-blob{fill:var(--blue-2);opacity:.16}
.pa-fill-d{fill:var(--blue-dark)}
html[data-theme="dark"] .pa-fill-d{fill:#0045E0}
.pad-quote{margin:10px 0 0;padding:8px 12px;border-left:3px solid var(--orange);
  background:var(--panel-2);border-radius:0 var(--r) var(--r) 0;font-size:12.5px;
  font-style:italic;color:var(--ink);line-height:1.45}
.pad-quote cite{display:block;margin-top:3px;font-style:normal;font-weight:700;
  font-size:11px;color:var(--muted)}
.pad-proof{display:flex;flex-wrap:wrap;gap:6px 18px;justify-content:center;align-items:center;
  margin:12px 0 0;padding-top:10px;border-top:1px solid var(--line);
  font-size:11.5px;color:var(--muted)}
.pad-proof b{color:var(--blue-ink);font-size:13px}
html[data-theme="dark"] .pad-proof b{color:var(--blue-2-ink)}
.pad-indep{color:var(--orange);font-weight:700}

/* ---- QRS supplier approval ---- */
.qrs-approve{border:1px solid var(--orange);border-radius:var(--r);padding:12px 14px;
  background:var(--orange-soft);margin-top:6px}
.qrs-approve h3{color:var(--orange)}
.qrs-approve table{background:var(--panel)}
.qrs-yn{display:block;font-size:12px;font-weight:600;cursor:pointer;padding:1px 0}
.qrs-yn input{margin-right:5px}
tr.qrs-undecided{background:var(--orange-soft)}
tr.qrs-undecided td:first-child{box-shadow:inset 3px 0 0 var(--orange)}
.hidden{display:none}

/* ---- Catalogue Health Check ---- */
.ch-grade{display:inline-block;color:#fff;border-radius:5px;padding:0 9px;font-weight:800;font-size:17px;vertical-align:2px}
.ch-find{border-top:1px solid var(--line);padding:12px 0}
.ch-find:first-of-type{border-top:0}
.ch-n{font-size:20px;font-weight:800;color:var(--orange);white-space:nowrap}
.ch-ex table{font-size:12px}
.ch-ages{display:grid;gap:7px;margin-top:6px}
.ch-age{display:grid;grid-template-columns:120px 1fr 64px;gap:10px;align-items:center;font-size:13px}
.ch-age b{text-align:right}
.ch-bar{height:10px;background:var(--panel-2);border:1px solid var(--line);border-radius:99px;overflow:hidden}
.ch-bar i{display:block;height:100%}

/* ================= GUIDED START ============================================================
   The step rail + explainer card that sits above each tool's own panels. Lives in theme.css
   rather than styles.css deliberately: theme.css loads SECOND, so a rule here wins a
   specificity tie instead of silently losing one. That has bitten this project three times.  */
.gd{margin:10px 0 18px}
.gd-rail{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px}
.gd-step{font-size:13px;font-weight:700;padding:7px 13px;border-radius:var(--r);
  background:var(--panel);border:1px solid var(--line);color:var(--muted);white-space:nowrap}
.gd-step.now{background:var(--orange);border-color:var(--orange);color:#fff;
  box-shadow:0 4px 12px rgba(239,109,26,.35)}
.gd-step.done{background:var(--panel);border-color:#D6E3FF;color:var(--blue-ink)}
.gd-card{background:var(--panel);border:2px solid var(--blue-lt);border-radius:var(--r);
  padding:16px 18px;box-shadow:0 8px 24px rgba(35,31,32,.07)}
.gd-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:8px}
.gd-num{font-size:12px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;
  color:#fff;background:var(--blue);padding:3px 9px;border-radius:99px}
.gd-title{font-size:14px;font-weight:700;color:var(--blue-dark-ink);flex:1}
.gd-x{background:none;border:1px solid var(--line);color:var(--muted);font:inherit;font-size:12px;
  padding:4px 9px;border-radius:var(--r);cursor:pointer}
.gd-x:hover{color:var(--ink);border-color:var(--muted)}
.gd-say{margin:0 0 6px;font-size:15px;line-height:1.45;color:var(--ink)}
.gd-why{margin:0 0 6px;font-size:13.5px;line-height:1.5;color:var(--grey)}
/* The reassurance line. Green, because it is the one people scan for before they click the
   button — and it says what THIS app actually keeps, never a generic "it's backed up". */
.gd-safe{margin:8px 0 0;font-size:13.5px;line-height:1.5;padding:9px 12px;border-radius:var(--r);
  background:rgba(31,138,76,.09);border-left:4px solid #1f8a4c;color:var(--ink)}
.gd-next{margin:8px 0 0;font-size:12.5px;color:var(--muted);font-style:italic}
.gd-off{margin:6px 0 12px}
/* Panels you have not reached yet: readable, obviously not-yet-your-problem, and still
   clickable — the guide advises, it never locks you out of your own tool. */
.gd-dim{opacity:.45;filter:saturate(.6)}
.gd-dim:hover,.gd-dim:focus-within{opacity:1;filter:none}
@media (max-width:700px){
  .gd-step{font-size:12px;padding:6px 10px}
  .gd-title{flex-basis:100%}
}

/* ================= GUIDED START (per-app wizard) ===========================================
   The wizard itself reuses .wizard / .wstep / .wiz-tip from the Calculator's Easy Start, so a
   guided run looks the same wherever you meet one. Only the bits unique to it live here.      */

/* The one-line invitation at the top of each app screen. A link, not a banner. */
.aw-link{margin:0 0 14px;font-size:14px;color:var(--grey)}
/* The guided-start card. Replaces .aw-link, which was one grey sentence above a screen full of
   controls — so the thing we recommend read as a footnote to the thing we do not. Kept to a single row
   on a normal screen, because "prominent" must not mean the tool itself starts below the fold. */
.aw-card{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin:0 0 16px;padding:12px 16px;
  /* --panel-2, NOT a hard-coded wash. There is no --tint variable in this sheet, so `var(--tint,#F2F6FF)`
     always resolved to the light fallback — and in dark mode that put near-white --ink text on a
     near-white card. Caught in a Chromium screenshot; the copy was there and simply could not be read. */
  background:var(--panel-2);color:var(--ink);border:1px solid var(--blue,#004EFE);border-left-width:4px;
  border-radius:8px}
.aw-card-body{display:flex;align-items:center;gap:12px;flex:1 1 320px;min-width:0;flex-wrap:wrap}
.aw-badge{flex:none;font-size:11.5px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;
  color:#fff;background:var(--blue,#004EFE);border-radius:20px;padding:3px 10px;white-space:nowrap}
.aw-card-txt{font-size:14px;line-height:1.45;min-width:0}
.aw-card .btn{flex:none}

/* Big tappable choices — the wizard asks one thing at a time, so the options can afford room. */
.aw-choices{display:grid;gap:10px;margin-top:14px}
.aw-choice{display:flex;align-items:flex-start;gap:12px;padding:14px 16px;text-align:left;width:100%;
  background:var(--panel);border:2px solid var(--line);border-radius:var(--r);cursor:pointer;
  font:inherit;color:var(--ink);transition:border-color .12s,background .12s}
.aw-choice:hover{border-color:var(--blue-lt)}
.aw-choice.on{border-color:var(--blue);background:var(--blue-wash,rgba(0,78,254,.06))}
.aw-choice input{margin-top:3px;flex:0 0 auto}
.aw-choice span{display:block}
.aw-choice b{display:block;font-size:15px;margin-bottom:2px}
.aw-choice small{display:block;font-size:13px;color:var(--muted);line-height:1.45}

.aw-facet{margin-top:14px}
.aw-facet-lbl{font-size:13px;font-weight:700;color:var(--grey);margin-bottom:6px}
.aw-count{margin:14px 0 0;font-size:15px;color:var(--ink)}

/* The reassurance block. Green, because it is the line people look for before they commit.
   Says what THIS app actually keeps — never a generic "it's backed up". */
.gd-safe{margin:12px 0;font-size:13.5px;line-height:1.5;padding:10px 13px;border-radius:var(--r);
  background:rgba(31,138,76,.09);border-left:4px solid #1f8a4c;color:var(--ink)}

/* Undo Centre: a run that has already been reversed must READ as reversed. A grey lowercase
   "put back" sitting where a button would be looked like a disabled button instead of a status. */
.un-done{display:inline-block;font-size:12.5px;font-weight:700;color:#1f8a4c;white-space:nowrap;
  background:rgba(31,138,76,.10);border:1px solid rgba(31,138,76,.35);border-radius:99px;padding:4px 10px}
.un-tag{display:inline-block;font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  color:#1f8a4c;background:rgba(31,138,76,.10);border-radius:99px;padding:1px 7px;margin-top:3px}
#un-grid tr.un-row-done td{opacity:.72}
#un-grid tr.un-row-done:hover td{opacity:1}

/* ---------------------------------------------------------------------------------------------
   THE SELECTED LIST. Every "tick some of these" control on the archivers (groups, suppliers,
   storage locations) hid its answer inside a scrolling, filterable list — so once you had typed a
   filter, or scrolled, or ticked fourteen groups out of eight hundred, the only way to see what was
   actually selected was to scroll the whole list looking for ticks. Darren, 2026-08-19: "the selected
   items should show in a 'Selected' list as you select them so it's easy to see what's selected as a
   separate list." One chip per selection, always visible, each removable on its own.
   --------------------------------------------------------------------------------------------- */
/* SIDE BY SIDE: the filter and the list on the left, what you picked on the right. It first shipped
   BETWEEN the filter box and the list, which split one control in half — Darren, 2026-08-19: "put the
   selected panel above the search or even better beside... It's strange having it in between." */
.pick-grid{display:flex;gap:12px;align-items:flex-start}
.pick-main{flex:1 1 340px;min-width:0}          /* min-width:0 or a long group path stops the flex shrink */
.picked-box{flex:0 0 300px;align-self:stretch;max-height:340px;overflow:auto;
  padding:8px 10px;border:1px solid var(--ch2,#7B7979);border-radius:var(--r);background:rgba(123,121,121,.06)}
/* Narrow screens stack, and the selection goes ON TOP — his first suggestion, and the right one when
   there is no room beside: what you have chosen matters more than the list you chose it from. */
@media (max-width:780px){
  .pick-grid{flex-direction:column}
  .pick-main{order:2;width:100%;flex:1 1 auto}
  .picked-box{order:1;flex:1 1 auto;width:100%;max-height:200px}
}
.picked-head{margin:0 0 6px;display:flex;align-items:center;gap:10px;flex-wrap:wrap;font-weight:800;font-size:12.5px}
/* A VERTICAL list, one per line — far easier to read down than a wrapped row of tags. */
.picked-chips{display:flex;flex-direction:column;align-items:stretch;gap:4px}
.chip-pick{display:flex;align-items:center;justify-content:space-between;gap:6px;background:var(--panel);
  border:1px solid var(--line);border-radius:var(--r);font-size:12px;padding:4px 5px 4px 9px}
.chip-pick>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.chip-pick button{border:0;background:var(--panel-2);color:var(--ink);cursor:pointer;font-family:inherit;
  font-size:13px;line-height:1;width:18px;height:18px;border-radius:999px;padding:0}
.chip-pick button:hover{background:var(--orange);color:#fff}
/* A review row that is telling you something is off. Warnings on the review page are the point of it:
   a protection that is armed and protects nothing has to look different from one that works. */
.rv-warn{color:var(--orange);font-weight:700}
/* The review table. `table-layout:fixed` + a bounded label column is what stops a long list of picked
   supplier names from pushing the panel off the right of the page. */
.rv-table{width:100%;table-layout:fixed;border-collapse:collapse;font-size:13.5px}
.rv-table th{text-align:left;padding:5px 12px 5px 0;vertical-align:top;width:170px;font-weight:700}
.rv-table td{padding:5px 0;vertical-align:top;overflow-wrap:anywhere}
.rv-list{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
.rv-tag{background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r);padding:1px 7px;font-size:12px}

/* ---------------------------------------------------------------------------------------------
   THE STEP BOARD. A long run used to show one line and an elapsed clock, which answers "is it alive"
   and nothing else. Darren, 2026-08-19, at 26m49s: "Can we not do a quick fetch of totals so we can
   get an ETA and also tick things off as they're getting done."

   PREFIXED `sbd-`, AND NOT A LIST, because the first version used `.sb-list` with <li> children — and
   `.sb-list` ALREADY EXISTED in styles.css, where `.sb-list li::before{content:"✓"}` paints an orange
   tick on every row. So every step showed as done, and because a ::before is itself a flex item, it
   squeezed the labels down to one word per line. Two bugs from one borrowed class name. The markers are
   plain characters in the markup now, so no other stylesheet can supply or override them.
   --------------------------------------------------------------------------------------------- */
/* ---- THE TWO PANELS, SIDE BY SIDE (2026-08-25) ----------------------------------------------------
   Darren, watching a three-hour QRS install: "Maybe the two boxes steps and individual items box can
   go side by side. There is plenty of room for that."

   ONE WRAPPER, TWO CELLS. Before this the step board and the item list were siblings found by
   `nextElementSibling`, which is also how the page came to draw both of them twice (see runPanelHost
   in public/app.js). They are two children of `.runpanels` now, which is what makes the pairing
   possible AND what makes "exactly one of each" structural rather than a thing to remember.

   THE BREAKPOINT IS 820px, and it is a real media query rather than flex-wrap alone. Each cell wants
   380px; two of those plus the 14px gap is 774px, so wrapping would kick in somewhere just under 800
   and land the item list in a 400px column on a tablet — wide enough to look deliberate and too narrow
   for a part number and a Simpro error message on one line. Below 820px they stack, full width, which
   is the shape the item feed actually needs.

   HEIGHTS ARE MATCHED, AND THE FEED KEEPS ITS OWN SCROLL. `align-items:stretch` makes both boxes the
   height of the taller one, and the item list's scroll region takes the slack (`flex:1 1 auto` with
   `min-height:0`, without which a flex child refuses to shrink below its content and the page scrolls
   instead of the list). So the pair reads as one object whichever of the two is taller, and a
   five-hour run's worth of rows still scrolls inside its own box rather than pushing the Stop button
   off the screen — which was the rule the single-column version already followed.
   --------------------------------------------------------------------------------------------- */
.runpanels{display:flex;flex-wrap:wrap;align-items:stretch;gap:14px;margin:8px 0 0}
.runpanels>.rp-step,.runpanels>.rp-rows{flex:1 1 380px;min-width:0;display:flex}
/* An empty cell must not hold a column open: before the first rows arrive there is nothing to show,
   and half a two-column layout standing blank reads as something that failed to load. */
.runpanels>.is-empty{display:none}
.runpanels>.rp-step>.stepboard,.runpanels>.rp-rows>.runrows{
  margin:0;max-width:none;width:100%;display:flex;flex-direction:column}
.runpanels>.rp-rows>.runrows>.rr-list{flex:1 1 auto;min-height:0}
@media (max-width:820px){
  .runpanels{display:block}
  .runpanels>.rp-step,.runpanels>.rp-rows{display:block}
  .runpanels>.rp-rows{margin-top:8px}
  /* Stacked, the list goes back to its own bounded height rather than stretching to a neighbour. */
  .runpanels>.rp-rows>.runrows>.rr-list{max-height:280px}
}

.stepboard{margin:8px 0 0;padding:10px 12px;border:1px solid var(--line);border-radius:var(--r);
  background:var(--panel-2);max-width:620px}
.sbd-head{font-size:12.5px;margin-bottom:6px;display:flex;gap:6px;flex-wrap:wrap;align-items:baseline}
.sbd-list{margin:0;padding:0}
.sbd-row{display:flex;align-items:baseline;gap:8px;padding:2px 0;font-size:13px}
.sbd-row::before{content:none}                    /* belt and braces against a stray li/div rule */
.sbd-row.is-todo{opacity:.55}
.sbd-row.is-now{font-weight:700}
.sbd-mark{width:14px;flex:0 0 14px;text-align:center}
.sbd-row.is-done .sbd-mark{color:var(--ch2,#7B7979);font-weight:800}
.sbd-row.is-now .sbd-mark{color:var(--orange)}
.sbd-row.is-todo .sbd-mark{color:var(--line)}
.sbd-row.is-failed .sbd-mark{color:var(--danger,#c0392b);font-weight:800}   /* v3.1090.0 — a step that did not land */
.sbd-row.is-failed .sbd-label{color:var(--danger,#c0392b)}
.sbd-failed-count{color:var(--danger,#c0392b);font-weight:700}
/* min-width:0 or a long step name cannot shrink, and the row wraps a word at a time instead. */
.sbd-label{flex:1 1 auto;min-width:0}
/* v3.294.0 — the stalled-run warning.
   IN THEME.CSS, NOT STYLES.CSS, and a test enforces it: every class this board draws must be owned
   here, or it inherits markers and layout from rules written for something else (.sb-list once put
   a tick on every step for exactly that reason).
   BUILT FROM TOKENS THAT EXIST IN BOTH THEMES. A hardcoded #fbe9e9 would be the wiz-tip fault
   again — four panels measured at 1.02:1 in dark mode because a light background beat the dark
   rule. `--orange-soft` is redefined in the dark block below; there is no `--danger-soft`, so the
   danger is carried by the BORDER and the text stays on the themed ink. */
.sbd-stalled{margin:0 0 10px;padding:9px 12px;border-left:4px solid var(--danger);
  background:var(--orange-soft);border-radius:6px;font-size:13px;line-height:1.55}
.sbd-detail{flex:0 0 auto;font-weight:400;font-size:12px;color:var(--muted,#777)}
.sbd-bar{height:6px;border-radius:999px;background:var(--line);overflow:hidden;margin-top:8px}
.sbd-bar>i{display:block;height:100%;background:var(--orange);transition:width .4s ease}
@media (max-width:560px){ .sbd-row{flex-wrap:wrap} .sbd-detail{flex-basis:100%;margin-left:22px} }

/* ---- THE PER-ITEM LIST (2026-08-24) ---------------------------------------------------------------
   Darren: "when reattaching ... we don't get the full list of items being done anymore, just the
   overview that was at the top." This is the list. It sits under the step board, in a node of its own.

   PREFIXED `rr-`, for the same reason the step board is prefixed `sbd-`: the first step board borrowed
   `.sb-list` and inherited a ::before tick on every row from styles.css. No borrowed names here.

   THE LIST SCROLLS, THE PAGE DOES NOT. Up to 700 rows can be in it, and a five-hour run's worth of
   markup pushing the Stop button off the bottom of the screen would make the panel actively harmful.
   --------------------------------------------------------------------------------------------- */
.runrows{margin:8px 0 0;padding:10px 12px;border:1px solid var(--line);border-radius:var(--r);
  background:var(--panel-2);max-width:620px}
.rr-head{font-size:12.5px;margin-bottom:6px}
.rr-sub{font-size:11.5px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted,#777);margin:2px 0 4px}
.rr-list,.rr-fails{max-height:280px;overflow-y:auto;overflow-x:hidden}
.rr-fails{margin-bottom:8px}
.rr-row{display:flex;align-items:baseline;gap:8px;padding:2px 0;font-size:12.5px;line-height:1.35}
.rr-row::before{content:none}
.rr-n{flex:0 0 auto;min-width:44px;text-align:right;color:var(--muted,#777);font-variant-numeric:tabular-nums}
.rr-mark{flex:0 0 12px;text-align:center}
.rr-row.is-ok .rr-mark{color:var(--ch2,#7B7979)}
.rr-row.is-bad .rr-mark{color:var(--danger);font-weight:800}
.rr-row.is-bad{color:var(--danger)}
/* min-width:0 or a long part name cannot shrink and the row wraps one word at a time. */
.rr-what{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}
.rr-step{display:block;font-size:11px;color:var(--muted,#777)}
.rr-why{flex:1 1 200px;min-width:0;font-size:11.5px;overflow-wrap:anywhere}
.rr-note{margin:8px 0 0}
/* ---- THE TICKER LINE (v3.234.0) -----------------------------------------------------------------
   Darren: "cycle through different catalogue items or different jobs... at least we can see it's doing
   something and moving through the list."

   Pinned above the list rather than inside it, because it is a DIFFERENT fact: the rows are a sampled
   history, this is where the run is right now. It must not scroll away with them.

   `min-height` matters more than it looks. The names underneath vary from "ACC-1" to a 90-character
   description, so an unreserved line changes height on every tick and the whole panel below it jitters
   — which reads as a page fault, not as progress. One fixed row, ellipsised. */
.rr-now{display:flex;align-items:baseline;gap:8px;margin:0 0 8px;padding:5px 8px;
  border-radius:calc(var(--r) - 2px);background:var(--panel);border:1px solid var(--line);
  min-height:26px;font-size:12.5px}
.rr-now-k{flex:0 0 auto;font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--ch2,#7B7979);font-weight:800}
.rr-now-v{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  font-variant-numeric:tabular-nums}
.rr-now.is-bad .rr-now-k,.rr-now.is-bad .rr-now-v{color:var(--danger)}
/* A TICKER THAT HAS STOPPED TICKING (v3.341.0). The teal label is the "this is live" signal on this
   panel, so a row still wearing it while nothing has moved for the best part of a minute is the
   screen asserting something it does not know. Muted, and relabelled "Last seen" by the renderer —
   the item is still where the run got to, which is worth showing; the present tense is not. */
.rr-now.is-stale{opacity:.72}
.rr-now.is-stale .rr-now-k{color:var(--muted)}
/* Inside the ticker the phase is an inline aside, not the block it is on a list row. */
.rr-now .rr-step{display:inline;flex:0 0 auto}
@media (max-width:560px){ .rr-row{flex-wrap:wrap} .rr-why{flex-basis:100%;margin-left:64px} }

/* ---- "SOMETHING IS RUNNING" — the two indicators (2026-08-24) -------------------------------------
   Darren: "there should be some way of knowing which app is running in the background so you can change
   back to it easily. Maybe we need a little symbol on the workspace and another symbol on the app within
   that workspace that is currently operating."

   The flag sits in the top bar (workspace level) and the dot on each app's button (app level). Both are
   clickable; the flag is a <button> and the dot rides inside one.

   THE UNKNOWN STATE IS GREY, NOT GREEN AND NOT HIDDEN. "We could not find out" must not look like "all
   clear" — an absent value is not zero.
   --------------------------------------------------------------------------------------------- */
.run-flag{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line);border-radius:999px;
  background:var(--panel-2);color:inherit;font:inherit;font-size:12.5px;padding:4px 11px;cursor:pointer;
  white-space:nowrap;max-width:260px;overflow:hidden}
.run-flag:hover{border-color:var(--orange)}
.run-flag.hidden{display:none}
.run-flag .rf-text{overflow:hidden;text-overflow:ellipsis}
.run-flag.is-running{border-color:var(--orange)}
.run-flag.is-running .rf-dot{color:var(--orange);animation:rf-pulse 1.8s ease-in-out infinite}
/* ---- SOMETHING RUNNING SOMEWHERE ELSE (v3.345.0) -------------------------------------------
   Its own state, and deliberately NOT the orange of `is-running`. A run in this workspace is
   something you are waiting for; a run in another workspace is information. Same pill, quieter
   ink, and no pulse — a dot that throbs about work on a different screen is a dot that trains
   people to ignore the throbbing one. */
.run-flag.is-elsewhere{border-color:var(--line)}
.run-flag.is-elsewhere .rf-dot{color:var(--muted,#777)}
.run-flag.is-unknown{border-style:dashed}
.run-flag.is-unknown .rf-dot{color:var(--muted,#777)}
@keyframes rf-pulse{0%,100%{opacity:1}50%{opacity:.35}}
/* Somebody who has asked for less motion gets a steady dot; it still reads as "live". */
@media (prefers-reduced-motion:reduce){ .run-flag.is-running .rf-dot{animation:none} }

.run-flag-menu{position:absolute;top:calc(100% + 8px);right:0;z-index:60;min-width:260px;max-width:340px;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:6px;
  box-shadow:0 10px 28px rgba(0,0,0,.18)}
.run-flag-menu.hidden{display:none}
.rf-head{font-size:11.5px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted,#777);padding:6px 8px}
.rf-item{display:block;width:100%;text-align:left;background:none;border:0;color:inherit;font:inherit;
  padding:7px 9px;border-radius:8px;cursor:pointer}
.rf-item:hover:not([disabled]){background:var(--panel-2)}
.rf-item[disabled]{cursor:default;opacity:.7}
.rf-sub{display:block;font-size:11.5px;color:var(--muted,#777);margin-top:2px}

/* The app-level mark. Pushed to the right of the button's label so it lines up with the ★ that already
   sits there on the starred tabs, and it stays visible when the sidebar collapses to icons. */
.runmark{margin-left:auto;color:var(--orange);font-size:9px;line-height:1;flex:0 0 auto;
  animation:rf-pulse 1.8s ease-in-out infinite}
@media (prefers-reduced-motion:reduce){ .runmark{animation:none} }
.apph-card .runmark{position:absolute;top:10px;right:12px;margin:0}
.apph-card{position:relative}

/* ---- CONNECT YOUR APPS (2.99.0) ------------------------------------------------------------- */
.connect-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px}
.conn-tile{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:16px 18px;
  display:flex;flex-direction:column;gap:8px;position:relative}
.conn-tile.on{border-color:var(--blue);box-shadow:0 0 0 1px var(--blue) inset}
.conn-tile.soon{opacity:.72;background:var(--panel-2)}
.conn-tile h3{margin:0;font-size:16px;display:flex;align-items:center;gap:8px}
.conn-tile .conn-why{font-size:13px;color:var(--muted);flex:1}
.conn-pill{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;padding:2px 8px;
  border-radius:20px;align-self:flex-start}
.conn-pill.live{background:var(--blue);color:#fff}
.conn-pill.off{background:var(--line);color:var(--muted)}
.conn-pill.soon{background:var(--orange-soft);color:var(--orange)}
.conn-flow{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px;margin-top:6px}
.conn-flow .cf{border:1px solid var(--line);border-radius:var(--r);padding:10px 12px;background:var(--panel-2);font-size:13px}
.conn-flow .cf b{display:block;margin-bottom:2px}

/* ---- THE GUIDED FIRST ENTRY AND THE COLUMN-PICKER STEP ARE GONE (v3.385.0) -------------------
   The Easy Start now adopts the real screens (Your team, Your expenses, Asset replacement …) into its
   body instead of drawing its own walk-through forms, so ~100 lines of .gfe-* and .colpick-* rules had
   nothing left to style. Removed rather than kept "just in case": a rule with no markup is a rule
   nobody can see fail. */

/* ============================================================================================
   THE PATH — numbered steps in the sidebar, a footer bar on each step screen (v3.3.0)
   ============================================================================================
   The menu is the workflow now, so a step has to say three things at a glance: which number it
   is, whether it is done, and what comes next. The number replaces the icon deliberately — it is
   the one glyph that still reads as "step 3" when the sidebar is collapsed to 64px. */
.sb-path .tab.step{gap:9px}
.sb-path .stepno{flex:0 0 auto;width:19px;height:19px;border-radius:50%;display:inline-flex;
  align-items:center;justify-content:center;font-size:10.5px;font-weight:800;
  border:1.4px solid var(--line);color:var(--grey);background:transparent;transition:.15s}
.sb-path .tab.active .stepno{border-color:var(--accent);color:#fff;background:var(--accent)}
.sb-path .tab:hover .stepno{border-color:var(--grey);color:var(--ink)}
.sb-path .steptick{margin-left:auto;font-size:12px;font-weight:800;line-height:1;flex:0 0 auto}
.sb-path .steptick.on{color:var(--ok,#1f9d55)}
/* An optional step that is empty gets a hollow ring, not a cross: nothing is wrong, there is just
   nothing there. A red mark on "no vehicles" would be a lie. */
.sb-path .steptick.opt::after{content:'';display:block;width:7px;height:7px;border-radius:50%;
  border:1.4px solid var(--line);opacity:.8}
.sidebar.collapsed .sb-path .steptick{display:none}
.sidebar.collapsed .sb-path .stepno{width:22px;height:22px;font-size:12px}

/* Where-am-I chip beside each step screen's title */
.step-of{font-size:11px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  color:var(--grey);border:1px solid var(--line);border-radius:999px;padding:3px 9px;
  vertical-align:middle;margin-left:10px;white-space:nowrap}

/* The footer bar. Sticks to the bottom of the step screens so "what's next" is always in reach
   without scrolling back up to the menu. */
.stepbar{position:sticky;bottom:0;z-index:5;margin:26px -4px 0;padding:12px 16px;
  display:flex;align-items:center;gap:16px;flex-wrap:wrap;
  background:var(--panel);border:1px solid var(--line);border-radius:12px;
  box-shadow:0 -2px 14px rgba(0,0,0,.06)}
.stepbar .sb-left{min-width:190px}
.stepbar .sb-where{font-size:13px;color:var(--ink)}
.stepbar .sb-state{font-size:12px;margin-top:2px}
.stepbar .sb-ok{color:var(--ok,#1f9d55);font-weight:700}
.stepbar .sb-todo{color:var(--grey)}
.stepbar .sb-dots{display:flex;gap:6px;margin:0 auto}
.stepbar .sb-dot{width:24px;height:24px;border-radius:50%;border:1.4px solid var(--line);
  background:transparent;color:var(--grey);font-size:11px;font-weight:800;cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;transition:.15s}
.stepbar .sb-dot:hover{border-color:var(--grey);color:var(--ink)}
.stepbar .sb-dot.done{border-color:var(--ok,#1f9d55);color:var(--ok,#1f9d55)}
.stepbar .sb-dot.now{border-color:var(--accent);background:var(--accent);color:#fff}
.stepbar .sb-right{display:flex;gap:8px;align-items:center;margin-left:auto}
@media (max-width:760px){
  .stepbar{flex-direction:column;align-items:stretch}
  .stepbar .sb-dots{margin:0}
  .stepbar .sb-right{margin-left:0;justify-content:space-between}
}

/* ============================================================================================
   IMPORT / EXPORT — the same numbered-question shape on both screens (v3.4.0 / v3.5.0)
   ============================================================================================
   These two screens replaced one screen that was doing four jobs. The point of the shared
   .wf-* markup is that the import and the export LOOK the same: numbered question, a row of
   tiles, then whatever that answer opens up. Learn it once, use it for both. */
.wf-step{margin:0 0 26px}
.wf-h{display:flex;align-items:center;gap:10px;font-size:17px;font-weight:800;color:var(--ink);margin:0 0 12px}
.wf-n{width:24px;height:24px;border-radius:50%;background:var(--accent);color:#fff;font-size:12px;
  display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto}
.wf-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:12px}
.wf-tile{display:flex;flex-direction:column;gap:5px;text-align:left;padding:14px 15px;cursor:pointer;
  background:var(--panel);border:1.5px solid var(--line);border-radius:12px;transition:.15s;color:var(--ink)}
.wf-tile b{font-size:14.5px}
.wf-tile span{font-size:12.5px;color:var(--grey);line-height:1.4}
.wf-tile:hover:not(:disabled){border-color:var(--accent);transform:translateY(-1px)}
.wf-tile.on{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent);background:var(--accent-soft)}
.wf-tile.soon,.wf-tile:disabled{opacity:.5;cursor:not-allowed}
.wf-body{margin-top:14px;padding:14px 16px;background:var(--panel);border:1px solid var(--line);border-radius:12px}

/* The diff headline. These chips are the answer to "what is about to happen to my data" — which is
   why the count that matters most (rows we could not read) is a chip and not a footnote. */
.imp-chips{display:flex;gap:10px;flex-wrap:wrap;margin:0 0 14px}
.imp-chip{padding:7px 13px;border-radius:999px;font-size:13px;border:1.5px solid var(--line);background:var(--panel)}
.imp-chip b{font-size:15px}
.imp-chip.new{border-color:var(--ok,#1f9d55);color:var(--ok,#1f9d55)}
.imp-chip.upd{border-color:var(--orange,#e07b00);color:var(--orange,#e07b00)}
.imp-chip.bad{border-color:var(--danger,#c0392b);color:var(--danger,#c0392b)}
.imp-chip.dupe{border-color:var(--grey)}
.imp-chip.zero{opacity:.45;border-color:var(--line);color:var(--grey)}
.nudge.bad{border-color:var(--danger,#c0392b);background:rgba(192,57,43,.07)}
.nudge.bad .nt{color:var(--danger,#c0392b)}

/* "Next on the list" — the one coming-soon connector actually being built. Without this every
   unbuilt connector looks equally far away, and the roadmap order in CONNECTORS says nothing. */
.conn-pill.next{border-color:var(--orange,#e07b00);color:var(--orange,#e07b00);background:transparent}
.conn-tile.soon.next{opacity:.85;border-color:var(--orange,#e07b00)}

/* ---- WHOSE WORKSPACE AM I ABOUT TO CHANGE? (v3.190.0) -----------------------------------------
   In theme.css, the sheet that loads last, because that is now the rule for anything that has to win
   on colour — two visible bugs have come from writing it in styles.css and losing.
   Darren: "it should be much more prominent." Everything below this line lands on that customer, so
   it reads as a state banner rather than a hint: full width, its own colour, the NAME first at size,
   and the id kept because a consultant on the phone has to read it out. */
.edge-wspicked{margin:12px 0 4px;padding:12px 14px;border-radius:var(--r);
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  border:1px solid var(--line);background:var(--panel-2, #FAFAFA)}
.edge-wspicked[data-picked="1"]{border:1px solid var(--accent);
  background:var(--blue-lt, rgba(0,78,254,.07));box-shadow:0 0 0 1px var(--accent) inset}
.edge-wspicked .wsp-none{color:var(--muted);font-size:13px}
.edge-wspicked .wsp-lab{text-transform:uppercase;letter-spacing:.08em;font-size:10.5px;
  font-weight:800;color:var(--muted)}
.edge-wspicked .wsp-name{font-size:19px;font-weight:800;color:var(--ink);overflow-wrap:anywhere}
/* The id is monospaced so it can be read out a character at a time without ambiguity, and dimmer
   because it is the thing you quote, not the thing you check. */
.edge-wspicked .wsp-id{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:11.5px;color:var(--muted);overflow-wrap:anywhere}
.edge-wspicked .wsp-clear{margin-left:auto}

/* ---- A TICK AND ITS WORDS STAY TOGETHER (v3.190.0) --------------------------------------------
   Darren: "Tick box not aligned." The migration checkbox was a `.wide` cell in a form grid, so the box
   centred itself under the column above it and the label wrapped to the next line — reading as an
   unlabelled tick followed by a stray sentence. A flex row keeps them on one line whatever the grid
   does around it, and `align-items:flex-start` keeps the box level with the FIRST line when the text
   wraps rather than floating to the vertical middle of two. */
.edge-check{display:flex;align-items:flex-start;gap:9px;font-weight:600}
.edge-check input[type=checkbox]{margin:2px 0 0;flex:0 0 auto}
.edge-check > span{font-weight:600}

/* ---- ADMINISTRATION: THE HUB AND THE STRIP (v3.250.0) -------------------------------------------
   Seven staff screens used to sit under four customer-facing sidebar groups. They are one area now.
   Every colour here is a TOKEN, never a literal: the .aw-card above carries a comment about what a
   hard-coded wash did in dark mode (near-white text on a near-white card, invisible, caught only in a
   screenshot), and this is the same shape of component. */
.admin-tiles{display:grid;gap:12px;grid-template-columns:repeat(auto-fill,minmax(260px,1fr))}
/* ---- THE THREE ZONES (v3.278.0) -------------------------------------------------------------
   A zone is a panel with a heading and a lead, then its tiles. The only thing it needs beyond an
   ordinary panel is breathing room BETWEEN zones, because three grids butted together read as one
   grid with stray text in it — which is the wall this change exists to break up.
   The lead is width-bounded: a full-width sentence over a 260px tile grid is hard to read back to. */
.admin-zone+.admin-zone{margin-top:18px}
.admin-zone>h2{margin-bottom:4px}
.admin-zone>.lead{max-width:62ch;margin-bottom:14px}
.admin-tile{display:grid;grid-template-columns:28px 1fr;grid-template-rows:auto auto;
  gap:4px 12px;align-items:start;text-align:left;padding:16px;cursor:pointer;font:inherit;
  background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:var(--r);
  transition:border-color .12s,background .12s}
.admin-tile:hover{border-color:var(--accent);background:var(--panel-2)}
.admin-tile:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.admin-tile-i{grid-row:1/3;width:24px;height:24px;fill:none;stroke:var(--accent);stroke-width:1.8}
.admin-tile-t{font-weight:700;font-size:15px;color:var(--ink)}
.admin-tile-b{font-size:13px;line-height:1.45;color:var(--muted)}
/* The strip. It sits ABOVE the view head, so moving between admin screens is one click rather than a
   trip back to the sidebar — which is the whole reason the areas were gathered up. Scrolls sideways on
   a narrow screen instead of wrapping into three rows above the heading. */
.admin-nav{display:flex;gap:6px;margin:0 0 14px;padding-bottom:6px;overflow-x:auto;
  border-bottom:1px solid var(--line)}
.admin-navb{flex:none;padding:6px 12px;font:inherit;font-size:13.5px;cursor:pointer;white-space:nowrap;
  background:transparent;color:var(--muted);border:1px solid transparent;border-radius:20px}
.admin-navb:hover{color:var(--ink);background:var(--panel-2)}
.admin-navb.on{color:#fff;background:var(--accent);border-color:var(--accent)}
.admin-navb:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* Raise an invoice (v3.251.0). The problem list is the form talking back — every problem at once, in
   the operator's words, so fixing one does not reveal the next. */
.edge-probs{margin:8px 0 0;padding-left:20px;color:var(--danger);font-size:13.5px;line-height:1.5}
.inv-lines input,.inv-lines select{width:100%;box-sizing:border-box}
.inv-lines td{vertical-align:top}
.inv-lines .inv-qty{max-width:80px}

/* The attention board (v3.252.0). Tone is carried by a left edge and the number, never by colour
   alone — a board read at a glance has to work for somebody who cannot tell red from green. */
.att-tiles{display:grid;gap:10px;grid-template-columns:repeat(auto-fill,minmax(230px,1fr))}
.att-tile{display:grid;grid-template-columns:auto 1fr;grid-template-rows:auto auto;gap:2px 12px;
  align-items:start;text-align:left;padding:14px 16px;cursor:pointer;font:inherit;
  background:var(--panel);color:var(--ink);border:1px solid var(--line);border-left-width:4px;
  border-radius:var(--r);transition:border-color .12s,background .12s}
.att-tile:hover{background:var(--panel-2)}
.att-tile:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.att-n{grid-row:1/3;align-self:center;font-size:26px;font-weight:800;line-height:1;min-width:1.2em;
  text-align:center}
.att-t{font-weight:700;font-size:14.5px}
.att-d{font-size:12.5px;line-height:1.4;color:var(--muted)}
.att-urgent{border-left-color:var(--danger)}
.att-urgent .att-n{color:var(--danger)}
.att-due{border-left-color:#c07a02}
.att-due .att-n{color:#c07a02}
.att-clear{border-left-color:#1f8b4c}
.att-clear .att-n{color:var(--muted)}
.att-unknown{border-left-color:var(--grey-mid)}
.att-unk{color:var(--grey);font-size:22px}
.att-line{font-weight:600}

/* Calendar access: the state and its explanation are two lines (v3.256.0). Microsoft's own error text
   is a whole sentence — as one run-on inline span it had no gap after the state and ran off the
   panel. Bounded and wrapping, so the longest answer is still readable. */
.cal-state{display:block}
.cal-why{display:block;margin-top:2px;max-width:52ch;font-size:12.5px;line-height:1.45;
  color:var(--muted);white-space:normal;overflow-wrap:anywhere}

/* ---- THREE CLASSES THAT WERE DOING NOTHING (v3.257.0) -------------------------------------------
   Darren, 29 Aug, on the Calendar access panel: "I think the layout of the calendar text doesn't wrap
   still." He was right, and the cause was not that panel. `.tablewrap` is used 21 times across the
   front end to make a wide table scroll instead of bursting its panel — and it had NO RULE ANYWHERE.
   Neither did `.num` (23 uses, meant to right-align a numeric column) or `.fieldrow` (6 uses).

   A class name in the markup that no stylesheet mentions is not a style that is subtly wrong; it is a
   style that was never applied, and it looks like an oversight in the panel rather than a hole in the
   sheet. test/lint's class census now fails on any class used three or more times with no rule, so
   the next one cannot sit there silently. */
.tablewrap{overflow-x:auto;max-width:100%;-webkit-overflow-scrolling:touch}
/* Only the HEADER is kept on one line. A data cell that cannot wrap is how one long value — a
   sentence of Microsoft's, say — forces a table wider than the screen and takes the panel with it. */
.tablewrap th{white-space:nowrap}
.tablewrap td{vertical-align:top}
.num{text-align:right;font-variant-numeric:tabular-nums}
.fieldrow{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin:8px 0}
.fieldrow>label{margin:0}


/* ================================================================================================
   MY DAY — THE CONSULTANT'S PHONE VIEW (v3.278.0)
   ------------------------------------------------------------------------------------------------
   Darren, 30 Aug 2026: "easily on their mobile phone... without needing the full-size browser."

   PHONE-FIRST, NOT PHONE-TOLERANT. Everything below is written for a narrow screen and RELAXES at
   640px, rather than being a desktop layout squeezed down. The difference shows up in the two things
   somebody actually does out there: Ring and Mark done are full-width blocks with a 48px minimum
   height, because the test of this screen is one-handed, outside, in the sun — not "does it fit".
   ============================================================================================== */
.myd-head h2{margin:0 0 4px}
.myd-list{display:flex;flex-direction:column;gap:12px}
.myd-card{background:var(--panel);border:1px solid var(--line);border-left:4px solid var(--blue);
  border-radius:10px;padding:14px 15px}
.myd-top{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:6px}
.myd-when{font-weight:800;color:var(--blue-ink);font-size:14px}
/* A DATE THAT IS NOT SET IS NOT A QUIET ROW. On a list sorted by date it would otherwise sit at the
   bottom looking ordinary, and "assigned to me, no date" is the booking that never happens. */
.myd-nodate{color:var(--orange)}
.myd-tag{font-size:11.5px;font-weight:700;letter-spacing:.02em;text-transform:uppercase;
  background:var(--blue-lt);color:var(--blue-dark-ink);border-radius:999px;padding:3px 9px}
.myd-onsite{background:#FFF1E3;color:#8A3B00}
/* ---- TENTATIVE / CONFIRMED / PAID, ON THE CARD (v3.551.0) -----------------------------------
   Darren: *"of course showing that it is tentative."* The same three colours the office diary uses
   for the same three states (see .bs-bk-* on the calendar blocks), so a consultant reading their
   phone and an office person reading the diary mean one thing by one colour. Solid rather than
   tinted: this card is read outside, in the sun, at arm's length. */
.myd-st-held{background:var(--orange,#FF6D00);color:#fff;border-color:var(--orange,#FF6D00)}
.myd-st-unpaid{background:var(--blue,#004EFE);color:#fff;border-color:var(--blue,#004EFE)}
.myd-st-paid{background:var(--ok,#1E8C50);color:#fff;border-color:var(--ok,#1E8C50)}
.myd-cust{margin:2px 0 2px;font-size:18px;line-height:1.25}
.myd-label{margin:0 0 6px;color:var(--muted);font-size:14px}
.myd-notes{margin:0 0 10px;font-size:13.5px;color:var(--ink);white-space:pre-wrap}
.myd-acts{display:flex;flex-direction:column;gap:8px;margin-top:10px}
/* 48px is the number, not a look. Below it, a thumb on a moving train misses. */
.myd-acts .btn{min-height:48px;display:flex;align-items:center;justify-content:center;gap:8px;
  text-decoration:none;font-size:15px;width:100%;box-sizing:border-box}
.myd-call{background:var(--blue);color:#fff;flex-wrap:wrap}
.myd-num{font-weight:600;opacity:.85;font-size:13px}
.myd-done{background:var(--orange);color:#151316}
.myd-zoom{border:1.5px solid var(--blue);color:var(--blue-ink);background:transparent}
/* WE COULD NOT READ THEM and NONE RECORDED share this style and never share a sentence — see
   mydayCard. The colour says "something is missing"; the words say which. */
.myd-nonum{margin:4px 0 0;font-size:13px;color:var(--muted);line-height:1.45}
.myd-pick{display:flex;flex-direction:column;gap:10px;margin-top:12px}
.myd-pick .btn{min-height:48px}
@media (min-width:640px){
  .myd-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px}
  .myd-acts .btn{width:auto;min-width:180px}
  .myd-acts{flex-direction:row;flex-wrap:wrap}
  .myd-pick{flex-direction:row;flex-wrap:wrap}
}

/* Step 3 does not autosave, and the slot its neighbours use for "saved ✓" must not read as one. */
.autosave.lr-unsaved{color:var(--orange);font-weight:700}
/* A ghost button that destroys something still has to look like it does. */
.btn.ghost.danger{border-color:var(--danger);color:var(--danger)}
/* ---- THE BLANK RED BOX (0283, 30 September 2026, v3.1113.0) --------------------------------------
   Darren: the Delete button beside "Merge a duplicate" on the customer record was a red box with no
   words. `.btn.danger` (with the base buttons, above) FILLS a button with --danger and writes white on
   it; the ghost rule just above turns the words --danger but never took the fill away, and
   `.btn.ghost{background:transparent}` loses to `.btn.danger` (same weight, later). Red words on a red
   fill. v3.1111.0's contrast pass took the fill away in a rule four thousand lines further down (see
   "A GHOST danger button was painted solid red"), so the build Darren was looking at predates it or
   loses it to anything that moves that block. The fill is taken away HERE too, beside the rule that
   caused it, so the ghost danger button is whole in one place — and the dark theme's heavier
   `html[data-theme] .btn.danger` (which gave it a pink wash at 0.75 opacity) is answered by one of the
   same weight: unfilled, the dark red ink, a clearer border. test/v31113arch.test.mjs lifts the real
   record header, resolves the cascade for the button's own classes, light and dark, plain and hovered,
   and checks the words are there and are not the colour of the fill. */
.btn.ghost.danger,.btn.ghost.danger:hover{background:transparent}
.btn.ghost.danger:hover{border-color:var(--danger);color:var(--danger)}
html[data-theme="dark"] .btn.ghost.danger,html[data-theme="dark"] .btn.ghost.danger:hover{background:transparent;
  color:#ff8f8f;border-color:rgba(255,107,107,.55)}

/* ================================================================================================
   THE CUSTOMER RECORD (v3.279.0) — numbers, and what you need before you arrive.
   ------------------------------------------------------------------------------------------------
   A form somebody fills in once and then edits at odd moments, so the layout is a plain two-column
   grid that collapses to one on a phone rather than anything clever. The only styling that carries
   MEANING is the warning colour: it appears on a number we could not read, on a list we could not
   load, and on the do-not-put-credentials-here line — three different sentences, one visual weight,
   because all three mean "read this before you type".
   ============================================================================================== */
.cd-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));margin-top:10px}
.cd-grid .field{max-width:none}
.cd-wide{grid-column:1/-1}
.cd-grid .hint{display:block;margin-top:4px;font-size:12px;line-height:1.4}
.cd-form{margin-top:18px;padding-top:14px;border-top:1px solid var(--line)}
.cd-form h3{margin:0;font-size:15px}
.cd-check{display:flex;align-items:center;gap:8px;margin-top:12px;font-size:14px}
.cd-acts{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:14px}
.cd-warn{color:var(--danger);font-size:13px;line-height:1.5}
.cd-nocreds{margin-top:12px;padding:10px 12px;border-left:3px solid var(--danger);
  background:rgba(192,57,43,.06);border-radius:0 6px 6px 0}
/* The primary contact is the one shown wherever only one fits, so it is marked on the row and not
   only in a column somebody has to scan for. */
.cd-primary td{background:var(--blue-lt)}
.cd-star{color:var(--orange)}
/* What we made of a number, versus what somebody typed. Shown only when the two differ — otherwise
   it is a line of noise under every row. */
.cd-typed{display:block;font-size:11.5px;color:var(--muted)}

/* The details block on a My day card. A definition list in all but name: the label is fixed-width so
   the values line up and can be read down the left edge at a glance. */
.myd-det{margin:8px 0 4px;padding:10px 12px;background:var(--bg);border-radius:8px}
.myd-d{display:flex;gap:10px;padding:3px 0;font-size:13.5px;line-height:1.45}
.myd-dk{flex:0 0 82px;color:var(--muted);font-weight:700;font-size:12px;text-transform:uppercase;
  letter-spacing:.02em;padding-top:2px}
.myd-dv{flex:1;min-width:0;overflow-wrap:anywhere}
.myd-dv a{color:var(--blue-ink)}
@media (max-width:420px){
  /* On the narrowest phones the fixed label column squeezes the value into two characters a line. */
  .myd-d{flex-direction:column;gap:0}
  .myd-dk{flex:none}
}

/* A consultant with nothing booked is still offered — the picker must never be empty — but the
   button says so before it is pressed rather than after. */
.myd-who{display:flex;align-items:center;justify-content:space-between;gap:10px}
.myd-who.myd-quiet{opacity:.72;font-weight:600}
.myd-none{font-size:12px;font-weight:600;color:var(--muted);text-transform:none}

/* `.myd-guess` was removed in v3.284.0 along with the guess itself: an identity is now recorded or
   asked for, never inferred, so there was nothing left to confirm. */


/* ================================================================================================
   /me — A CONSULTANT'S SCREEN, NOT THE APP WITH A DIFFERENT VIEW OPEN (v3.282.0)
   ------------------------------------------------------------------------------------------------
   Darren: "We don't need them to see all the details as though they were logging in on the desktop.
   This would be a very specific view for that consultant... The /me should be specific for
   Consultants only."

   ONE CLASS ON THE ROOT, and no second front end. A consultant delivering a session needs their
   schedule, the customer's details and what that customer has left — not thirty other screens. The
   sidebar, the workspace switcher and the tip bar all go; the hamburger goes with them, because a
   menu with nothing in it is worse than no menu.

   IT IS NOT A PERMISSION. Anybody who reaches #myday still gets whatever the routes allow them —
   this decides what is DRAWN, and every route still refuses on its own. Hiding a menu is a courtesy,
   never a guard, which is the same rule the admin hub follows.
   ============================================================================================== */
html.myday-only .sidebar,
html.myday-only .sb-burger,
html.myday-only .sb-backdrop,
html.myday-only .tipbar,
html.myday-only .apptop-right .ws-switch,
html.myday-only .nudge{display:none!important}
html.myday-only .appwrap{display:block}
html.myday-only #app-shell main{padding:16px 14px;max-width:760px;margin:0 auto}
/* The top bar keeps the theme toggle and the sign-out route only. It is the whole chrome now, so it
   says where you are rather than which product this is. */
html.myday-only .apptop{padding:12px 16px}
html.myday-only .apptop-title{font-size:16px;font-weight:800}
/* The view header repeats what the top bar already says on a screen this small. */
html.myday-only #view-myday .view-head .lead{font-size:13.5px;color:var(--s-hero-muted)}

/* A consultant's own numbers. Small, countable, and deliberately not money — this gets held up in
   front of customers. */
.myd-stats{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
.myd-stat{flex:1 1 auto;min-width:88px;background:var(--bg);border:1px solid var(--line);
  border-radius:8px;padding:8px 10px;display:flex;flex-direction:column;gap:2px}
.myd-stat b{font-size:20px;line-height:1.1;color:var(--blue-ink)}
.myd-stat span{font-size:11.5px;text-transform:uppercase;letter-spacing:.03em;color:var(--muted);
  font-weight:700}
.myd-stat-bad b{color:var(--danger)}
.myd-stat-warn b{color:var(--orange)}

/* What the customer has left. Tinted like the details block so it reads as reference rather than as
   an action — nothing here is a button, and it must not look like one. */
.myd-rem{margin:8px 0 4px;padding:9px 11px;border-radius:8px;background:var(--blue-lt);
  font-size:13px;line-height:1.5;display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 12px}
.myd-remk{font-size:11px;text-transform:uppercase;letter-spacing:.03em;font-weight:800;
  color:var(--blue-dark-ink);flex:1 0 100%}
.myd-remline b{color:var(--blue-dark-ink)}
/* An entitlement that exists and cannot be spent is struck through, not hidden — the consultant
   should see it is there AND that it is not available. */
.myd-remline-off{opacity:.6;text-decoration:line-through}
.myd-rem-unknown{background:var(--bg);color:var(--muted)}
.myd-rem-lapsed{background:#FFF1E3}
.myd-remwarn{flex:1 0 100%;color:#8A3B00;font-weight:600}

/* ---- /me: SAVING YOUR NAME, AND THE WAY OUT (v3.299.0) ----------------------------------------
   Darren, 31 Aug 2026: "how come there's no logout button for /me?" There was one — in the sidebar,
   which is the one thing a phone does not show, so the one screen built for a phone was the one
   screen you could not sign out of.

   And: "why would I still need to indicate who I am? My email address is tied to my account." Right,
   so the name now comes off the account and this offers to write it down — which also ticks "takes
   bookings", the other half of the same complaint.

   TOUCH TARGETS, not links. Everything on this screen is pressed one-handed, outside, in the sun. */
.myd-remember{background:var(--accent-soft);border-radius:var(--r);padding:10px 12px;margin:8px 0 4px}
.myd-remember p{margin:0 0 8px;color:var(--ink)}
.myd-remember .btn{min-height:44px;width:100%}
/* The way out sits under the numbers rather than beside the heading: it is the last thing somebody
   wants on this screen, and a sign-out button next to a title gets pressed by accident. */
.myd-out{margin:14px 0 0;padding-top:10px;border-top:1px solid var(--line);
  display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.myd-out .btn{min-height:40px}

/* ================================================================================================
   THE REMINDER EDITOR — MARKS, THE LIVE PREVIEW, AND THE ATTACHMENT (v3.298.0)
   ------------------------------------------------------------------------------------------------
   Darren picked "simple marks + live preview" over a rich editor. The preview is the half that makes
   the marks usable: nobody can be expected to read `**by Friday**` and see bold, so the pane below
   the box shows the message as it will arrive.

   ---- THE ONE PANEL IN THE PRODUCT THAT DELIBERATELY IGNORES THE THEME ----------------------------
   `.rem-live` paints white paper with dark ink, both stated outright, in BOTH themes. Everywhere
   else that would be a bug — and the suite has a check for exactly this shape, because
   painting a background from `var(--tint, ...)`, a variable this sheet does not define, once put
   near-white --ink text on a near-white card in dark mode and the copy simply could not be read.
   (The check greps this file for that declaration, so the sentence above deliberately does not
   spell it out — an explanatory comment that trips its own test has cost a release here before.)

   Here it is the point. The pane is a PICTURE OF AN EMAIL, and the email arrives on white however
   this screen is set. A dark-mode preview would show somebody a message that does not exist, and
   they would size their wording against a rendering their customer never sees. So both halves are
   literal: no variable, no fallback, and no way for one to move without the other.

   The chrome AROUND the pane still follows the theme, so it sits in the screen rather than glowing
   out of it. The same class does duty on the board and in the against-real-records preview, so all
   three show the message identically and there is one place to change it.
   ================================================================================================ */
.rem-marks{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.82rem;
  letter-spacing:.01em}
/* White paper, dark ink, stated in full — see the note above on why this one does not use tokens. */
.rem-live{background:#FFFFFF;color:#231F20;border:1px solid var(--line);border-radius:var(--r);
  padding:10px 12px;margin:6px 0 10px;font-size:.92rem;line-height:1.55;
  /* A long pasted URL in a message must not push the table out sideways. */
  overflow-wrap:anywhere}
.rem-live p{margin:0 0 10px}
.rem-live p:last-child{margin-bottom:0}
.rem-live ul{margin:0 0 10px;padding-left:20px}
.rem-live li{margin:0 0 4px}
.rem-live a{color:#004EFE}
/* Inside a table cell it is a preview of a LINE, not a document — no paper, no border, and the
   theme's own muted ink, because a white block in every row of a dark table is a row of lights. */
td .rem-live{background:transparent;color:var(--muted);border:0;padding:0;margin:2px 0 4px;
  font-size:.86rem}
td .rem-live a{color:var(--blue-ink)}

/* "with the invoice attached" — a fact about the row, beside the button that acts on it. */
.rem-clip{display:inline-block;margin-top:4px;padding:1px 7px;border-radius:999px;font-size:.74rem;
  font-weight:600;background:var(--accent-soft);color:var(--blue-ink)}
.rem-clip::before{content:"\1F4C4\00a0"}

/* The tick-box reads as a decision, not as another field in the grid. */
.rem-attach{display:flex;align-items:center;gap:8px;margin:8px 0 2px;font-weight:600;color:var(--ink)}
.rem-attach input{width:auto;margin:0}

/* ================================================================================================
   OUR DIARY versus THEIR OUTLOOK, ON A PHONE (v3.302.0)
   ------------------------------------------------------------------------------------------------
   Darren: "Is there a way for Consultants to see what they're booked to on each day so it can be
   matched to what their Outlook calendar says?"

   The panel is DELIBERATELY QUIET when everything agrees, and only earns colour when it does not.
   A comparison that shouts on a good day is one people stop reading, and this screen is read
   one-handed, outside, in the sun — the same standing rule as every other control on /me.
   ================================================================================================ */
.myd-diary{margin-top:14px}
.myd-diary h3{margin:0 0 6px}
.myd-diary h4{margin:14px 0 6px;font-size:.92rem;color:var(--muted)}
/* Amber, not red. A booking missing from somebody's calendar is a thing to fix, not a fault of
   theirs — and red on this screen already means a failed save. */
.myd-diary-bad{color:#8A3B00}
.myd-diary-list{margin:0;padding-left:20px}
.myd-diary-list li{margin:0 0 6px;line-height:1.5}
/* Their own appointments: present so the office cannot book over a dentist, styled so it reads as
   context rather than as a list of problems. */
.myd-diary-quiet li{color:var(--muted)}
/* A calendar we could not read gets the panel's quietest treatment — it is not news about the
   consultant's day, it is news about our connection to Microsoft. */
.myd-diary-unknown{border-left:3px solid var(--line)}
.myd-diary-unknown .lead{color:var(--muted)}
/* The card badge. Only ever drawn for the one state worth acting on. */
.myd-tag-warn{background:#FFF8E7;border-color:#E0A93B;color:#8A3B00;font-weight:600}

/* ================================================================================================
   THE WEEK GRID (v3.302.0)
   ------------------------------------------------------------------------------------------------
   Darren: "Office admin should be able to see a calendar view or something similar for all
   Consultants."

   OUR BOOKINGS ARE SOLID; THEIR OWN DIARY IS QUIET. That contrast is the whole design: an office admin
   scanning for a free slot needs to tell at a glance between "we have committed this person to a
   customer" and "they have something of their own on". Two weights of the same grid, not two colours
   competing for attention.

   It scrolls horizontally inside .tablewrap rather than reflowing — seven columns is what a week IS,
   and a week that stacks into a list on a laptop stops answering "who is free on Thursday".
   ================================================================================================ */
/* The WEEK TABLE. Renamed from .wk-grid in v3.312.0: that name already belonged to a
   `display:grid` CARD layout in styles.css, and `display:grid` on a <table> removes it from table
   layout entirely — header and body stop sharing columns and the whole thing collapses into one
   blob. Two screens, one class name, two meanings. */
.wk-week{font-size:.84rem;table-layout:fixed;width:100%}
.wk-week th[scope="row"]{text-align:left;white-space:nowrap;vertical-align:top;padding-top:8px;
  width:150px}
.wk-week td{vertical-align:top;min-width:132px}
/* An event title is longer than a day column is wide, every time. Wrapping is what keeps the column
   the width of a DAY rather than the width of the longest meeting somebody booked. */
.wk-week td>div{overflow-wrap:anywhere;white-space:normal}
.wk-week td.num{width:96px;white-space:nowrap}
/* v3.436.0 — today's column is the wide one and it is orange, on the office week as on the
   consultant's own. The widths are <col> widths because the table is fixed-layout: today takes
   roughly twice a normal day; the rest share what is left. */
.wk-week col.wk-col-name{width:150px}
.wk-week col.wk-col-num{width:96px}
.wk-week col.wk-col-today{width:26%}
.wk-week .wk-today{box-shadow:inset 0 3px 0 var(--orange);
  background:color-mix(in srgb,var(--orange) 7%,transparent)}
.wk-week th.wk-today{color:var(--orange)}
.wk-today-tag{display:inline-block;font-size:.7rem;font-weight:800;letter-spacing:.08em;
  text-transform:uppercase;color:#fff;background:var(--orange);border-radius:999px;padding:0 6px;
  margin-left:4px;vertical-align:middle}
/* A booking WE made. Solid, branded, and the customer's name is the thing being read. */
.wk-book{background:var(--accent-soft);border-left:3px solid var(--blue);border-radius:4px;
  padding:3px 6px;margin:0 0 4px;line-height:1.35;overflow-wrap:anywhere}
.wk-onsite{border-left-color:var(--orange)}
/* v3.1148.0 (7 October 2026) — AN OFFERED DATE. Jarryd: "Offering dates does not block them out in the
   calendar." Held, not booked: outlined and hatched so it never reads as confirmed work, in theme tokens
   so it holds up in both themes (ink on the panel colour, never a fixed light wash). */
.wk-book.wk-held{background:repeating-linear-gradient(135deg,var(--panel) 0 6px,var(--panel-2) 6px 12px);
  border:1px dashed var(--blue-ink);border-left:3px dashed var(--blue-ink);color:var(--ink)}
.wk-book.wk-held.wk-onsite{border-color:var(--orange-ink)}
.wk-heldtag{font-size:.72rem;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--blue-ink)}
.wk-book[data-week-enq]{cursor:pointer}
.wk-h{color:var(--muted);font-size:.78rem}
/* v3.443.0 — the session's label under the customer's name. */
.wk-lbl{display:block;color:var(--muted);font-size:.78rem}
/* Their own appointment. Present so nothing gets booked over it, quiet so it never competes. */
.wk-own{border-left:3px solid var(--grey-mid);border-radius:4px;padding:3px 6px;margin:0 0 4px;
  color:var(--muted);line-height:1.35;overflow-wrap:anywhere}
.wk-tent{border-left-style:dashed}
.wk-empty{color:var(--muted)}
/* WHEN THE WEEKEND IS DRAWN it is dimmed rather than coloured in, so five working days read as the
   week and Saturday reads as an exception. Since v3.314.0 it is normally not drawn at all — Platinum
   does not work weekends — and comes back only on request or when something has landed on one. */
.wk-weekend{background:var(--panel-2)}
/* The weekend control, ABOVE the grid on both week screens (v3.314.0). It was below, which on a
   phone put it under a scrolling table — off the bottom of the one screen built for a phone. */
.wk-weekend-ctl{margin:0 0 10px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.wk-weekend-ctl .hint{margin:0}
.wk-weekend-why{margin:0 0 10px}
/* Today, on a consultant's own week. The one column they look at first, so it is findable without
   counting along the row. */
.myd-today{box-shadow:inset 0 2px 0 var(--orange)}
th.myd-today{color:var(--orange)}

/* ================================================================================================
   THE FOUR AREAS (v3.303.0)
   ================================================================================================
   Darren, 31 Aug 2026: "I want us to make sure that the UI is very clean and simple for customers
   but also clean and simple for Office Admin and Consultants."

   So these two screens deliberately do LESS than the screens they sit above. One big thing, then
   supporting detail — not a wall of equal-weight panels. Every colour is a token so both themes and
   the explicit toggle follow; nothing here defines a colour only inside a media query. */

/* ---- The app switcher's two headings -------------------------------------------------------- */
.apps-menu .apps-head{padding:10px 14px 4px;font-size:.72rem;letter-spacing:.08em;
  text-transform:uppercase;color:var(--muted);font-weight:700}
/* "We could not check" must not look like a lock. Quieter than open, not redder than refused. */
.apps-menu .app-row.unsure .as{color:var(--muted);font-style:italic}

/* ---- The consultant's own area -------------------------------------------------------------- */
/* WHAT IS NEXT is the only thing on this screen that gets a box of its own. Everything a consultant
   asks between appointments is inside it: when, who, what they want, and how to get in. */
.con-next{border:1px solid var(--line);border-left:4px solid var(--accent);border-radius:10px;
  padding:14px 16px;background:var(--panel-2)}
.con-next.con-over{border-left-color:var(--danger)}
.con-next h3{margin:2px 0 4px}
.con-when{margin:0;font-weight:700;color:var(--accent-ink)}
.con-next.con-over .con-when{color:var(--danger)}
/* The purpose is the field a consultant most wants before walking in, so it is readable prose rather
   than a hint — and its absence is a hint, which is the right way round.
   BOTH COLOURS FROM THE THEME. `var(--tint,#F2F6FF)` is banned in this sheet and the ban is
   screenshot-verified: there is no --tint here, so it always resolves to the light fallback and put
   near-white text on a near-white card in dark mode. The copy was there and could not be read. */
.con-purpose{background:var(--panel-2);color:var(--ink);border-radius:8px;padding:8px 10px;margin:8px 0}
.con-day{margin:10px 0}
.con-day h4{margin:0 0 4px;font-size:.86rem;color:var(--muted)}
.con-list{list-style:none;margin:0;padding:0}
.con-list li{padding:6px 0;border-bottom:1px solid var(--line);display:flex;gap:10px;
  align-items:baseline;flex-wrap:wrap}
.con-list li:last-child{border-bottom:0}
.con-list .linkbtn{margin-left:auto}
.con-undated{border-left:4px solid var(--warn)}

/* ---- The office front page ------------------------------------------------------------------ */
/* Four counts, each a door. A grid rather than a row so it does not overflow on a laptop, and the
   tiles stay legible at 320px because an office admin does sometimes check this on a phone. */
/* v3.441.0 — seven tiles now; at 190px minimum the seventh (Emails) wrapped onto a row of its own. */
.off-tiles{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(136px,1fr));
  margin:10px 0 4px}
/* v3.443.0 — it still wrapped 6 + 1 at 1400px (panel ≈ 1100px wide). One row of seven above 1200px;
   below that, auto-fit, which gives 4 + 3 or 2 + 2 + 2 + 1 rather than a lone tile under six. */
@media(min-width:1200px){.off-tiles{grid-template-columns:repeat(7,minmax(0,1fr))}}
@media(max-width:1199px) and (min-width:700px){.off-tiles{grid-template-columns:repeat(4,minmax(0,1fr))}}
@media(max-width:699px){.off-tiles{grid-template-columns:repeat(2,minmax(0,1fr))}
  .off-tiles>.off-tile:last-child:nth-child(odd){grid-column:1/-1}}
.off-tile{display:flex;flex-direction:column;gap:2px;text-align:left;cursor:pointer;
  border:1px solid var(--line);border-radius:10px;padding:12px 14px;background:var(--panel);
  color:inherit;font:inherit}
.off-tile:hover{border-color:var(--accent)}
.off-n{font-size:1.7rem;font-weight:800;line-height:1.1}
.off-lab{font-weight:600}
.off-blurb{font-size:.8rem;color:var(--muted)}
/* Tone is applied to the NUMBER, not the whole tile — a wall of red tiles is a wall nobody reads. */
.off-bad .off-n{color:var(--danger)}
.off-warn .off-n{color:var(--warn)}
/* AN UNKNOWN IS NOT A ZERO AND MUST NOT LOOK LIKE ONE. Dashed, greyed, and the blurb says so. */
.off-unknown{border-style:dashed}
.off-unknown .off-n{color:var(--muted)}
.off-warn-inline,.off-warn{color:var(--warn)}
.off-doors{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}
.off-door{display:flex;flex-direction:column;gap:3px;text-align:left;cursor:pointer;
  border:1px solid var(--line);border-radius:10px;padding:12px 14px;background:var(--panel);
  color:inherit;font:inherit}
.off-door:hover{border-color:var(--accent)}
/* ---- START SOMETHING (v3.316.0) ---------------------------------------------------------------
   Darren, 1 Sep 2026: "there should be a button that's easily accessible from the office area
   saying: Create new invoice / quote / customer / site / contact / booking / enquiry. All these
   different options are all available from the one page, nice and simple."

   Wider than the navigation doors below and a heavier label, because these are the VERBS on the
   page and everything else on it is a number. Auto-fit so seven of them wrap into a readable block
   rather than one scrolling row. */
.off-news{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
  margin:10px 0 4px}
.off-new{display:flex;flex-direction:column;gap:3px;text-align:left;cursor:pointer;
  background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r);padding:12px 14px;
  font:inherit;color:var(--ink);min-height:44px}
.off-new b{font-size:14px}
.off-new .hint{font-weight:400;line-height:1.4}
.off-new:hover{border-color:var(--accent);background:var(--panel)}
.off-new:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* A follow-up the board has flagged carries the flag through to the consultant's own list, so the
   two screens cannot disagree about what is urgent. */
.enq-flag td:first-child{border-left:3px solid var(--warn);padding-left:7px}

/* ---- The member's dashboard (v3.303.0) ------------------------------------------------------- */
/* WHAT IS COMING sits above what is left, and gets the accent bar, because it is the question a
   member opens this screen with. */
.edge-next{border-left:4px solid var(--accent)}
/* The member's own ledger is a reference, not a headline — collapsed, and quiet when closed. */
.edge-ledger-own summary{cursor:pointer;padding:4px 0}
.edge-ledger-own summary:hover{color:var(--accent-ink)}

/* ---- One customer, one picture (0102, v3.304.0) ---------------------------------------------- */
/* The workspace link sits directly above the three panels that depend on it, so "not linked" and
   three empty panels read as one fact rather than four. Dashed while unlinked — the same visual
   grammar as an unknown count on the office page: not an error, not yet an answer. */
.dir-link{border-left:4px solid var(--ok)}
.dir-link.dir-unlinked{border-left-color:var(--grey-mid);border-style:dashed}
/* 0505, v3.803.0 — every workspace a customer holds, with what each one is paying for. Same shape
   as the suggestion list above it on purpose: they are the same kind of row, one confirmed and one
   proposed, and a second layout for the confirmed half would read as a different kind of thing. */
.dir-ws{list-style:none;margin:8px 0 0;padding:0}
.dir-ws li{display:flex;gap:12px;align-items:flex-start;justify-content:space-between;
  padding:8px 0;border-bottom:1px solid var(--line)}
.dir-ws li:last-child{border-bottom:0}
.dir-ws-holds{margin-top:5px;display:flex;flex-wrap:wrap;gap:5px}
.dir-ws-folk{margin-top:5px;font-size:12.5px;color:var(--ink-soft,inherit);word-break:break-word}
.dir-sugg{list-style:none;margin:8px 0 0;padding:0}
.dir-sugg li{display:flex;gap:12px;align-items:center;justify-content:space-between;
  padding:8px 0;border-bottom:1px solid var(--line)}
.dir-sugg li:last-child{border-bottom:0}

/* The list price beside a member's own price (v3.304.0). Quiet, and legible — a discount nobody can
   see is a discount nobody values, but it must not shout over the number they actually pay. */
.bi-was{font-size:.74rem;color:var(--muted);font-weight:400}
/* Open holds beside "Record a delivery" — a warning, not an error: the operator may still be
   recording genuinely separate work. */
.edge-holds-warn{border:1px solid var(--warn);border-left-width:4px;border-radius:8px;
  padding:10px 12px;margin:10px 0;background:var(--panel-2);color:var(--ink)}

/* ---- Notification switches (0104, v3.305.0) -------------------------------------------------- */
/* A switched-off row is quieted, not struck through: it is still a live part of the product that
   somebody has paused, and strikethrough reads as deleted — which is the exact confusion this
   feature exists to remove. */
/* v3.1124.0 — a switched-off row reads muted by its COLOUR, not by opacity: opacity also faded the hint text under
   it below a readable contrast (the a11y sweep, on "Pop a card on file", which ships off). */
.notif-off td{color:var(--muted)}
.notif-off td:first-child{border-left:3px solid var(--warn);padding-left:7px}
/* 0147 — a message whose wording an operator has rewritten. */
.notif-custom{display:inline-block;font-size:11px;font-weight:600;color:var(--blue-ink);
  background:color-mix(in srgb,var(--blue) 12%,transparent);border-radius:5px;padding:1px 6px;
  vertical-align:middle}
/* 0147 — the wording editor's own fields: label above, full-width control below. */
.nw-fld{display:block;margin:10px 0;font-weight:600}
.nw-fld input,.nw-fld textarea{display:block;width:100%;margin-top:4px;font-weight:400}
/* ---- v3.1120.0 — every email, with chips and a search (public/app.js notifHtml) ----------------
   The search box sits on its own line above the two chip rows; the tags under an email's name wrap
   rather than widen the table on a phone; an opened email's preview is the white "as it will arrive"
   card, which `td .rem-live` otherwise flattens for the reminders board. */
.ecat-search{display:flex;align-items:center;gap:var(--sp-2,8px);margin:var(--sp-3,12px) 0 0;font-weight:600;max-width:520px}
.ecat-search input{flex:1;min-width:0;font-weight:400}
.ecat-tags{display:inline-flex;flex-wrap:wrap;gap:4px;align-items:center;margin:4px 0 2px}
.ecat-tags code{font-size:var(--fs-xs,12px);color:var(--muted)}
.ecat-detail>td{background:var(--s-sunk,var(--panel-2))}
.ecat-open p{margin:0 0 8px}
.ecat-open .rem-live{background:#FFFFFF;color:#231F20;
  border:1px solid var(--line);border-radius:var(--rad-sm,8px);padding:var(--sp-3,12px);margin:0 0 8px;max-width:640px}
.ecat-open .rem-live a{color:#004EFE}
/* ---- A NICER "CHOOSE FILE" (v3.417.0) --------------------------------------------------------
   Darren: the default file button "looks so cheap". Style the native control's own button — no
   markup change, so every <input type=file> in the app gets it and nothing about how it works
   changes. The filename text beside it stays the browser's. */
input[type=file]{font-size:13px;color:var(--muted)}
input[type=file]::file-selector-button{
  margin-right:10px;padding:7px 14px;border:1px solid var(--line);border-radius:var(--r);
  background:var(--grey-light);color:var(--ink);font-weight:600;font-size:13px;cursor:pointer;
  transition:background .12s,border-color .12s,color .12s}
input[type=file]::file-selector-button:hover{background:var(--blue);color:#fff;border-color:var(--blue)}
/* ---- WHAT THE MEMBERSHIP INCLUDES (v3.419.0) — a compact read-only catalogue ---- */
.wm-inc{list-style:none;margin:6px 0 0;padding:0}
.wm-inc li{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:7px 0;
  border-top:1px solid var(--line)}
.wm-inc li:first-child{border-top:0}
.wm-inc-badge{flex:0 0 auto;font-size:11.5px;font-weight:600;color:var(--muted);
  border:1px solid var(--line);border-radius:5px;padding:1px 7px;white-space:nowrap}
.wm-inc-free{color:var(--blue-ink);background:color-mix(in srgb,var(--blue) 10%,transparent);
  border-color:transparent}
/* ---- THE ENQUIRY THROUGH-LINE (v3.420.0) — Quoted → Invoiced → Paid → Scheduled → Delivered ---- */
/* ================================================================================================
   WHERE IT IS UP TO, AS EPAULETTES (v3.588.0)
   ================================================================================================
   Darren, 9 September 2026:
     *"Where is this up to? Can we make that with arrows, so each one is like an epaulette into the
     next one, so we can see that it goes in a sequence?"*
     *"The one that is highlighted isn't as bold and obvious as I would like it. Please make it more
     obvious that that is where it's up to."*
     *"I think they can be smaller, so we can have more easily on the screen."*

   All three at once, and they pull against each other — a chevron that interlocks needs its
   neighbours flush, "more obvious" needs the current one to stand out from ten tinted siblings, and
   "smaller" takes away the room to do either with borders and shadows. So:

   THE SEQUENCE IS THE SHAPE. `clip-path` cuts a notch out of the left edge and a point onto the
   right, and a negative left margin slides each one into the notch of the last. The row reads as
   one strip that flows forwards, which is what an epaulette is.

   COLOUR CARRIES THE STATE, NOT A BORDER. A clipped element has no usable border — the clip cuts
   through it — so what was an outline is now a fill. That is also what makes "more obvious"
   affordable: the current step is a SOLID fill in the status's own colour with white text, against
   flat tints either side. Nothing else on the row is solid.

   SMALLER: 12px, tighter padding, and the tick mark is gone from every step except the ones that
   have actually been reached (a row of ○ is ten characters of nothing).

   IF `clip-path` IS UNSUPPORTED the shapes are plain rectangles in the same colours, still in
   order, still with the current one solid. Degraded, not broken. */
/* ---- THE BARE STAGE ROW (0248, v3.616.0) ----------------------------------------------------
   The stages lost their panel and heading (Darren: *"it doesn't need to be inside a block"*), so the
   row is the section now. It keeps the `scroll-margin-top` every block has — a jump chip that lands
   behind the sticky bar reads as a broken link — and it drops the panel's padding rather than
   inheriting nothing and sitting flush against the identity band above it.
   THE `?` MOVED ONTO THIS ROW. It was on the heading, and the heading is gone; it is the only place
   the built-in stages explain themselves, so it is laid out beside the last chip rather than
   dropped. `align-self:center` so it sits on the chips' own line instead of the top of a wrapped
   second row. */
.enq-upto-bare{display:flex;align-items:center;flex-wrap:wrap;gap:8px;scroll-margin-top:52px}
.enq-upto-bare > .tl-steps{margin:0;flex:1 1 auto;min-width:0}
.tl-why{align-self:center;flex:0 0 auto}
.tl-steps{display:flex;flex-wrap:wrap;column-gap:0;row-gap:6px;margin:6px 0 4px;
  /* The first chip's own notch would otherwise hang off the panel's padding edge. */
  padding-left:8px}
.tl-step{display:flex;align-items:center;gap:5px;font-size:12px;line-height:1;
  /* The point on the right and the notch on the left are the same 9px, so they interlock exactly. */
  padding:7px 12px 7px 19px;margin-left:-9px;border:0;border-radius:0;
  background:color-mix(in srgb,var(--pill,var(--line)) 16%,var(--panel));
  color:var(--muted);font-weight:600;
  clip-path:polygon(0 0,calc(100% - 9px) 0,100% 50%,calc(100% - 9px) 100%,0 100%,9px 50%)}
/* A step that has actually been reached: the tint deepens and the label takes the ink colour, so
   the row reads left-to-right as "done, done, done — here — not yet". */
.tl-step.tl-on{background:color-mix(in srgb,var(--pill,var(--blue)) 34%,var(--panel));
  color:var(--ink)}
.tl-step .tl-mark{font-weight:700;display:none}
.tl-step.tl-on .tl-mark{display:inline;color:color-mix(in srgb,var(--pill,var(--blue)) 90%,var(--ink))}
.tl-step.tl-on .tl-lab{font-weight:700}
/* ---- ONE CONTROL, AND AN ORANGE RIM ON WHERE IT IS (0181, v3.494.0) ------------------------
   Darren, 8 Sep 2026: *"If you're going to put a blue tick in it, maybe put an orange rim around
   it."* The chip that says where it is now was blue-on-blue with the reached ones, so on a screen
   of eight blue-ish chips the one that mattered was the hardest to pick out. The rim is the brand
   orange, two pixels, with a soft ring outside it, and the tick stays blue — one colour saying
   "a record backs this" and another saying "this is where it is".

   Every chip is also a BUTTON now (the "Move it to:" bar it replaces is gone), so it needs a
   pointer, a hover and a focus ring. A chip that cannot be moved to stays a div and gets none of
   them: nothing on the screen invites a press that does nothing. */
button.tl-step{font:inherit;font-size:12px;font-weight:600;cursor:pointer;
  transition:background .12s,color .12s}
button.tl-step:hover{background:color-mix(in srgb,var(--orange) 30%,var(--panel));color:var(--ink)}
/* A focus ring is drawn OUTSIDE the box, and this box is clipped — so an outline would be cut off
   at the chevron. An inset ring is inside the clip and survives it. */
button.tl-step:focus-visible{outline:0;box-shadow:inset 0 0 0 2px var(--orange)}
/* ---- WHERE IT IS NOW: THE ONLY SOLID ONE ON THE ROW (v3.588.0) ----------------------------
   Was a 2px orange rim on a pale chip (v3.494.0), which Darren said twice was not obvious enough,
   and he is right — a rim on one of ten tinted pills is a detail you have to look for. Now it is
   the only filled shape in the strip, in the status's OWN colour (so the row still agrees with the
   board and the set-up screen), with white text and a lift. It also sits a shade larger than its
   neighbours, which is what an epaulette does at the shoulder. */
/* The FILL and its contrast rule are in styles.css — see the long note there on why a raw `--pill`
   background is not safe. This is the shape and the weight. */
.tl-step.tl-now{font-weight:800;
  padding:9px 14px 9px 21px;z-index:1;position:relative;
  filter:drop-shadow(0 1px 2px color-mix(in srgb,var(--pill,var(--orange)) 45%,transparent))}
.tl-step.tl-now .tl-lab{font-weight:800;color:#fff}
.tl-step.tl-now .tl-mark{color:#fff}
button.tl-step.tl-now:hover{background:color-mix(in srgb,var(--pill,var(--orange)) 60%,#231F20)}
/* A stage a PERSON set, with no record behind it. Said, not hidden — the honest half of the rule
   the pressable chip replaced. A dashed border cannot survive the clip, so it is a hatch instead. */
.tl-step.tl-now.tl-byhand{background-image:repeating-linear-gradient(135deg,
  transparent 0 5px,color-mix(in srgb,#fff 22%,transparent) 5px 10px)}
.tl-hnote{font-style:italic;opacity:.85}
/* ================================================================================================
   THE ENQUIRY, AS BLOCKS YOU CAN JUMP TO (v3.495.0)
   Darren: *"maybe when we scroll up and down, there should be little buttons at the top that you
   can click that take you directly to what you're looking for."*
   ================================================================================================ */
/* STICKY, because a jump bar you have to scroll back up to is not a jump bar. It sits inside the
   record's own scroller (the modal), so `top:0` is the top of that and not of the window. */
.enq-jump{position:sticky;top:0;z-index:5;display:flex;flex-wrap:wrap;gap:6px;
  padding:8px 0 8px;margin:2px 0 6px;
  background:var(--panel);border-bottom:1px solid var(--line)}
.enq-jbtn{white-space:nowrap}
/* ---- A CHIP WEARS ITS OWN BLOCK'S COLOUR (0247, v3.615.0) -----------------------------------
   Darren, 11 September 2026: *"make sure that the chips at the top match the heading colours down
   the bottom as well please."*
   THE SAME TWO VALUES, not a second palette. `--area` and `--bandink` are declared once per block
   in styles.css and those declarations now match `[data-enq-jump="<key>"]` as well as
   `[data-enq-block="<key>"]` — so a chip cannot say one colour while its band says another, and a
   colour changed in one place moves both. Widening the selector was the whole mechanism; this rule
   only spends the values.
   THE SAME 16% WASH the band uses, so the chip reads as a small piece of the section it jumps to
   rather than as a differently-tinted button. `--bandink` (not `--area`) for the text, because eight
   of the thirteen needed stepping toward black to clear 4.5:1 on that wash — a chip on `--area`
   would be the unreadable version of the same colour on exactly those eight.
   AND 4.5 IS THE RIGHT FLOOR HERE TOO: the chip label is ~12px, which WCAG counts as normal text,
   so it needs what the 15px bold heading needs. Measured in both themes (test/bands.test.mjs). */
.enq-jbtn{border-color:color-mix(in srgb,var(--area,var(--blue)) 34%,transparent);
  background:color-mix(in srgb,var(--area,var(--blue)) 16%,var(--panel));
  color:var(--bandink,var(--area,var(--ink)))}
/* Hover and focus deepen the same colour rather than swapping to the generic button blue, which
   would make a chip change identity under the cursor. */
.enq-jbtn:hover{background:color-mix(in srgb,var(--area,var(--blue)) 26%,var(--panel));
  border-color:color-mix(in srgb,var(--area,var(--blue)) 52%,transparent)}
.enq-jbtn:focus-visible{outline:2px solid var(--area,var(--blue));outline-offset:1px}
/* The count rides IN the button, so "Quotes 2" answers the question without the jump.
   0247 — and it follows the chip's colour, not the one global blue: a blue pill on an amber chip
   read as a second, unrelated badge. */
.enq-jn{display:inline-block;min-width:16px;padding:0 4px;border-radius:999px;font-weight:700;
  font-size:11px;background:color-mix(in srgb,var(--area,var(--blue)) 22%,transparent);
  color:var(--bandink,var(--area,var(--blue)))}
/* A block is a panel with a name and a scroll target. `scroll-margin-top` is what stops the sticky
   bar landing on top of the heading somebody just jumped to — without it the first line of every
   block is hidden behind the bar, which reads as a broken link. */
.enq-block{scroll-margin-top:52px}
/* ---- THE SLIM BAR THAT STAYS (v3.1106.0, 29 September 2026) ----------------------------------
   Darren: *"they should be sticky so that as we scroll down, they stay there … you can keep that row
   so we know which company we're looking at, always."*
   The `.enq-jump` rule above has always said sticky, and it never could be: v3.522.0 put the chips
   in band 1's right-hand column, and sticky only sticks inside its parent. So the record now opens
   with a ZERO-HEIGHT sticky wrapper as its own first child — its range is the whole record — and
   the bar is drawn absolutely inside it. Zero height is what stops the page jumping down by the
   bar's height the moment it appears. `margin-bottom:-12px` gives back the record's flex gap, which
   a zero-height item would otherwise still take. Hidden until `enqStickSync` (app.js) sees the full
   header has gone; `visibility` rather than `display` so it can still be measured while hidden.
   Every colour is a token (--panel, --line, --muted), so dark mode needs no second block. */
.enq-rec>.enq-stuck{position:sticky;top:0;z-index:7;height:0;margin:0 0 -12px}
.enq-stuck-bar{position:absolute;top:0;left:0;right:0;display:flex;align-items:center;
  justify-content:space-between;flex-wrap:wrap;gap:6px 14px;padding:6px 2px 7px;
  background:var(--panel);border-bottom:1px solid var(--line);
  box-shadow:0 8px 14px -12px rgba(35,31,32,.45);
  visibility:hidden;opacity:0;transform:translateY(-6px);
  transition:opacity .15s ease,transform .15s ease,visibility 0s linear .15s}
.enq-rec.enq-is-stuck .enq-stuck-bar{visibility:visible;opacity:1;transform:none;
  transition:opacity .15s ease,transform .15s ease}
.enq-stuck-id{display:flex;align-items:center;gap:6px;min-width:0;flex:0 1 auto;font-size:14px}
.enq-stuck-co{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:32ch}
.enq-stuck-sep{color:var(--muted)}
.enq-stuck-who{color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  max-width:22ch}
/* The copy of the chips drops the big bar's own sticky, border and padding — the slim bar is the
   bar now — and sits hard right, as the real ones do in band 1. */
.enq-stuck .enq-jump{position:static;border:0;padding:0;margin:0;background:none;
  flex:1 1 auto;justify-content:flex-end;gap:4px}
/* A PHONE: the name on its own line, and the chips ONE row that scrolls sideways. Thirteen chips
   wrapped at 375px is four rows, which would be a bar a third of the screen tall. */
@media (max-width:700px){
  .enq-stuck-bar{flex-direction:column;align-items:stretch;gap:4px}
  .enq-stuck-co{max-width:62vw}
  .enq-stuck .enq-jump{flex-wrap:nowrap;overflow-x:auto;justify-content:flex-start;
    scrollbar-width:none;overscroll-behavior-x:contain}
  .enq-stuck .enq-jump::-webkit-scrollbar{display:none}
}
@media (prefers-reduced-motion:reduce){.enq-stuck-bar,.enq-rec.enq-is-stuck .enq-stuck-bar{transition:none}}
/* LAND JUST UNDER THE BAR. `--enq-stuck-h` is the bar's measured height, written by enqStickSync
   before every jump, so two rows of chips on a narrow window get two rows' worth of margin. Every
   jump target on the record has an `enqb-` id (enqblocks.js `blockId`), so one rule covers the
   blocks, the bare stage row and the files / history anchors alike. */
.enq-rec [id^="enqb-"]{scroll-margin-top:calc(var(--enq-stuck-h,52px) + 10px)}
/* ---- THE CUSTOMER RECORD'S SLIM BAR (v3.1107.0, 29 September 2026) --------------------------
   Darren, asked whether the customer record should get the enquiry's slim company bar: *"Yes"*. The
   same construction as `.enq-stuck` above: a zero-height sticky wrapper, first child of the record's
   main column, with the bar drawn absolutely inside it, hidden until `dirStickSync` (app.js) sees the
   at-a-glance summary has gone. One z-index above the record's own sticky chips (`.dir-secs`, z 3), so
   it covers them rather than stacking a second bar under them. Tokens only, so dark mode needs no
   second block. */
.dir-main>.dir-stuck{position:sticky;top:0;z-index:4;height:0;margin:0}
.dir-stuck-bar{position:absolute;top:0;left:0;right:0;display:flex;align-items:center;
  justify-content:space-between;flex-wrap:wrap;gap:6px 14px;padding:6px 2px 7px;
  background:var(--panel);border-bottom:1px solid var(--line);
  box-shadow:0 8px 14px -12px rgba(35,31,32,.45);
  visibility:hidden;opacity:0;transform:translateY(-6px);
  transition:opacity .15s ease,transform .15s ease,visibility 0s linear .15s}
.dir-main.dir-is-stuck .dir-stuck-bar{visibility:visible;opacity:1;transform:none;
  transition:opacity .15s ease,transform .15s ease}
.dir-stuck-id{display:flex;align-items:center;gap:6px;min-width:0;flex:0 1 auto;font-size:14px;color:var(--ink)}
.dir-stuck-co{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:32ch}
.dir-stuck-sep{color:var(--muted)}
.dir-stuck-who{color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:22ch}
.dir-stuck .dir-stuck-chips{margin:0;flex:1 1 auto;justify-content:flex-end;gap:4px}
@media (max-width:700px){
  .dir-stuck-bar{flex-direction:column;align-items:stretch;gap:4px}
  .dir-stuck-co{max-width:62vw}
  .dir-stuck .dir-stuck-chips{flex-wrap:nowrap;overflow-x:auto;justify-content:flex-start;
    scrollbar-width:none;overscroll-behavior-x:contain}
  .dir-stuck .dir-stuck-chips::-webkit-scrollbar{display:none}
}
@media (prefers-reduced-motion:reduce){.dir-stuck-bar,.dir-main.dir-is-stuck .dir-stuck-bar{transition:none}}
/* A count tile's jump lands its section just under the bar: `--dir-stuck-h` is the bar's measured
   height, written by dirStickSync before the jump. */
.dir-main .dir-sec{scroll-margin-top:calc(var(--dir-stuck-h,48px) + 10px)}
@media print{.dir-stuck{display:none}}
.enq-bn{font-weight:600}
/* ---- ONE ROW PER BOOKING, AND THE OFFERED DATES ON IT ------------------------------------------
   Darren: *"One row per booking in its own block."* A row, not a paragraph: the four facts about a
   booking (what, when, what was offered, what to do) each get a column on a wide screen and stack
   on a narrow one. `minmax(0,…)` on every column, or a long consultant name pushes the actions off
   the end instead of wrapping. */
.enq-appts{list-style:none;margin:6px 0 0;padding:0}
.enq-appt{display:grid;gap:2px 14px;padding:8px 0;border-top:1px solid var(--line);
  grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) minmax(0,1.6fr) auto;align-items:baseline}
.enq-appt:first-child{border-top:0}
@media(max-width:900px){ .enq-appt{grid-template-columns:1fr} }
.enq-appt-h{min-width:0}
.enq-appt-when{font-weight:600}
/* THE ONE THAT WAS INVISIBLE. A booking with dates offered and none answered is the state somebody
   is chasing, so it is the quietest thing on the row and never absent. */
.enq-appt-off{font-size:12.5px;color:var(--muted);line-height:1.45}
.enq-appt-st{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em}
.enq-appt-do{white-space:nowrap}
/* The four words a session can be, each its own colour, so a row of them reads at a glance.
   Tentative is the one that needs somebody: orange, like everything else that is waiting. */
.enq-appt-tentative{color:var(--orange);border-color:color-mix(in srgb,var(--orange) 45%,transparent);
  background:color-mix(in srgb,var(--orange) 10%,transparent)}
.enq-appt-confirmed{color:var(--blue-ink);border-color:color-mix(in srgb,var(--blue) 45%,transparent);
  background:color-mix(in srgb,var(--blue) 10%,transparent)}
.enq-appt-delivered{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 45%,transparent);
  background:color-mix(in srgb,var(--ok) 10%,transparent)}
.enq-appt-requested{color:var(--muted);border-color:var(--line)}
/* ---- WHICH KIND OF BOOKING (0194, v3.524.0) -------------------------------------------------
   Beside the state, not instead of it: they answer different questions. The state is "where is
   this up to" and the kind is "what sort of thing is it" — and the kind is the one that decides
   whether the customer has heard anything at all.

   QUIETER THAN THE STATE, deliberately: a row with two loud pills on it has none. Offline is the
   one worth a colour of its own, because it is the one whose customer is not being written to and
   somebody scanning the list for "have they been told" needs to skip those rows. */
.enq-appt-kind{font-size:11px;font-weight:600;letter-spacing:.02em}
/* v3.542.0 — who is booked and what it is for, on the header line. Quiet, because the label and
   the two pills are what the eye lands on; present, because "who" was the missing answer. */
.enq-appt-who{white-space:nowrap}
.enq-appt-for{font-style:italic}
.enq-kind-onsite{color:var(--blue-ink);border-color:color-mix(in srgb,var(--blue) 35%,transparent)}
.enq-kind-remote{color:var(--muted);border-color:var(--line)}
.enq-kind-offline{color:var(--purple,#8E44AD);
  border-color:color-mix(in srgb,var(--purple,#8E44AD) 40%,transparent);
  background:color-mix(in srgb,var(--purple,#8E44AD) 9%,transparent)}
/* Remove is destructive and it is on a row of ordinary buttons, so it is marked without being
   shouted: the danger colour on the text and the border only, and never a filled red button on a
   list — a wall of red is a wall nobody reads. */
.enq-appt-rm{color:var(--danger);border-color:color-mix(in srgb,var(--danger) 40%,transparent)}
.enq-appt-rm:hover{border-color:var(--danger);
  background:color-mix(in srgb,var(--danger) 8%,transparent)}

/* ---- THE DATES ON A BOOKING, ONE ROW EACH (v3.525.0) ---------------------------------------
   Darren: *"each individual date should show as one line here, so you can remove individual dates,
   you can confirm individual dates, etc."* Indented under their booking and quieter than it, so the
   list reads as "this booking, and its dates" rather than as six bookings. */
.enq-dates{list-style:none;margin:6px 0 0;padding:0 0 0 12px;display:flex;flex-direction:column;
  gap:2px;border-left:2px solid var(--line);grid-column:1/-1}
/* ---- THE ROW IS READ LEFT TO RIGHT, NOT SPREAD ACROSS THE PANEL (v3.526.1) ----------------
   MEASURED on a 1990px window: the date sat at x=44 and its consultant at x=1696, with the two
   buttons past 1786 — a row 1,916 pixels wide to carry four short facts, so the name belonged to no
   date in particular and the buttons were somewhere off in the margin. `margin-left:auto` on the
   name did that: on a full-width block it does not "put the name on the right", it puts it a metre
   away from the thing it names.

   A GRID with columns that fit their content, capped at a reading width. The four facts sit
   together, in one order, and every row's columns line up with the row above it — which is what
   makes three dates scannable as a set rather than as three sentences. */
/* FIXED first and third columns, not `auto`: each row is its own grid, so `auto` sizes to each
   row's own content and three rows then line up with nothing. Measured — "Fri, 2 Oct" is shorter
   than "Mon, 28 Sept" and shifted its whole row left of the two above it. */
.enq-date{display:grid;grid-template-columns:215px 64px 130px auto;
  align-items:center;gap:10px;justify-content:start;font-size:12.5px;padding:2px 0;
  max-width:760px}
@media(max-width:700px){
  /* On a phone the actions drop under the facts rather than squeezing a 150px date into 60px. */
  .enq-date{grid-template-columns:1fr auto;max-width:none}
  .enq-date-do{grid-column:1/-1;justify-content:flex-start}
}
.enq-date-when{font-weight:600;font-variant-numeric:tabular-nums}
.enq-date-st{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.03em}
.enq-date-held .enq-date-st{color:var(--orange);
  border-color:color-mix(in srgb,var(--orange) 45%,transparent)}
.enq-date-confirmed .enq-date-st{color:var(--blue-ink);
  border-color:color-mix(in srgb,var(--blue) 45%,transparent)}
.enq-date-delivered .enq-date-st{color:var(--ok);
  border-color:color-mix(in srgb,var(--ok) 45%,transparent)}
/* A date we offered and let go: kept on the list, because "we gave them a choice and it ran out" is
   why a customer might say they were never offered one — but quieter than the live ones. */
.enq-date-released{opacity:.65}
/* NOT `margin-left:auto` — see the note above. The name sits beside the state it belongs to. */
.enq-date-who{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.enq-date-do{display:flex;gap:6px;flex-wrap:wrap}
.enq-date-rm{color:var(--danger);border-color:color-mix(in srgb,var(--danger) 40%,transparent)}
.enq-date-rm:hover{border-color:var(--danger);
  background:color-mix(in srgb,var(--danger) 8%,transparent)}

/* ---- A QUOTE ROW, SPREAD OUT LIKE A BOOKING ROW (v3.525.0) ---------------------------------
   *"I think quotes should be more like the way the bookings are spread out a bit, with more
   information, dates, status etc."* It IS the booking row (`.enq-appt`), so only the differences
   live here: the four status colours and the mark on one that has run out. */
.enq-qrow .enq-appt-when{font-variant-numeric:tabular-nums}
.enq-q-when{white-space:nowrap}
.enq-q-draft{color:var(--muted);border-color:var(--line)}
.enq-q-sent{color:var(--blue-ink);border-color:color-mix(in srgb,var(--blue) 45%,transparent)}
.enq-q-accepted{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 45%,transparent);
  background:color-mix(in srgb,var(--ok) 10%,transparent)}
.enq-q-declined{color:var(--danger);border-color:color-mix(in srgb,var(--danger) 40%,transparent)}
/* SENT AND RUN OUT is the one state on this list that needs somebody — the same orange edge the
   bookings list uses for a hold that has expired, so the two screens mean the same thing by it. */
.enq-qrow-lapsed{box-shadow:inset 3px 0 0 var(--warn,#b26a00)}
/* v3.1148.0 (7 October 2026) — THE QUOTE'S BUTTONS GET A LINE OF THEIR OWN. The Quotes block is half
   the record's width, and the actions column was `auto` + nowrap: three buttons ("Approve the price",
   "Approve & send", "⋯") took the width and squeezed the reference, the money and the dates to nothing,
   so they drew on top of each other. The three facts keep their columns; the actions wrap underneath. */
.enq-qrow{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) minmax(0,1.6fr)}
.enq-qrow .enq-appt-do{grid-column:1/-1;white-space:normal;margin-top:4px}
@media(max-width:900px){ .enq-qrow{grid-template-columns:1fr} }

/* ---- THE KIND, ON THE BOOKING SHEET (0194) --------------------------------------------------
   Three radios with their reasons under them rather than a select, because the reason is the
   choice: *"offline, but still booking them in"* is not a phrase somebody guesses from a word in a
   dropdown. Wraps to one per row on a narrow sheet rather than shrinking to three unreadable
   columns. */
.bs-kinds>span{display:block;font-size:12px;color:var(--muted);margin-bottom:4px}
/* ---- IT WRAPS; IT DOES NOT CLIP (0237, v3.611.0) -----------------------------------------
   Darren, 11 September 2026, dragging the splitter in: *"Dragging out this side does weird things.
   It should make the width of these wider to fit more text in a single line, not try and squeeze
   more in like it is right now. So when I squish it, it should wrap the text. There's no reason
   why it can't do that."*

   There was a reason, and it is a well-known one: a grid track declared `minmax(200px, 1fr)` will
   not go below 200px. Narrow the pane past that and the track keeps its 200 and the content is cut
   off mid-word — his screenshot shows "29 ALFRED STREET, BEAUMARIS VIC 31" and nothing after it.
   `min(200px, 100%)` is the fix: 200px while there is room for 200px, and the container's width
   when there is not, so the text reflows instead of disappearing.

   And `min-width:0` on the card, because a flex item's default `min-width:auto` refuses to shrink
   below its content — which puts the overflow back one level down even with the track fixed. */
/* ---- ONE ACROSS, FULL WIDTH (0255, v3.625.0) ----------------------------------------------
   Darren, 11 September 2026, on a screenshot of this exact control: *"Don't do this. Widen the text
   boxes, don't squeeze them. 1 item across only in that pane."*
   And the screenshot showed why it was not a preference. `auto-fit` put On-site and Remote side by
   side in a pane about 640px wide, so the CHOSEN card had roughly 280px to hold everything that
   opens up inside it — the site picker, the whole address, the who-is-nearest list, the CBD figures
   and the charge table. The result was a column of two-word lines, and the fee itself broke in half:
      Onsite fee — Up to 30      $1
      minutes from the CBD       59
   "$159" wrapping between the 1 and the 59 is a number a customer could be quoted from. Meanwhile
   the Remote card sat beside it as an empty rectangle the height of all that.
   SO IT IS ONE COLUMN, ALWAYS. The kinds are a short list of radio cards read top to bottom, and
   the chosen one is the one that has content — a layout that gives the empty option half the room
   is spending the width on the answer nobody picked. */
.bs-kindrow{display:grid;gap:8px;grid-template-columns:1fr}
.bs-kindone{display:flex;align-items:flex-start;gap:8px;border:1px solid var(--line);
  border-radius:8px;padding:8px 10px;cursor:pointer;background:var(--panel);font-size:13px;
  min-width:0}
/* The label inside the card is the flex child that actually holds the words. Without this it is the
   one that refuses to shrink, and a long address is what finds it. */
.bs-kindone>span,.bs-kindone>div{min-width:0;overflow-wrap:anywhere}
.bs-kindone.on{border-color:var(--blue);background:var(--blue-lt,rgba(0,78,254,.07))}
.bs-kindone input{margin:2px 0 0;flex:0 0 auto}
.bs-kindone b{display:block}
.bs-kindone .hint{display:block;font-size:11.5px;line-height:1.35;font-weight:400}
/* WHICH ONE IS CHOSEN, IN A WORD (v3.527.0). *"why is this one hilighted?"* — because it is the
   one selected, and a tint on its own does not say that. */
.bs-kindon{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;
  color:var(--blue-ink);vertical-align:2px}
/* THE SITE, ON THE ON-SITE OPTION. *"I think the actual site address should show here."* */
.bs-kindsite{display:block;margin-top:4px;font-size:11.5px;line-height:1.35;font-weight:400;
  padding-top:4px;border-top:1px solid var(--line)}
.bs-kindsite b{font-weight:700}
/* ---- THE DRIVE, AND WHAT IT COSTS (v3.532.0) ------------------------------------------------
   *"the travel time from CBD should come up (and a charge based on that)."* Under the address it is
   measured from, on the row that decides whether anybody drives at all. The figures are the site's
   and the money is src/onsite.js — this is only how they are laid out. */
.bs-trav{display:block;margin-top:6px;padding-top:6px;border-top:1px solid var(--line);
  font-size:11.5px;line-height:1.4;font-weight:400}
.bs-travwhy{display:block;margin-top:2px}
.bs-travlines{list-style:none;margin:4px 0 0;padding:0}
.bs-travlines li{display:flex;justify-content:space-between;gap:10px;padding:1px 0}
/* ---- THE TOTAL LINES UP WITH THE FIGURES ABOVE IT (0239, v3.612.0) ------------------------
   Darren: *"Where it says on top of session, the dollar value should be to the right side, in line
   with the other dollar values."* It was a block with the words and the money in one run, so the
   figure sat wherever the sentence happened to end. Same flex row as the lines above, so the
   figures form a column. */
.bs-travtot{display:flex;justify-content:space-between;gap:10px;margin-top:3px;padding-top:3px;
  border-top:1px dashed var(--line)}
/* ---- ONE LINE FOR THE DRIVE (0239) -------------------------------------------------------
   *"It says '41 minutes from the CBD', then it should be '22.7 km, 41 minutes from CBD'. That can
   be in one line."* `.bs-trav` is a `<small>` whose children are blocks and `<b>` inside it ran the
   full width, pushing the kilometres down. A row, and the kilometres are a quieter part of it. */
.bs-travline{display:block}
.bs-travline b{display:inline}
.bs-travkm{color:var(--muted);font-weight:400}

/* ================================================================================================
   THE EXPLANATIONS, BEHIND A BUTTON (0239, v3.612.0)
   ================================================================================================
   Darren, 11 September 2026: *"We also need to add the hide button for most of the text on the
   booking and the office area, because somebody who does this all day every day doesn't need the
   same explanation again and again."*

   ONE ROOT FLAG, so it costs nothing and applies everywhere at once — the booking sheet today and
   every office screen as they are marked up. `data-expl="off"` on <html>.

   MARKED BY HAND, NOT BY BLANKET RULE. `.expl` is put on text that says the same thing on every
   booking anybody ever makes. A `.hint` carrying a live fact — "Nobody ticked, so this is
   everybody", "no home address" — is NOT one, and a rule over `.hint` would have taken those with
   it. That curation is the work; this is three lines of stylesheet.

   HIDDEN, NOT GONE: whatever the text explained carries it in a `title` as well, so hovering still
   answers the question for somebody who has not met the screen. */
html[data-expl="off"] .expl{display:none}
/* The toggle. Says which state it is in, like the editing-mode button — a control labelled
   "Explanations" that might mean either is one nobody presses twice. */
.bs-expl{display:inline-flex;align-items:center;gap:6px}
.bs-expl.on{border-color:var(--blue);background:var(--blue-lt,rgba(0,78,254,.07));font-weight:600}

/* ---- THE KIND, AND ITS WORD, ON ONE ROW (0239) -------------------------------------------
   *"Chosen can go to the right of on site. It doesn't need to be underneath."* `b{display:block}`
   is what put it there; this is the row that takes it back. `baseline` so the small uppercase word
   sits on the same line as the label rather than floating above it. */
.bs-kindtop{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}
.bs-kindtop b{display:inline}
/* A CONSENT RULE, NOT A PRICE. Travel the customer has not accepted must not be booked, so it is
   drawn as the warning it is rather than as another grey line. */
.bs-travwarn{display:block;margin-top:4px;color:var(--warn);font-weight:600}
/* ---- INTERSTATE IS NOT A LONG DRIVE (v3.535.0) ----------------------------------------------
   *"If we select an on-site for someone who's in a different state, we probably need to flag that
   this is going to be a flight."* Above the minutes and the money, and louder than either, because
   it makes both of them meaningless. Its own colour: this is not a warning about a rule we keep, it
   is a fact about the journey. */
.bs-travflight{display:block;margin:0 0 6px;padding:6px 8px;border-radius:6px;
  background:var(--orange-soft,rgba(178,106,0,.1));border-left:3px solid var(--orange,#f07d00);
  font-weight:600;line-height:1.35}
.bs-travflight b{font-weight:800}
/* ---- WHERE, INSIDE THE OPTION IT BELONGS TO (v3.528.0) --------------------------------------
   *"'Where' should be in the area where you select the booking to be On-Site, Remote, Offline. If
   you select On-Site we should then be required to choose or create a site."*

   So the picker is a row of the on-site option itself, and it only exists while that option is
   chosen. `width:100%` on the select because the option is a flex row: left to itself the select
   sizes to its longest option name and a long site address made the row wider than its neighbours.
   The whole kind row is a <label>, so a click anywhere in it selects the radio — which would fight
   the select and the button. Hence the stop on those two; the handler in app.js does the rest. */
.bs-kindwhere{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:6px;width:100%}
.bs-kindwhere select{flex:1 1 160px;min-width:0;max-width:100%;font-size:12.5px;padding:5px 7px}
.bs-newsite{font-size:12px;font-weight:600;white-space:nowrap}
/* THE REQUIREMENT, SAID WHERE THE CHOICE IS MADE — not only in a disabled button's tooltip, which
   is a sentence nobody sees until they have already tried. */
.bs-kindneed{color:var(--warn,#b26a00);font-weight:600}
.bs-needsite{flex:1 1 100%;font-size:12px;color:var(--warn,#b26a00);font-weight:600}

/* ---- ONE DAY, EVERYBODY'S COLUMN (v3.524.0) -------------------------------------------------
   The third of *"per person, per day, per week"*. Full names on the columns rather than initials —
   there is one date and therefore room for them, which is the whole difference between this and the
   by-weekday view. */
.bs-oneday{overflow-x:auto;padding-bottom:6px}
.bs-daybighead{font-size:15px;padding:0 2px 8px;border-bottom:2px solid var(--blue);
  margin-bottom:8px}
.bs-oneday .bs-daycolhead{font-size:12px;font-weight:600;color:var(--ink)}
.bs-oneday .bs-daycols{grid-auto-columns:minmax(120px,180px)}

/* ---- THE BOOKINGS SCREEN'S TWO TABS AND ITS DIARY (v3.524.0) --------------------------------
   *"bookings and calendar should have their own menu and their own screen"* — the list answers
   which bookings need somebody, the calendar answers what is on when, and they share the screen
   because they are two questions about one thing. */
.bkg-tabs{display:flex;gap:6px;margin:2px 0 10px;flex-wrap:wrap;flex:0 0 auto}
.bkg-cal{margin-top:4px}
.bkg-calwho{margin:0 0 8px}
.bkg-calnav{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin:6px 0 10px}
/* ---- THE DIARY'S CONTROLS ARE ONE ROW (v3.556.5) -------------------------------------------
   *"Too much space is being wasted in the middle, up the top."* Three boxes that each took a line
   of their own — and, inside the first, a label, the ticks and a sentence that each took another —
   now sit in one wrapping row. The rules below are all SCOPED TO THE BAR: `.bkg-calwho` and
   `.bkg-calnav` are drawn elsewhere too (the booking sheet's own strip), and those keep their
   stacked shape, which is right in a 320px-wide column.

   AND THE CLOCK STOPS BEING PUSHED TO THE EDGE. `.bs-tz{margin-left:auto}` is what put 700px of
   nothing in the middle of his screenshot; in this bar it queues up after the week buttons like
   every other control. Its two lines (the select and the gap-in-words underneath) are what set the
   bar's height, so they are the thing allowed to be two lines. */
.bkg-calbar{display:flex;flex-wrap:wrap;align-items:center;gap:6px 16px;margin:0 0 10px}
.bkg-calbar>*{margin:0}
.bkg-calbar .bkg-calwho{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px}
.bkg-calbar .bs-crewlab{display:inline;margin:0}
.bkg-calbar .bs-ticks{margin:0}
.bkg-calbar .bs-views{margin:0}
.bkg-calbar .bs-tz{margin-left:0;align-items:flex-start}
/* A HAIRLINE BETWEEN THE GROUPS, so one long row still reads as "who / how / when / which clock"
   rather than as sixteen buttons. On the wrapped rows it is the leading edge of a group, which is
   the same job. */
.bkg-calbar .bs-views,.bkg-calbar .bkg-calnav{border-left:1px solid var(--line);padding-left:16px}
/* Said once, at the bottom, quietly: a diary that looks like the booking screen has to say it is
   not one, or the first click somebody makes is a click they expected to do something. */
.bkg-calnote{margin:10px 0 0}
/* Where the Offer button used to be on an offline booking — the space says why rather than being
   empty, which would read as a button that failed to render. */
.bs-offlinenote{max-width:320px;line-height:1.35;align-self:center}
/* Nothing booked in yet is marked on the row rather than left to the reader to notice. */
.enq-appt-open .enq-appt-when{color:var(--muted);font-weight:400}
.enq-appt-open{background:color-mix(in srgb,var(--orange) 5%,transparent)}

/* ---- MORE ACROSS, LESS DOWN (v3.494.0) ----------------------------------------------------
   Darren: *"We need to have more things listed horizontally to save vertical space. Yes, it still
   has to be readable and legible and look good, but also we don't want to have to scroll to China
   to see all the data."*

   Quotes, Invoices, Sessions and Tasks are four INDEPENDENT lists, not a sequence — so they sit
   two (or three) across and the block is a third of the height. `auto-fit` with a 300px floor is
   what keeps it honest: on a phone it is one column, and no row ever gets squeezed to unreadable.
   `align-items:start` so a one-row list beside a five-row one does not stretch. */
/* v3.495.0 — `.tl-lists` / `.tl-list` lived here: the two-across grid that stopped the four lists
   stacking INSIDE the through-line panel. Each list is now its own block (public/enqblocks.js), so
   the grid has nothing to arrange and the rules are gone rather than left to rot. The reasoning
   that produced them still applies and now belongs to `.enq-block`, which the record lays out. */
/* The note that used to sit under those four lists. Its own class because it is a footnote about
   this CUSTOMER (no workspace, so no sessions or payments), not about any one block. */
.enq-nowork{margin-top:10px}
/* ---- EVERY CHIP IN ITS OWN COLOUR (v3.496.0) -----------------------------------------------
   Darren: *"I also think the chips should be colour-coded like they are when you're looking at the
   list."* On the board a status chip is `.enq-stchip` — the status's own colour at 10% behind a
   45% border. This is the same statement for the through-line's chips, so one recolour on Set-up →
   The enquiry pipeline lands on the board AND here. Before this, an untouched chip was grey and
   only the reached and current ones carried any colour at all, so a row of ten was nine greys. */
/* ---- THE TINT IS THE BASE NOW, SO THESE ONLY CARRY STATE (v3.588.0) -----------------------
   These four rules used to set the border and background for every state, and they sit 300 lines
   BELOW the epaulette block — so with equal specificity they won on order and the new "where it is
   now" fill was quietly reverted to a 8% tint with dark text. Measured: the current step came back
   `bg=color(srgb 0.486 …) fg=rgb(35,31,32)` instead of solid with white on it.
   The epaulette base already applies `--pill` at 16%, so the tinting is done. What is left is the
   two states, written with `.tl-tint` IN the selector so they cannot be undercut by it again. */
.tl-step.tl-tint .tl-mark{color:color-mix(in srgb,var(--pill,var(--muted)) 75%,var(--muted))}
.tl-step.tl-tint.tl-on{background:color-mix(in srgb,var(--pill,var(--blue)) 34%,var(--panel));
  color:var(--ink)}
.tl-step.tl-tint.tl-on .tl-lab{color:var(--ink);font-weight:700}
/* WHERE IT IS NOW WINS EVERYTHING. Three classes, last in the file, and the fill is mixed toward
   the ink colour so white text is readable however pale the operator's chosen status colour is —
   see the long note in styles.css for why a raw `--pill` background is not safe. */
.tl-step.tl-tint.tl-now{background:color-mix(in srgb,var(--pill,var(--orange)) 74%,#231F20);
  color:#fff}
.tl-step.tl-tint.tl-now .tl-lab,.tl-step.tl-tint.tl-now .tl-mark{color:#fff;font-weight:800}
button.tl-step.tl-tint.tl-now:hover{background:color-mix(in srgb,var(--pill,var(--orange)) 60%,#231F20)}
/* ---- TWO SHORT BLOCKS, ONE ROW (v3.496.0) --------------------------------------------------
   *"The quotes can have their own section. Invoices can have their own little section next to
   quotes."* A floor of 320px, so a phone gets one column and neither list is squeezed. The blocks
   inside lose their own top margin — the row owns the spacing. */
.enq-blockrow{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));
  gap:0 12px;align-items:start}
.enq-blockrow > .enq-block{min-width:0}
/* ---- THE READING ROW (0248, v3.616.0) -------------------------------------------------------
   "What they asked for" and "The story so far", side by side. Two things it needs that the
   quotes/invoices row does not:
     * `align-items:stretch` so the two panels are the SAME HEIGHT whichever has more in it — two
       reading panels of different heights beside each other read as one broken one.
     * `> *{min-width:0}` on every child, not only `.enq-block`: the wants box is `.enq-wants` and
       the brief is `#enq-aibrief`, and neither carries that class, so without this a long
       unbroken line in either would push the grid wider than the card. */
/* AN EMPTY BRIEF SLOT MUST NOT HOLD HALF THE ROW (measured: on a fresh enquiry the AI brief has
   not been written yet, `#enq-aibrief` renders '' — and an empty grid item still takes its column,
   so "What they asked for" sat in half the width with nothing beside it). Collapsed, and `auto-fit`
   then gives the survivor the full width. Same rule and same reason as `.enq-rec > :empty`. */
.enq-readrow > :empty{display:none}
.enq-readrow{align-items:stretch}
.enq-readrow > *{min-width:0}
.enq-readrow > * > .panel.sub,.enq-readrow > .enq-wants{height:100%;box-sizing:border-box}
/* A group heading inside a block: "Raised from this enquiry" / "Other quotes for this customer". */
.enq-sub{margin:8px 0 2px;font-size:12.5px;text-transform:uppercase;letter-spacing:.04em;
  color:var(--muted)}
.enq-sub:first-child{margin-top:2px}
/* ---- THE INBOX ROW SAYS WHO ELSE WAS ON IT (0183, v3.497.0) ---------------------------------
   Darren: *"in bold on the left, and then to and CC to the right of that. No need for extra rows.
   Just put it to the right."* So `.ib-who` becomes a flex row: the sender's half keeps the left and
   the To/CC group is pushed to the right of the SAME line. `min-width:0` on both halves or the
   longer one refuses to shrink and the row grows sideways instead of truncating. */
.ib-who{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.ib-from{min-width:0;flex:1 1 auto}
.ib-tocc{min-width:0;flex:0 1 auto;display:flex;gap:12px;font-size:12px;color:var(--muted);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ib-tc{overflow:hidden;text-overflow:ellipsis}
.ib-tc b{font-weight:700;color:var(--muted);text-transform:uppercase;font-size:10.5px;
  letter-spacing:.04em;margin-right:2px}
/* On a narrow screen the right-hand group takes its own line rather than squeezing the sender to
   nothing — which is not "an extra row" on a phone, it is the only way both facts fit at all. */
@media(max-width:700px){ .ib-who{flex-wrap:wrap} .ib-tocc{flex-basis:100%} }
/* ---- A CONSULTANT'S REGULAR WEEK, THREE FIELDS ACROSS (0184, v3.500.0) ---------------------
   Darren: *"We will need to set regular start and finish times for each consultant."* Three short
   fields — a start, a finish and a day list — stacked would be three rows of a sheet that already
   scrolls. Across, they read as one fact about one person. `minmax(0,…)` or the day list refuses to
   shrink and pushes the finish off the row. */
.edge-week{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,160px),1fr));
  gap:0 12px;align-items:end}
.edge-week > .field{min-width:0}
/* ---- AND THE PROSE USES THE ROOM (v3.494.0) -----------------------------------------------
   Darren: *"the wrapping of the text doesn't go all the way to the end. We need to be more
   efficient with space and use every bit of it on the screen."*

   MEASURED: it was `.panel > p{max-width:var(--measure)}` — 82ch, about 940px — wrapping the AI
   brief two thirds of the way across a 1300px record. That 82ch rule is right for an ARTICLE and
   wrong here: a record is a dense screen somebody scans, not a page they read, and the brief is
   five lines of summary rather than five paragraphs. So the measure is lifted inside the record
   only, and the reading rule stays for every screen that is actually prose. */
#enq-detail .panel > p,#enq-detail > p,#enq-detail .dir-ai-sum,#enq-detail .hint{max-width:none}
/* The owner caption was a two-line block above a select on a one-row grid, which cost the row its
   whole height twice over. Inline beside the label, where a caption belongs. */
.enq-f > .hint{display:inline}
/* ---- COLOURED, CUSTOM ENQUIRY STATUSES (0148, v3.421.0) ---- */
.enq-pill{display:inline-block;font-weight:600;font-size:12.5px;padding:2px 9px;border-radius:999px;
  color:var(--pill,#6B7280);background:color-mix(in srgb,var(--pill,#6B7280) 16%,transparent);
  border:1px solid color-mix(in srgb,var(--pill,#6B7280) 35%,transparent);
  white-space:nowrap}
/* v3.433.0 — status chips on the board head: the status's own colour, solid when it is the filter. */
.enq-stchips{display:inline-flex;flex-wrap:wrap;gap:6px;margin-left:10px;vertical-align:middle}
.enq-stchip{border-radius:999px;font-weight:700;color:var(--pill,#6B7280);
  border:1px solid color-mix(in srgb,var(--pill,#6B7280) 45%,transparent);
  background:color-mix(in srgb,var(--pill,#6B7280) 10%,transparent)}
.enq-stchip.on{color:#fff;background:var(--pill,#6B7280);border-color:var(--pill,#6B7280)}
.es-row{display:flex;align-items:center;gap:10px;padding:8px 0;border-top:1px solid var(--line)}
.es-row:first-child{border-top:0}
.es-row input[type=color]{width:34px;height:30px;padding:0;border:1px solid var(--line);
  border-radius:6px;background:none;cursor:pointer;flex:0 0 auto}
.es-row .es-label{flex:1 1 auto;min-width:120px}
.es-row .es-kind{flex:0 0 auto;font-size:12px;color:var(--muted)}
.es-move{display:flex;gap:2px;flex:0 0 auto}
.es-move button,.es-del{flex:0 0 auto}
.es-terminal{opacity:.85}
.notif-log{margin-top:12px}
.notif-log summary{cursor:pointer;padding:4px 0}
.notif-log summary:hover{color:var(--accent-ink)}

/* ---- THE SITE FORM'S SECTION HEADINGS (v3.317.0) ---------------------------------------------
   The form asks three separate questions — where the place is, how far it is, and what a consultant
   needs on arrival — and running them together as one wall of fields is what made the old one hard
   to fill in. Small headings rather than separate panels: it is still one form and one Save. */
.dst-h{margin:16px 0 2px;font-size:13px;font-weight:800;letter-spacing:.02em;color:var(--ink)}
.dst-h+.hint{margin:0 0 4px}

/* ---- "NOT RUN" IN THE PLACE A NUMBER GOES (v3.319.0) -----------------------------------------
   Darren: "So do we just say 'Not run' or something." Yes. A dash on these tiles means "we looked
   and could not read it"; a nought would mean "nobody is short". Nobody-has-looked is a third state
   and it belongs where the number is, not in a footnote — this is the one tile where guessing wrong
   costs a customer money. Smaller than a numeral because it is words, and quieter than the alarm
   tones because nothing is known to be wrong yet. */
.off-unrun .off-word{font-size:20px;line-height:1.15;letter-spacing:-.01em}
.off-unrun{border-style:dashed}

/* ---- RICH TEXT (v3.322.0) ---------------------------------------------------------------------
   Darren: "we should build in rich text format so that we can select and enter in all areas where
   there should be free type text."

   The box is styled to look like the other inputs on the form, because a contenteditable that looks
   like a page rather than a field is one people do not realise they can type in. The toolbar is
   quiet: it is a set of tools, not the subject of the panel. */
.rt{display:flex;flex-direction:column;gap:4px;margin:12px 0 0;font-weight:600;font-size:13.5px}
.rt-label{font-weight:600}
.rt .hint{font-weight:400}
.rt-bar{display:flex;flex-wrap:wrap;gap:4px;padding:4px;border:1px solid var(--line);
  border-bottom:0;border-radius:6px 6px 0 0;background:var(--panel-2)}
.rt-b{font:inherit;font-size:12.5px;font-weight:600;min-width:32px;min-height:28px;padding:2px 8px;
  border:1px solid transparent;border-radius:4px;background:transparent;color:var(--ink);
  cursor:pointer;line-height:1}
.rt-b:hover{background:var(--panel);border-color:var(--line)}
.rt-b:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
/* The editable area. `overflow-wrap` because a pasted URL with no spaces in it would otherwise push
   the whole form sideways. */
.rt-box{font:inherit;font-weight:400;line-height:1.55;padding:10px 12px;
  border:1px solid var(--line);border-radius:0 0 6px 6px;background:var(--panel);color:var(--ink);
  overflow-wrap:anywhere;max-height:420px;overflow-y:auto}
.rt-box:focus{outline:2px solid var(--accent);outline-offset:-2px}
/* What the allowed grammar looks like inside it — and, because the same tags are rendered back on a
   read-only screen, `.rt-read` gets the identical treatment. A note that looks one way while being
   typed and another way when read is a note somebody formats twice. */
.rt-box p,.rt-read p{margin:0 0 8px}
.rt-box p:last-child,.rt-read p:last-child{margin-bottom:0}
.rt-box ul,.rt-box ol,.rt-read ul,.rt-read ol{margin:0 0 8px;padding-left:22px}
.rt-box li,.rt-read li{margin:0 0 3px}
.rt-box h3,.rt-read h3{font-size:15px;margin:10px 0 4px}
.rt-box h4,.rt-read h4{font-size:13.5px;margin:10px 0 4px}
.rt-box blockquote,.rt-read blockquote{margin:0 0 8px;padding-left:10px;
  border-left:3px solid var(--line);color:var(--muted)}
.rt-box a,.rt-read a{color:var(--accent-ink)}
.rt-read{font-weight:400;line-height:1.55}

/* ================================================================================================
   THE WHAT-IF PLANNER'S TABS  (v3.345.0)
   ================================================================================================
   Darren, 2 Sep 2026: *"it's kind of like they are different reports ... Can we break it into
   segments somehow? Maybe we have the same top part, and then we can click a button or tab to
   flick between the different reports that use that data."*

   Exactly that: the levers and the three headline cards stay put, and the four answers sit behind
   these. Styled as a strip of buttons rather than as browser-chrome tabs so it is obvious they
   belong to the panel below them and not to the page. */
/* ---- TWO HALVES, THEN FOUR ANSWERS (v3.377.0) ------------------------------------------------
   Darren: *"weren't we going to have different tabs or different buttons so that it could be split,
   so it wasn't scroll down, scroll down, scroll down, scroll down?"*

   The inner `.wif-tabs` strip (v3.345.0) split the four ANSWERS and left the inputs stacked — and
   then the rear-view mirror was added below all of it, using none of the levers above it. So there
   are two strips now and they must not look like the same control: the top one is a HALF of the
   screen, chunky and clearly primary; the inner one is a report within a half, quiet and filed. */
.wif-top{display:flex;gap:8px;flex-wrap:wrap;margin:6px 0 18px}
.wif-toptab{display:flex;flex-direction:column;align-items:flex-start;gap:2px;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  color:var(--muted);font:inherit;font-weight:800;font-size:15px;cursor:pointer;
  padding:11px 16px;text-align:left;line-height:1.25;flex:1 1 220px}
/* ---- v3.462.0: THESE HOVERS DID NOTHING --------------------------------------------------
   They were written as `color:var(--text)`, and `--text` is not a variable either stylesheet
   declares — so the declaration resolved to nothing and the colour was simply inherited. A hover
   that sets the colour it already had is a hover with no effect, and `.wif-tab.on` below had the
   same fault, which is why the SELECTED tab never looked selected by colour. `--ink` is the token
   that was meant, and test/uiaudit.test.mjs now fails on any var() with no declaration. */
.wif-toptab:hover{color:var(--ink)}
.wif-toptab.on{color:#fff;background:var(--blue);border-color:var(--blue)}
.wif-topsub{font-weight:600;font-size:11.5px;letter-spacing:.01em;opacity:.78;text-transform:none}
.wif-tabs{display:flex;gap:6px;flex-wrap:wrap;margin:16px 0 4px;border-bottom:1px solid var(--line);padding-bottom:0}
.wif-tab{background:none;border:1px solid transparent;border-bottom:0;color:var(--muted);
  font:inherit;font-weight:600;cursor:pointer;padding:9px 14px;border-radius:8px 8px 0 0;
  position:relative;top:1px}
.wif-tab:hover{color:var(--ink)}
.wif-tab.on{color:var(--ink);border-color:var(--line);background:var(--panel);
  /* Sits ON the rule, hiding the segment under it — which is what makes it read as the front tab
     rather than as a button that happens to be darker. */
  box-shadow:0 1px 0 0 var(--panel)}
.wif-pane{padding-top:6px}

/* THE ANSWER, ABOVE THE CHART. A pane opens with a sentence, because a chart answers a question
   only if you already knew what it was. Bordered on the leading edge rather than boxed: it is the
   first thing in the pane and a full box would put a second frame inside the panel. */
.wif-answer{border-left:3px solid var(--orange);padding:2px 0 2px 12px;margin:8px 0 14px}
.wif-headline{margin:0 0 4px;font-size:16px;line-height:1.5}
.wif-answer .hint{margin:0}

/* ---- A VEHICLE GROUP'S FOUR COST BOXES (v3.354.0) -------------------------------------------
   Darren: "I want to spread the vehicle expenses out into bite size pieces." A 🚐-flagged asset
   group swaps one Cost-to-replace box for four narrower ones plus a derived total, which is four
   more columns in the same table. They are narrow on purpose — these are four-and-five-figure
   numbers, not sentences — and the derived cell is styled as a figure rather than a field so it
   reads as an answer instead of an empty box somebody forgot to fill in. */
/* ---- AND IT SCROLLS RATHER THAN CRUSHES (measured in Chromium, 3 Sep 2026) -------------------
   The first cut set percentage widths on the four new columns and left the Item column at `width:
   auto`. Under `table-layout:fixed` the declared percentages then added up to more than 100%, the
   browser scaled every column down to fit, and the Item column — the only `auto` one — collapsed to
   ZERO. "Hiace Van" was invisible on screen. Caught by screenshotting it, not by reading it.
   So the vehicle table is given a real minimum and put in its own horizontal scroller: ten columns
   of four-figure numbers do not fit a phone, and the v3.352.0 rule for that is that wide content
   scrolls inside its own container rather than the page. */
/* ---- A COLUMN HEADING WRAPS RATHER THAN LOSING ITS "?" (v3.449.0) ----------------------------
   Darren, 7 Sep 2026: *"Some ? marks are cut off."* Measured: the "Replace Every (Years)" heading
   plus its help dot needs 159px and its column is 147px, so the dot hung 12px past the cell and the
   scroll container sliced it in half. The global `th,td{white-space:nowrap}` is right for columns of
   figures and wrong for a five-word heading, so these headings — and only these — are allowed to
   wrap. `vertical-align:bottom` keeps a two-line heading sitting on the same baseline as the
   one-line ones beside it, and the dot is glued to the last word so a wrap can never orphan it. */
.asset-group .ag-items th{white-space:normal;vertical-align:bottom;line-height:1.3}
.asset-group .ag-items th .help{margin-left:3px}
.ag-scroll{overflow-x:auto;max-width:100%}
/* The PLAIN asset table gets a floor too. It was clipping "Replace Every (Years)" to "Replace E" at
   760px — not new, and in the same table family, so it is fixed in the same pass. Below the floor
   it scrolls, which beats a heading nobody can read. */
.ag-items{min-width:720px}
/* Eleven columns since v3.377.0 — the Interest % box joined the four money ones. See the note at
   the top of this block: under `table-layout:fixed` the declared widths have to leave room for the
   `auto` Item column or it collapses to nothing, which is exactly what happened last time a column
   was added here. The rate box is narrower than the money boxes because it holds "8.5", not
   "60,000". */
/* v3.449.0 — trimmed from 1180px so the whole vehicle table FITS a 1440px laptop (container 1152)
   instead of scrolling inside its group. Measured, not guessed: at 1180 it wanted 28px more than a
   1440 screen has. Below ~1360 twelve columns of four-figure numbers genuinely do not fit and the
   scroller below is still the right answer — see the note above. */
table.editable.is-veh{min-width:1096px}
table.editable.is-veh th.c-part,
table.editable.is-veh td[data-key="purchase"],
table.editable.is-veh td[data-key="fitout"],
table.editable.is-veh td[data-key="interest"],
table.editable.is-veh td[data-key="tradeIn"]{width:100px}
table.editable.is-veh th.c-interestPct,
table.editable.is-veh td[data-key="interestPct"]{width:78px}
/* The rate feeds the box to its right, and a bracket in the heads is the only place that can say so
   without a sentence under the table nobody reads. */
table.editable.is-veh th.c-interestPct::after{content:' →';color:var(--muted);font-weight:800}
table.editable.is-veh th.c-label,
table.editable.is-veh td[data-key="label"]{width:auto;min-width:140px}
table.editable.is-veh th.c-cost,
table.editable.is-veh td[data-key="cost"]{width:108px}
table.editable.is-veh td.ag-cost-derived{
  font-weight:800;white-space:nowrap;padding:8px 10px;color:var(--ink);
}
/* Trade-in is the one that comes OFF, and the heading is the only place that can say so at a
   glance. A minus sign in the column head beats a sentence underneath the table nobody reads. */
table.editable.is-veh th.c-tradeIn::after{content:' −';color:var(--muted);font-weight:800}
.ag-veh-note{margin:6px 0 0;font-size:12px}

/* ---- TYPE THE PRICE, OR DRAG IT (v3.354.0) ---------------------------------------------------
   Darren: "The slider is really hard to get the exact price we want because it jumps." The step was
   already $1; the problem is that 360 dollars share ~260 pixels. The box is the fix — the slider is
   for exploring, the box is for landing on $166. Sized to the digits it holds so it reads as a
   figure rather than a form field, and the $ and /hr sit inside the border with it. */
.wif-num{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--line);
  border-radius:var(--r);background:var(--panel);padding:0 8px;height:var(--field-h);
  margin:0 4px;vertical-align:middle}
.wif-num input{width:5.5ch;border:0;background:transparent;padding:0;height:auto;
  font-size:var(--field-size);font-weight:800;text-align:right;color:var(--ink);
  font-family:inherit;-moz-appearance:textfield}
.wif-num input:focus{outline:none}
.wif-num:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 22%,transparent)}
.wif-pre,.wif-suf{color:var(--muted);font-size:12px;font-weight:700}
/* The spinners are 14px of arrow nobody can hit and they steal a character of width. Arrow keys
   still step the value, which is the accessible path and the one that actually gets used. */
.wif-num input::-webkit-outer-spin-button,
.wif-num input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}

/* ---- WHAT UNTICKING A COLUMN ACTUALLY DOES ---------------------------------------------------
   The two `.col-eff` badges ("affects the rate" / "screen only") were REMOVED in v3.376.0 along
   with the distinction they described. Darren, 4 Sep 2026: *"Pay rate: dollars per hour is not just
   screen only... I don't think you need to show what is screen only and what affects the rate."*
   Pay Rate had been on the "screen only" side of that split, so the menu printed a reassuring badge
   beside the most rate-changing number in the app. One rule now — off means out of the calculation
   — so there is nothing to badge, and the rule is stated once in the menu head instead. */
.col-menu-item{display:flex;align-items:center;gap:6px}
.col-menu-item .cm-l{flex:1 1 auto}
.col-menu-key{padding:8px 12px;font-size:11px;line-height:1.5;color:var(--muted);
  border-bottom:1px solid var(--line)}

/* ---- TWO RATES SIDE BY SIDE (v3.354.0) -------------------------------------------------------
   Darren asked for the Company Average Cost Per Hour next to the overhead rate. They are two
   answers to two questions — what to push to Simpro, and what an hour actually costs you — so they
   get equal weight rather than one being a footnote of the other. They wrap on a narrow screen
   instead of shrinking, because a dollar figure squeezed to eight pixels is not a dollar figure. */
.hero-rates{display:flex;flex-wrap:wrap;gap:14px 40px;align-items:flex-start}
.hero-one{min-width:220px}
/* ---- THE SECOND FIGURE WAS BLUE ON BLUE (v3.380.0) -------------------------------------------
   Darren, 4 Sep 2026: *"The company average cost per hour is good, but it's very difficult to see."*

   This rule said `color: var(--accent2, var(--accent))`. `--accent2` IS NOT DEFINED ANYWHERE in
   this repo — this was its only mention — so it fell through to `--accent`, which is `--blue`
   (#004EFE), which is the light end of the banner's own gradient. The number was painted in the
   background colour.

   The tell that it was this rule and not an opacity: the print stylesheets override `.hero-rate`
   with `!important`, so the figure has always PRINTED correctly and only ever looked faint on
   screen.

   Both figures are white on the blue banner now — they are the same kind of fact, they sit side by
   side, and the only reason to tint one was a variable that never existed. The distinction between
   them is carried by their labels, which is where it belongs. */
.hero-avg .hero-rate{color:#fff}
@media(max-width:640px){ .hero-rates{gap:12px 0} .hero-one{min-width:100%} }

/* ---- IMPORT, OFFERED WHERE THE TYPING WOULD START (v3.354.0) ---------------------------------
   Darren: "The easy start should take have an import option for employees first before that section
   and then expenses second when we're at that section." A nudge rather than a step: it must be easy
   to read and easier to walk past, because most people on the Easy Start are typing, not importing.
   Quieter than the accent-bordered nudges around it for exactly that reason. */
.nudge.wiz-import{border-left:4px solid var(--accent);background:color-mix(in srgb,var(--accent) 7%,transparent)}
.nudge.wiz-import .nt{margin-bottom:2px}

/* ---- THE SIGNATURE PAD IS PAPER, IN BOTH THEMES (v3.355.0) -----------------------------------
   Darren: "When signing in dark mode you can't see the writing very well." The pad used to take
   `var(--panel)` — #17181D in dark — against ink of #12233D. Measured: 1.13:1. Invisible.
   It is white here rather than the ink being lightened, because the signature is composited onto
   white for the PDF (see padSetup in public/app.js): light ink would have looked right on screen
   and arrived on the signed document as nothing. Slightly off-white so a 600x160 white rectangle
   does not glare on a dark page, and a solid border so its edge is still findable. */
.sg-pad{
  background:#FDFDFB;
  border:1px solid var(--grey-mid);
  border-radius:var(--r);
  box-shadow:0 1px 0 rgba(0,0,0,.04);
}


/* ---- A TICKED ROW LOOKS TICKED, AND A CHANGED CELL FLASHES (v3.377.0) ------------------------
   Darren, 4 Sep 2026: *"when you tick each row, the row should be slightly highlighted in a
   different colour, or maybe have a box around the whole row so you can see what's highlighted.
   Also, when you select something and click Apply Selected, it should flash or blink or something
   when the data is changed."*

   Both are about the same failure: Your Team is wide enough to scroll sideways, so a 13px checkbox
   at the far left and a cell change forty columns to the right are invisible to each other. Tick
   three people, scroll right to the column you want, and which three is off screen — then press
   Apply and nothing appears to happen. */
#emp-grid tbody tr.row-picked > td{background:color-mix(in srgb,var(--blue) 8%,transparent)}
/* The left edge is the part that survives horizontal scrolling, because the name column is sticky. */
#emp-grid tbody tr.row-picked > td:first-child{box-shadow:inset 3px 0 0 var(--blue)}
#emp-grid tbody tr.row-picked > td.col-name{background:color-mix(in srgb,var(--blue) 12%,var(--panel))}

/* TWO PULSES, THEN GONE. One is easy to miss when the eye is somewhere else on a wide table;
   holding it longer starts reading as an error state. Colour AND weight, so it does not depend on
   being able to distinguish two pale greens. */
@keyframes cellFlash{
  0%   {background:color-mix(in srgb,var(--ok,#1f8a4c) 34%,transparent)}
  45%  {background:transparent}
  70%  {background:color-mix(in srgb,var(--ok,#1f8a4c) 26%,transparent)}
  100% {background:transparent}
}
.cell-flash{animation:cellFlash 1.3s ease-out 1}
.cell-flash input,.cell-flash select{font-weight:800}
/* A flash is feedback, not information — somebody who has asked for less motion has still been
   told what happened by the status line under the bar, which says how many changed. */
@media (prefers-reduced-motion:reduce){
  .cell-flash{animation:none;background:color-mix(in srgb,var(--ok,#1f8a4c) 20%,transparent)}
}

/* A derived interest figure shown as a placeholder — grey and italic, so it can never be mistaken
   for something somebody typed. The moment a figure is typed the placeholder is not rendered at
   all, which is the honest signal: what you see is what is counted. (v3.377.0) */
#assets-groups input.from-rate::placeholder{color:var(--muted);font-style:italic;opacity:.9}
#assets-groups input.from-rate{background:color-mix(in srgb,var(--blue) 4%,transparent)}

/* ---------- the office board — seven tiles, always the same seven (v3.384.0) ---------- */
.off-board-wrap{padding:12px 14px}
.off-board{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(136px,1fr))}
/* v3.443.0 — seven tiles: one row on a desktop, 4 + 3 on a laptop, pairs on a phone (the odd one
   spans), never six and an orphan.
   ---- EIGHT TILES NOW, SO FOUR AND FOUR (v3.460.0) -------------------------------------------
   "Waiting on a decision" joined the seven (Darren: *"make sure the office dashboard contains inbox
   stats as well and clicking on them opens the full inbox"*). Seven across would have wrapped the
   eighth alone under a full row — the exact shape v3.443.0 exists to prevent — and eight across
   leaves each tile too narrow for its own label. Two balanced rows of four. */
/* ---- THE TRACK COUNT FOLLOWS THE TILE COUNT (v3.618.0) ----------------------------------
   FOUR, until 0214 added a ninth tile ("Quotes to approve") and four-across left it alone under a
   full row — which is the exact shape test/polish443.test.mjs exists to prevent, and it caught it.
   THREE, measured in Chromium at 1440 / 1280 / 900: 3-3-3 at every desktop width, and the board is
   SHORTER than the four-column version was (296px vs 308px at 1440) because a 463px tile stops
   wrapping its blurb onto a second line. The phone breakpoint is untouched — two across, with the
   existing last-child rule spanning the odd one out. */
/* 0254 (v3.625.0) — FIVE, and two labelled bands of five rather than one run of ten. Darren:
   *"squish the stats at the top so they take up less horizontal room to fit on 2 rows… try to group
   similar ones."* Three-across was right for nine tiles (v3.618.0) and wrong for ten. A narrower
   tile holds the same three facts because the blurb is the only part that grows, and it wraps. */
@media(min-width:1200px){.off-board{grid-template-columns:repeat(5,minmax(0,1fr))}}
@media(max-width:1199px) and (min-width:700px){.off-board{grid-template-columns:repeat(5,minmax(0,1fr))}}
@media(max-width:699px){.off-board{grid-template-columns:repeat(2,minmax(0,1fr))}
  .off-board>.off-board-tile:last-child:nth-child(odd){grid-column:1/-1}}
.off-board-tile{min-height:92px;padding:10px 12px}
/* ================================================================================================
   ONE BAND OF STATS, WITH THE MONEY IN IT (v3.460.0)

   Darren, 7 Sep 2026: *"the main stats, including money, should all be at the top."* The seven doors
   and the two money figures were two panels with four other panels between them, so "how much is
   owed" was a scroll away on the screen whose reader is asked it before lunch.

   ONE PANEL, RULED not boxed: a second border and a second `<h2>` between two halves of one answer
   would make it two things again, which is what it already was. */
.off-money{margin-top:12px;padding-top:12px;border-top:1px solid var(--line)}
.off-money>h3{margin:0 0 6px;font-size:14px;color:var(--muted);text-transform:none}
/* The failed reads, once, at the bottom — these used to live inside "Waiting on us", which is gone.
   Not a panel's worth of anything, and they must not be lost: a screen that could not read the
   enquiries has to say so, or every count on it is a lie of omission. */
.off-faults-list{margin:6px 0 0;padding-left:18px}
.off-faults-list li{margin:2px 0;font-size:13px;line-height:1.4}
/* ---- BRIGHT ENOUGH TO READ IN THE DARK (v3.523.0) -------------------------------------------
   Darren, 9 September 2026, of this band: *"On the office dashboard, these are a little bit dim.
   Can we make these a bit brighter, please?"*

   THE CAUSE WAS A TOKEN USED FOR TWO JOBS. `--grey-mid` is #BCBEC0 in the light theme and #3A3D46
   in the dark one — because it is an EDGE colour (a border, a dashed rule, a hatched fill), and an
   edge gets darker as the page gets darker. Every `color:var(--grey-mid)` therefore inverted from
   "a soft grey on white" to "near-black on near-black", which is what made these labels, blurbs and
   noughts almost invisible. `--muted` is the text token and moves the right way (#5B6070 light,
   #9A9DA9 dark), so every text colour in both stylesheets now uses it — nineteen declarations
   across seventeen screens, not just this one. test/uipolish.test.mjs keeps `--grey-mid` off text
   for good.

   AND A NOUGHT IS AN ANSWER. "0 missed calls" is the best news on the page, and it was drawn in the
   dimmest grey on the tile — so it reads at full ink weight now, and only the em-dash of an
   unreadable figure stays muted (the dashed border is what says "unknown"). */
.off-board-tile .off-lab{font-size:.8rem;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
.off-board-tile .off-n{font-size:1.9rem}
.off-board-tile.off-zero .off-n{color:var(--ink);font-weight:700}
.off-board-tile.off-notyours{opacity:.7;cursor:default}
.off-board-note{margin:8px 0 0}
/* + New: one small button in the page head, a menu under it */
.off-newmenu{position:relative}
.off-newmenu-list[hidden]{display:none}
.off-newmenu-list{position:absolute;right:0;top:calc(100% + 6px);z-index:40;min-width:170px;
  background:var(--panel);border:1px solid var(--line);border-radius:10px;box-shadow:0 10px 30px rgba(0,0,0,.14);
  padding:6px;display:flex;flex-direction:column}
.off-newmenu-item{text-align:left;background:none;border:0;padding:8px 10px;border-radius:7px;cursor:pointer;
  color:var(--ink);font:inherit}
.off-newmenu-item:hover,.off-newmenu-item:focus-visible{background:var(--panel-2,rgba(0,78,254,.08));outline:none}
/* the inbox */
/* 0269 (v3.647.0) — the list is a real container now, not just a wrapper. It was one div and had
   no rule; with the inbox split into "Needs you now" and "Waiting on us" there are two of them
   stacked, and `.ib-row:first-child{border-top:0}` below only means anything relative to one of
   these. The block spacing is what keeps the second heading off the last row of the first list. */
.ib-list{display:block;margin:0 0 4px}
.ib-list + .ib-sect{margin-top:16px}
/* 0370, v3.724.0 — a FOURTH column at the front for the tick box. The three that follow are
   unchanged (kind icon, message, actions), which is why `.ib-media` below moved from 2 to 3 and
   nothing else in this block needed touching. */
.ib-row{display:grid;grid-template-columns:auto auto 1fr auto;gap:12px;align-items:start;padding:10px 0;border-top:1px solid var(--line)}
.ib-row:first-child{border-top:0}
.ib-kind{font-size:1.3rem;width:32px;text-align:center}
.ib-who{font-weight:700}
.ib-what{color:var(--muted);font-size:.9rem;white-space:pre-wrap;word-break:break-word;
  /* THREE LINES IN A LIST (v3.458.0, three from v3.460.0 at Darren's word: *"let's show the first
     three actual lines"*). Measured: the original 400-character preview ran to FIVE lines, so three
     messages filled a laptop screen; two was legible but thin for an email. The clamp follows the
     column width rather than a character count guessing at it, and the rest is one press away. */
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.ib-when{font-size:.8rem;color:var(--muted)}
.ib-actions{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}
/* ---- THE ROW OPENS (v3.458.0) ---------------------------------------------------------------
   Darren: *"I can't seem to click on an email to open it up and get more info."* The middle of the
   row is now a button, so it needs to STOP LOOKING LIKE ONE: no chrome, inherited type, text to the
   left. The only affordance is the cursor and the hover — a row in a list that grew a button's
   border would read as an action rather than as the message. */
.ib-body{display:block;width:100%;text-align:left;background:none;border:0;padding:0;margin:0;
  font:inherit;color:inherit;cursor:pointer;border-radius:8px}
/* Darren, 7 Sep 2026: *"let's not highlight with an underline when you hover."* The background
   already says the row is pressable; an underline on the sender's name says it is a LINK to that
   person, which it is not — it opens the message. */
.ib-body:hover{background:var(--panel-2)}
.ib-body:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
/* An email's subject is what somebody scans for, so it sits between the sender and the body. */
.ib-subject{font-weight:600;margin:2px 0 0}
/* "Read it all" where the body was cut: the ellipsis is a fact, this is the thing to press. */
.ib-more{color:var(--blue-ink);font-weight:600;font-size:.85rem;margin-top:2px}
/* The audio lives outside the button (a control inside a button cannot be used), so it needs its
   own row under the body rather than the old inline margin. */
.ib-media{grid-column:3;margin-top:6px}
.ib-media audio{max-width:100%}
.ib-media:empty{display:none}
/* ---- THE READER ---------------------------------------------------------------------------- */
.ib-read audio{max-width:100%;margin:0 0 10px}
/* The short version is the first thing in the sheet (v3.460.0), so it gets the top margin the
   lead paragraph above it would otherwise have given the audio. */
/* ---- THE SHORT VERSION: SMALLER, BLUE, ITALIC (v3.460.0) ------------------------------------
   Darren: *"the short version should use less space… maybe make the short version have blue, or
   just make it a little bit nicer to look at… it probably needs a bit more padding under the short
   version, where it starts the actual email. The short version/AI summary should probably be in
   italics so we can see that it's AI."*

   Four things, and the italics is the one that is not decoration: nobody wrote this sentence, and a
   reader has to be able to tell that at a glance from the words rather than from a label they have
   to find. It was a `.panel sub`, which carried a panel's padding and a heading's line-height for
   two lines of text — measured at 236px tall for a three-line summary.

   THE BLUE IS AN EDGE, NOT A FILL. A filled blue block competes with the message it introduces; a
   left rule marks it as an aside and costs no height. The gap under it is deliberately larger than
   the gap above: it separates the AI's words from the sender's, which is the boundary that matters. */
.ib-read-sum{margin:0 0 18px;padding:8px 12px;border-left:3px solid var(--blue);
  border-radius:0 8px 8px 0;background:color-mix(in srgb, var(--blue) 5%, transparent)}
.ib-read-sum-lab{display:block;font-size:11px;font-weight:800;letter-spacing:.04em;
  text-transform:uppercase;color:var(--blue-ink)}
/* ITALIC, so it reads as reported rather than as the message. */
.ib-read-sum-t{margin:2px 0 0;font-style:italic;line-height:1.45}
.ib-read-sum-by{display:block;margin-top:4px;font-size:11.5px;color:var(--muted)}
/* ---- "SHOW EVERYTHING" IS A VIEW, NOT A SETTING (v3.460.0) ----------------------------------
   Darren: *"it probably shouldn't be a tick box… something a bit more obvious, because it's
   something we might want to do semi-regularly."* It sits at the right of the filter row as the
   sixth chip, and the note beside it says which view you are in — the button alone says what
   pressing it does, which is the opposite information. */
.ib-allbtn{margin-left:auto}
.ib-allnote{flex:0 1 auto;max-width:22em;line-height:1.3}
@media (max-width:700px){
  /* On a phone the note goes under the row rather than squeezing the buttons. */
  .ib-allbtn{margin-left:0}
  .ib-allnote{flex:1 1 100%}
}
/* A plain-text message keeps its own line breaks and never pushes the sheet sideways. */
.ib-read-text{white-space:pre-wrap;word-break:break-word;font:inherit;margin:0 0 12px}
/* A sanitised HTML message is a stranger's markup: it is boxed, it scrolls inside its own box
   rather than stretching the sheet, and images are capped so a 2000px banner cannot take over. */
.ib-read-html{border:1px solid var(--line);border-radius:10px;padding:10px 12px;margin:0 0 12px;
  max-height:60vh;overflow:auto;word-break:break-word}
.ib-read-html img{max-width:100%;height:auto}
.ib-read-html table{max-width:100%}
.ib-row.ib-missed .ib-kind{color:var(--danger)}
/* ---- DEALT WITH IS A MARK, NOT A FADE (v3.453.0) --------------------------------------------
   This was `opacity:.55` on the whole row. Darren: *"At the moment, it's very, very grey and hard to
   read."* Right, and the intent was wrong as well as the execution — these rows are the history of
   every call, voicemail and message, and a history you cannot read is not one. Full contrast, a
   green tick over the kind icon (see `.ib-tick`), and a quiet left rule so the eye can still sort
   handled from waiting down the edge of the list. */
.ib-row.ib-done{box-shadow:inset 3px 0 0 var(--ok)}
.ib-row.ib-done .ib-kind{position:relative}
/* ---- WHAT SHOWS RED, AND WHAT SHOWS AMBER (v3.474.0) ----------------------------------------
   Darren: *"Inbox rules for what shows red, like we used to have."* The rules themselves are in
   public/inboxrules.js — including the reason the clock counts working hours only, which is what
   keeps a quiet weekend from painting the whole list red.

   A LEFT RULE, NOT A BACKGROUND. Exactly the device `.ib-done` already uses, three pixels down the
   edge, so the marks form a readable column down the side of the list and the message itself stays
   at full contrast. A red row is a row somebody has to READ; tinting it is the fastest way to make
   the words harder to get at, which is the opposite of what the mark is for.

   `ib-done` WINS WHERE BOTH APPLY. `inboxTone` returns `done` for an attended row and `toneClass`
   gives that no colour, so the two should never meet — but "should never" is not a stylesheet, and a
   history painted red would be the single most alarming thing this screen could do. So the two
   compound selectors below restate green explicitly, and they are more specific than either mark. */
.ib-row.ib-red{box-shadow:inset 3px 0 0 var(--danger)}
.ib-row.ib-amber{box-shadow:inset 3px 0 0 var(--warn,#b26a00)}
.ib-row.ib-done.ib-red,.ib-row.ib-done.ib-amber{box-shadow:inset 3px 0 0 var(--ok)}
.ib-row.ib-done.ib-red .ib-kind{color:inherit}

/* ================================================================================================
   0370 — MARKING SEVERAL AT ONCE (v3.724.0)
   ------------------------------------------------------------------------------------------------
   Darren: *"we need to be able to multi-select SMS, phone calls, or emails to say 'dealt with' or
   'read' or something."*

   A 44px TARGET, which is the whole reason the box is wrapped in a label. The native checkbox is
   ~13px and a 13px target in a list somebody is working through at speed is a list where you tick
   the wrong row. The input itself stays in the page (never `display:none`) so the keyboard and the
   screen reader get the real control; it is the LABEL that is sized, and the visible mark is drawn
   from the input's own `:checked` state rather than from a class the renderer has to remember. */
.ib-pickbox{display:flex;align-items:center;justify-content:center;
  width:44px;min-height:44px;margin:-6px 0 0 -6px;cursor:pointer;border-radius:8px}
.ib-pickbox:hover{background:var(--panel-2)}
.ib-pickbox input{position:absolute;width:18px;height:18px;opacity:0;cursor:pointer;margin:0}
.ib-pickmark{width:20px;height:20px;border:2px solid var(--line);border-radius:6px;
  display:flex;align-items:center;justify-content:center;background:var(--panel)}
.ib-pickbox input:checked+.ib-pickmark{background:var(--blue);border-color:var(--blue)}
/* The tick is drawn, not a character: a glyph would inherit the row's font and change size with it. */
.ib-pickbox input:checked+.ib-pickmark::after{content:'';width:5px;height:10px;margin-top:-2px;
  border:solid var(--panel);border-width:0 2px 2px 0;transform:rotate(45deg)}
.ib-pickbox input:focus-visible+.ib-pickmark{outline:2px solid var(--blue);outline-offset:2px}
/* A CHOSEN ROW IS MARKED, faintly. The left rule is already spoken for three times over (done,
   red, amber), so this is a wash across the row instead — enough to count them at a glance, not
   enough to fight the red marks it has to coexist with. */
/* 0388 — ONE CLASS FOR EVERY LIST. `.ib-row.ib-picked` was specific to the communications rows
   because they were the only list with ticks; `is-picked` is now worn by an arrival, a comms row
   and a drawer row alike, so the chosen state cannot look different depending on which list you
   are working down. */
.ib-row.is-picked,.ia-row.is-picked,.cm-row.is-picked{
  background:color-mix(in srgb, var(--blue) 6%, transparent)}
/* The arrivals rows are a grid; the tick sits in the identity line beside the kind icon. */
.ia-head .ib-pickbox{margin:-6px 6px 0 -6px}
/* In the drawer the tick shares the action strip with "Mark dealt with". */
.cm-actions{display:flex;align-items:center;gap:6px}

/* ---- 0390 — THE MICROPHONE PICKER AND ITS LEVEL BAR (v3.735.0) --------------------------------
   Darren, of a call nobody could hear him on: *"The mic meter was part of google Chrome."* Chrome's
   indicator says the page is capturing; it does not say the call can hear you. This bar is fed by
   the SDK's own `inputVolume`, off the same capture path the call sends from, so it answers the
   question that was actually being asked. */
.ph-mic{margin-top:10px}
.ph-micbar{height:8px;border-radius:5px;background:var(--panel-2,var(--line));overflow:hidden;
  margin:6px 0 4px}
.ph-micbar>span{display:block;height:100%;width:0;background:var(--accent);
  /* Fast up, slow down — a bar that decays instantly reads as broken on the gaps between words. */
  transition:width 90ms linear}
.ph-mic select{width:100%}

/* THE BAR IS ALWAYS THERE and its buttons are disabled rather than hidden — a control that appears
   only once something is ticked is a control nobody finds, and one that appears mid-press is a row
   of buttons that moves under the pointer. */
.ib-bulk{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:10px 0 2px;
  padding:8px 10px;border:1px solid var(--line);border-radius:10px;background:var(--panel-2)}
.ib-bulkn{font-size:.85rem;color:var(--muted);margin-right:auto}
.ib-bulk .btn[disabled]{opacity:.5;cursor:default}

/* ================================================================================================
   0369 — THE SECTION CHIPS (v3.724.0)
   ------------------------------------------------------------------------------------------------
   Darren: *"the inbox probably needs chips at the top because to get to people who have been in
   touch: you have to scroll down quite a long way."*

   FULL-SIZE BUTTONS, not `.btn small`. These are the screen's navigation — the thing pressed first
   and most often — and the filter chips below them (`.ib-filters`) are `small`, so the size
   difference is what says which row is which. They wrap on a phone rather than scrolling
   sideways: a horizontally scrolling chip row hides its last chip, and "Ignored" being the one
   nobody can find is exactly the bug this replaces.

   THE COUNT IS A PILL, so a two-digit and a three-digit number take the same shape and the row
   does not reflow as the morning's mail arrives. */
.ib-sects{padding:10px 12px}
.ib-segs{display:flex;gap:8px;flex-wrap:wrap}
.ib-seg{display:inline-flex;align-items:center;gap:8px;min-height:44px}
.ib-segn{display:inline-flex;align-items:center;justify-content:center;min-width:24px;
  padding:0 6px;height:22px;border-radius:11px;font-size:.78rem;font-weight:800;
  background:var(--panel-2);color:var(--muted)}
/* On the chosen chip the pill sits on a coloured button, so it takes the panel colour to stay
   readable — `var(--muted)` on `var(--blue)` is the contrast failure this avoids. */
.ib-seg.primary .ib-segn{background:var(--panel);color:var(--ink)}
/* NOUGHT IS QUIETER, NOT HIDDEN. An empty section you can see is work that is finished; a chip
   with no number beside it reads as a chip that forgot to count. */
.ib-segz{opacity:.55}
/* AND "WE COULD NOT READ IT" IS NOT NOUGHT. Same refusal the panels themselves make. */
.ib-segq{background:color-mix(in srgb, var(--warn,#b26a00) 18%, transparent)}

/* ================================================================================================
   THE BOOKINGS AREA (0171, v3.475.0)
   ------------------------------------------------------------------------------------------------
   Darren: *"I don't really see anywhere that says 'booked sessions'."* and *"we should be able to
   search for appointments, bookings, or calendar appointments in a list as well, based on those
   various stages."*

   THE CLOCK IS THE THING THE EYE FINDS. Everything below is arranged so that "the hold ran out 3
   hours ago" is the second thing read after the customer's name — the stage chip, the dates and the
   value are context for it. The stage colour is a left rule, the same device the inbox and the
   dealt-with mark already use, so the row's own words stay at full contrast.
   ================================================================================================ */
.bkg-chips{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0 4px}
.bkg-count{font-variant-numeric:tabular-nums;opacity:.85;margin-left:2px}
/* THE TOTAL BESIDE THE TITLE. It was `.bk-total` — a name public/styles.css also uses for the
   CHECKOUT BASKET's total (`display:grid`, a 2px border-top, 16px of margin), so the number
   rendered as a bordered block and pushed the help dot onto a third line. The whole screen now owns
   the `bkg-` prefix instead of sharing `bk-` with the basket; see the note in
   public/apps/bookings.js and the rule in test/uipolish.test.mjs. */
.bkg-headcount{font-weight:800;font-size:1.08em;font-variant-numeric:tabular-nums}
/* The title, the count, the "?" and the two tabs on ONE row, with the tabs pushed to the right so
   the row reads title-first however wide the window is. */
.bkg-head{justify-content:space-between}
.bkg-head h2{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.bkg-head .bkg-tabs{margin:0}
.bkg-filters{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end;margin:8px 0 12px}
.bkg-find{flex:1 1 260px;min-width:0}
.bkg-find input{width:100%;max-width:100%;padding:6px 8px;border:1px solid var(--line);
  border-radius:8px;font:inherit;background:var(--panel);color:inherit}
.bkg-sel{display:flex;flex-direction:column;gap:3px;font-size:12px;color:var(--muted)}
.bkg-sel select,.bkg-sel input{padding:6px 8px;border:1px solid var(--line);border-radius:8px;
  font:inherit;background:var(--panel);color:inherit}
.bkg-note{margin:0 0 10px}
.bkg-list{display:flex;flex-direction:column}
.bkg-row{display:grid;grid-template-columns:1fr auto;gap:12px;align-items:start;padding:10px 0 10px 10px;
  border-top:1px solid var(--line)}
.bkg-row:first-child{border-top:0}
.bkg-main{min-width:0}
.bkg-who{font-size:14px;line-height:1.35;overflow-wrap:anywhere}
.bkg-meta{display:flex;flex-wrap:wrap;gap:4px 10px;margin-top:3px;font-size:12px;color:var(--muted)}
.bkg-stage{font-weight:700;color:inherit}
/* WHICH KIND, ON EVERY ROW (v3.533.0) — *"it should say onsite / remote / offline next to all the
   bookings."* A quiet chip beside the stage: on-site is the one that means somebody drives, so it
   is the one that carries a colour. */
.bkg-kind{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;
  padding:1px 6px;border-radius:999px;border:1px solid var(--line);color:var(--muted)}
.bkg-kind-onsite{border-color:var(--blue);color:var(--blue-ink)}
.bkg-dates,.bkg-when,.bkg-cons,.bkg-value{white-space:nowrap}
.bkg-value{font-variant-numeric:tabular-nums}
.bkg-clock{margin-top:4px;font-size:12.5px;font-weight:600}
.bkg-clocklate{color:var(--danger)}
.bkg-stale{margin-top:4px;font-size:12px;color:var(--warn,#b26a00)}
.bkg-acts{display:flex;flex-wrap:wrap;gap:6px;justify-content:flex-end}
.bkg-more{margin:10px 0 0}
/* The stage, as a left rule. Same three pixels as .ib-done and .ib-red — see the inbox block. */
.bkg-tentative{box-shadow:inset 3px 0 0 var(--warn,#b26a00)}
.bkg-confirmed{box-shadow:inset 3px 0 0 var(--blue)}
.bkg-locked{box-shadow:inset 3px 0 0 var(--ok)}
.bkg-delivered{box-shadow:inset 3px 0 0 var(--grey-mid)}
.bkg-cancelled{box-shadow:inset 3px 0 0 var(--line)}
.bkg-none{box-shadow:inset 3px 0 0 var(--line)}
/* A clock that has RUN OUT beats the stage colour — it is the row somebody has to act on today. */
.bkg-row.bkg-late{box-shadow:inset 3px 0 0 var(--danger)}
/* ---- THE ONE SOMEBODY ASKED FOR (v3.517.0) -------------------------------------------------
   "Open it" on an enquiry's session row lands here with the booking searched for by `#id`. The
   search may legitimately return more than one row, so the one that was asked for says so. A tint
   and an inset bar rather than an outline: an outline on a grid row reads as a focus ring and
   somebody tries to type into it. */
.bkg-row.bkg-focus{box-shadow:inset 3px 0 0 var(--blue);background:var(--blue-lt,rgba(0,78,254,.06))}
.bkg-row.bkg-focus.bkg-late{box-shadow:inset 3px 0 0 var(--danger),inset 6px 0 0 var(--blue)}
/* ---- ON A PHONE THE ROW STACKS ----------------------------------------------------------------
   The same lesson as `.ib-row` at 420px: a two-column grid leaves the customer's name about 150px
   wide while two buttons stack down the right. Full width for the words, buttons underneath. */
@media(max-width:560px){
  .bkg-row{grid-template-columns:1fr;row-gap:8px}
  .bkg-acts{justify-content:flex-start}
  .bkg-find,.bkg-sel{flex-basis:100%}
  .bkg-sel select,.bkg-sel input{width:100%}
  /* The tabs drop under the title rather than squeezing it — a two-word title in a 90px column is
     worse than a second row. */
  .bkg-head{flex-wrap:wrap}
}

/* ================================================================================================
   THE BOOKING SHEET: CALENDAR ONE SIDE, THE BOOKING THE OTHER (0171, v3.476.0)
   ------------------------------------------------------------------------------------------------
   Darren, 7 September 2026: *"when we're booking a session, it should come up with the calendar on
   one side of the screen and the session booking details on the other side of the screen… Blocks
   should show for the amount of time selected, so it's easy to click on one and it will just add
   those details in."*

   A BLOCK IS A BUTTON, AND IT IS 44px TALL. Every one of these is a touch target on a tablet in
   somebody's hand, and a 28px block is a block you miss. The day columns scroll HORIZONTALLY rather
   than shrinking below legibility: fourteen columns of 78px is 1090px, and squeezing that into 520px
   would give 37px a column — not enough for "10:30am", which is the whole content.

   SIDE BY SIDE ONLY WHERE THERE IS ROOM. Under 900px the two columns stack, calendar first, because
   the calendar is where the work is done and the form below it is what the clicking fills in.
   ================================================================================================ */
/* ---- THE TWO SIDES, AND A HANDLE BETWEEN THEM (v3.527.0) -----------------------------------
   *"can we add a slider bar to change the split between calendar and menu."* The default is what it
   always was — the calendar takes what is left of 340px — and `--bs-split` overrides it the moment
   somebody drags. Three columns, so the handle IS the boundary rather than something sitting near
   it. */
/* ---- THE RIGHT PANE HAS A FLOOR, AND THE THIN BAR IS REACHABLE AGAIN (v3.533.0) -------------
   Darren, 9 September 2026: *"When I push the width bar out to the left a bit I can't then slide it
   back to where it was as a thin bar. Looks like there's a minimum width of the right bar."*

   Right on both counts, and they were the same fault. Untouched, the right pane is 340px — a thin
   form. The moment the handle is dragged, `--bs-rest` becomes `1fr` and the ratio drives BOTH sides,
   so the narrowest the form could get was the clamp's ceiling (72% left = 28% right), which at any
   ordinary window is WIDER than the 340px it started at. The default was unreachable by dragging.

   So the clamp goes to 86% and the floor moves where it belongs — on the pane it is about:
   `minmax(320px, …)` means the form stops at 320px and the calendar takes everything else, in
   pixels, whatever the window. Dragging further simply stops rather than being refused at 72%. */
.bs-wrap{display:grid;
  grid-template-columns:var(--bs-split,minmax(0,1fr)) 10px minmax(320px,var(--bs-rest,340px));
  gap:8px;align-items:start}
.bs-wrap[style*="--bs-split"]{--bs-rest:1fr}
.bs-left{min-width:0}
.bs-right{min-width:0}
/* ---- THE TOPICS PANEL INSIDE THE BOOKING SHEET (0316) ----------------------------------------
   The slot is always rendered so the node can always be relocated into it; `:empty` is what keeps
   a new booking (which has no agenda to have) from showing a rule and a gap. The top border is the
   only separation it gets — the form above it is already a stack of labelled blocks, and a second
   card inside a card reads as a bug. */
.bs-agenda:empty{display:none}
.bs-agenda{margin-top:14px;padding-top:12px;border-top:1px solid var(--line)}
.bs-agenda .ag-panel-h{margin:0 0 2px;font-size:15px}
.bs-agenda .ag-panel-lead{margin:0 0 8px}
/* Textareas in a 320px column: full width, and never wider than it. A textarea's default `cols`
   makes it overflow a narrow pane, which on this sheet is a horizontal scrollbar under the form. */
.bs-agenda textarea{width:100%;max-width:100%;box-sizing:border-box}
.bs-agenda h3{margin:12px 0 2px;font-size:13.5px}
.bs-agenda hr{margin:14px 0;border:0;border-top:1px solid var(--line)}
/* A REAL CONTROL: focusable, arrow-key movable, and wide enough to grab. The bar is drawn on the
   inner span so the hit area can be bigger than the line somebody is aiming at — 10px of target
   for a 2px rule. */
.bs-split{align-self:stretch;min-height:120px;display:flex;align-items:center;
  justify-content:center;cursor:col-resize;background:transparent;border:0;padding:0;
  touch-action:none}
.bs-split>span{display:block;width:2px;height:100%;background:var(--line);border-radius:2px;
  transition:background .12s,width .12s}
.bs-split:hover>span,.bs-split:focus-visible>span{background:var(--blue);width:4px}
.bs-split:focus-visible{outline:2px solid var(--blue);outline-offset:2px;border-radius:4px}
@media(max-width:900px){ .bs-split{display:none} }
.bs-cal{min-width:0}
/* ---- ONE ROW ACROSS THE TOP (v3.533.0) ------------------------------------------------------
   *"I feel like we should utilise the top section space better."* Measured: the paging row, the view
   chips and the timezone note were three stacked rows at y=116/152/192, each about a quarter full,
   on a screen whose whole point is how many blocks fit. All three answer "what am I looking at", so
   they share one wrapping row — and the clock, the only one that is not a control, sits last and
   pushes to the right where there is room for it. */
.bs-topbar{display:flex;align-items:center;flex-wrap:wrap;gap:8px 14px;margin:0 0 8px}
.bs-topbar .bs-navrow,.bs-topbar .bs-views{margin:0}
.bs-topbar .bs-zone{margin-left:auto;white-space:nowrap}
/* ---- WHICH CLOCK, AS A CONTROL (v3.536.0) ---------------------------------------------------
   *"Can we have a timezone toggle…"* It was a sentence ("Times in Australia/Sydney") in the corner
   of the top bar; it is the same corner, now a select with the zone and the gap to our own clock
   under it. Pushed right, like the sentence it replaced. */
.bs-tz{margin-left:auto;display:flex;flex-direction:column;align-items:flex-end;gap:2px}
.bs-tz select{font-size:12px;padding:3px 6px;max-width:230px}
.bs-tzwhy{white-space:nowrap}
.bs-navrow{display:flex;align-items:center;gap:10px;margin:0 0 8px}
.bs-range{font-weight:700;font-variant-numeric:tabular-nums}
.bs-warn{margin:0 0 8px;padding:8px 10px;border-radius:var(--r);background:var(--orange-soft,rgba(178,106,0,.1));
  border-left:3px solid var(--warn,#b26a00);font-size:12.5px}
.bs-checked{margin:8px 0 0;font-size:12px}
/* The days, scrolling sideways inside their own container — the page body never scrolls across. */
/* v3.553.0 — the fortnight is now one grid too (see booksheet.bandGridHtml), so `.bs-bands` and
   this share the same cell rules. The strip still scrolls sideways; nothing is squeezed. */
.bs-days{display:flex;gap:6px;overflow-x:auto;padding-bottom:6px}
.bs-cal .bs-banddays{overflow-x:auto;padding-bottom:6px}
/* ---- WIDE ENOUGH TO READ (v3.547.0) ---------------------------------------------------------
   Darren, for the fourth time: *"it doesn't show what's going on, who's doing what, where, on the
   calendar view."* The blocks now DRAW the customer, the state and the consultant instead of
   hiding them in a tooltip (see booksheet.faceHtml), and 78px cannot hold a company name. The
   strip already scrolls sideways inside itself, so widening a column costs a scroll, not a layout.
*/
.bs-day{flex:0 0 190px;min-width:190px;display:flex;flex-direction:column;gap:4px}
.bs-dayhead{display:flex;flex-direction:column;align-items:center;line-height:1.2;padding:2px 0 4px;
  border-bottom:1px solid var(--line);font-size:12px}
.bs-weekend{opacity:.55}
.bs-none{text-align:center;color:var(--muted);font-size:12px;padding:6px 0}
/* ---- THE FOUR STATES ------------------------------------------------------------------------
   free / taken / unchecked / on-the-booking. `unchecked` is the one nobody would guess, so it is
   striped as well as coloured and the key below the grid names all four in words. */
.bs-slot{min-height:44px;width:100%;border:1px solid var(--line);border-radius:8px;background:var(--panel);
  color:inherit;font:inherit;font-size:12.5px;font-variant-numeric:tabular-nums;cursor:pointer;
  position:relative;padding:4px 2px}
.bs-slot:disabled{cursor:not-allowed}
/* ---- THE FACE OF A BLOCK (v3.547.0) ---------------------------------------------------------
   A stack, left-aligned, tight leading: the time, then the state, the customer, what it is and who
   is doing it. A free block has only a time in it and looks exactly as it did. */
/* ---- EVERY BLOCK THE SAME HEIGHT (v3.553.0) -------------------------------------------------
   MEASURED in the bands view, which v3.547.0 did not measure: a block with a booking on it was 79px
   and an empty one 44px, so the nth row of one column did not line up with the nth row of the next
   — and in a layout whose whole job is comparing three consultants' weeks side by side, rows that
   do not line up read as blocks drawn on top of each other. A FIXED height, so a week is a grid
   again. The face is clipped to it and the list under the calendar carries the full text. */
.bs-slot{display:flex;flex-direction:column;align-items:flex-start;gap:1px;text-align:left;
  padding:4px 5px;overflow:hidden;min-height:44px;width:100%}
.bs-slottime{font-weight:700;font-variant-numeric:tabular-nums;line-height:1.15}
.bs-bk{align-self:flex-start;font-size:9.5px;font-weight:800;letter-spacing:.04em;
  text-transform:uppercase;padding:1px 4px;border-radius:999px;line-height:1.3;white-space:nowrap;
  max-width:100%;overflow:hidden;text-overflow:ellipsis}
.bs-bk-held{background:var(--orange,#FF6D00);color:#fff}
.bs-bk-unpaid{background:var(--blue,#004EFE);color:#fff}
.bs-bk-paid{background:var(--ok,#1E8C50);color:#fff}
.bs-bk-theirs{background:var(--grey-light,#e3e3e3);color:var(--muted)}
.bs-bk-off{background:transparent;color:var(--muted);border:1px dashed var(--line)}
.bs-bkline{font-size:10px;line-height:1.2;max-width:100%;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;font-weight:400}
.bs-bkco{font-weight:700}
.bs-bkcont{opacity:.75}
.bs-bkdim{color:var(--muted)}
/* v3.553.0 — a real event out of somebody's own diary: its own start and finish, then its subject.
   Not dimmed as far as `bs-bkdim`: it is the thing Darren asked to be able to READ off the block. */
.bs-bkev{color:var(--ink);opacity:.85}
/* ---- WHAT IS ON, IN FULL, UNDER THE GRID (v3.547.0) -----------------------------------------
   The reading that cannot truncate and cannot need a hover. Also the answer to *"when booking an
   enquiry, we can't see what people are doing before or after"* — the booking sheet draws the same
   grid, so this list appears there as well. */
.bs-agenda{margin:12px 0 0;padding:10px 12px;border:1px solid var(--line);border-radius:var(--r);
  background:var(--panel-2,rgba(0,0,0,.02))}
.bs-agendah{margin:0 0 8px;font-size:13px}
.bs-agendal{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
.bs-agendarow{display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-size:12.5px;
  padding-bottom:6px;border-bottom:1px solid var(--line)}
.bs-agendarow:last-child{border-bottom:0;padding-bottom:0}
.bs-agwhen{font-weight:700;font-variant-numeric:tabular-nums;min-width:170px}
.bs-agco{font-weight:700}
.bs-agwhat,.bs-agwho{color:var(--muted)}
.bs-agendarow .btn{margin-left:auto}
.bs-free{border-color:var(--ok);background:var(--green-soft,rgba(30,140,80,.08))}
.bs-free:hover{background:var(--green-soft,rgba(30,140,80,.18))}
.bs-busy{border-color:var(--line);background:repeating-linear-gradient(135deg,
  var(--grey-light) 0 5px,transparent 5px 10px);color:var(--muted)}
/* ---- A BOOKED BLOCK YOU CAN OPEN (v3.544.0) --------------------------------------------------
   Darren: *"It is vital that calendars show all the appointments and even link through to the
   inquiry."* A busy block covered by a booking of ours is no longer a dead square — pressing it
   opens the enquiry. It has to LOOK pressable, and it must not look free: the hatching stays, the
   cursor and the text say it does something, and the border firms up. */
/* ---- TENTATIVE IS NOT BOOKED (v3.546.0) -----------------------------------------------------
   Darren: *"and of course showing that it is tentative."* A time somebody has AGREED to and a time
   we have merely OFFERED are the two most different things on a consultant's week, and both were
   the same grey hatching. Amber, and a dashed edge — the same amber the enquiry board uses for
   TENTATIVE, so the two screens mean one thing by one colour. */
.bs-held{border-style:dashed;border-color:var(--orange,#FF6D00);
  background:repeating-linear-gradient(135deg,rgba(255,109,0,.14) 0 5px,transparent 5px 10px)}
.bs-held.bs-slotgo{border-color:var(--orange,#FF6D00)}
.bs-slotgo{cursor:pointer;color:var(--ink);border-color:var(--blue)}
.bs-slotgo:hover,.bs-slotgo:focus-visible{border-color:var(--blue);
  background:repeating-linear-gradient(135deg,var(--blue-wash,rgba(0,78,254,.10)) 0 5px,
  transparent 5px 10px)}
.bs-past{border-style:dashed;color:var(--muted);background:transparent}
/* ---- A PAST BLOCK THAT HAD SOMETHING ON IT (v3.526.0) ---------------------------------------
   *"It should show what bookings were already there… it's useful to see what's before and after,
   even if it's days before."* So a Monday that was fully booked no longer looks like an empty one:
   the same hatching a busy block wears, at half strength, and still un-clickable. The tooltip says
   what it was. */
.bs-waspast{background:repeating-linear-gradient(135deg,var(--grey-light) 0 5px,transparent 5px 10px);
  border-style:dashed;opacity:.8}
.bs-uncertain{border-color:var(--warn,#b26a00);border-style:dashed;
  background:var(--orange-soft,rgba(178,106,0,.07))}
.bs-on{border-color:var(--blue);border-width:2px;background:var(--blue-soft,rgba(0,78,254,.12));
  font-weight:700}
.bs-tick{position:absolute;top:1px;right:3px;font-size:10px;color:var(--blue-ink)}
/* ---- A PICK WITH NOBODY ON IT (v3.528.0) ----------------------------------------------------
   Darren: *"I clicked on a single block for a consultant and 3 blocks highlighted (one for each
   consultant)."* An owned pick is now drawn in its own column only. This is the other case — a time
   picked in the one-grid view, with nobody assigned yet — which honestly IS on the booking for all
   of them, so it is still ticked in each column and drawn dashed rather than solid to say that the
   three rings are one pick rather than three. */
.bs-onany{border-style:dashed;font-weight:600}
/* ---- WHO CAN TAKE THIS BLOCK (0193, v3.517.0) ------------------------------------------------
   Initials, under the time, in the block's own colour at 10px. Measured: three full names took a
   44px block to three lines and the calendar is fourteen of these side by side, so the badge is
   initials and the names are in the title. `display:block` so it sits under the time rather than
   pushing the column wider — the column width is what the whole grid's legibility rests on. */
.bs-who{display:block;font-size:10px;line-height:1.2;letter-spacing:.04em;opacity:.75;
  font-weight:700;margin-top:1px}

/* ================================================================================================
   THREE ARRANGEMENTS OF THE SAME BLOCKS (v3.518.0)
   ================================================================================================
   Darren, 9 Sep 2026, with an Outlook screenshot: five days for each consultant, side by side,
   scrolling left and right; or one consultant over two or three weeks; or compressed by weekday
   with a column per consultant. Plus chips to switch, and a sheet big enough to hold them. */

.bs-views{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 10px}
.bs-view{white-space:nowrap}

/* ---- VIEWS 1 AND 2: BANDS OF FIVE DAYS ------------------------------------------------------
   A band is a consultant's week (view 1) or a week of one consultant (view 2). The label column is
   STICKY, because *"we can scroll across to the left or right"* — and four weeks scrolled sideways
   with no label is four anonymous grids. */
/* ---- SIDE BY SIDE, LIKE THE OUTLOOK SCREENSHOT HE SENT --------------------------------------
   MEASURED FIRST, AND WRONG: the first build stacked the bands vertically with a label down the
   left, which reads fine and is not what he asked for. His screenshot is three PANELS across —
   Darren | Hayden | Jarryd — each with its own five day columns, and *"we can scroll across to the
   left or right"* only makes sense of that arrangement. Two or three weeks for one consultant is
   *"in the same way"*, so it is the same strip with weeks as the panels. */
/* ---- BANDS STACK, THEY DO NOT SIT SIDE BY SIDE (v3.556.0) -----------------------------------
   MEASURED in a browser, which is the only way this was ever going to be found: three consultants'
   weeks were laid out as three columns ACROSS, so Hayden's Friday was followed by Jarryd's Monday
   and the whole point of the arrangement — comparing the same days down the page — was gone. It was
   right while a band was five narrow tiled columns; a band drawn to scale is as wide as a week, so
   two of them side by side is 2,000 pixels of horizontal scroll to compare Tuesday with Tuesday.
   One band per ROW, each scrolling sideways on its own. */
.bs-bands{display:flex;flex-direction:column;gap:14px;padding-bottom:8px;align-items:stretch}
.bs-band{min-width:0}
/* ---- THE SAME BANDS, ACROSS INSTEAD OF DOWN (v3.563.0) ------------------------------------
   *"I would like to have a view that's exactly the same as the by-the-way consultant view, but it
   puts each consultant across the page. Instead of scrolling down to see the next consultant, you
   scroll across."*

   Exactly the same bands, one CSS rule apart: the stack becomes a row and the page scrolls
   sideways. Nothing in the renderer changes, which is the point — two renderers drawing "a
   consultant's week" would drift the first time one of them was corrected. Each band keeps its own
   sticky label, so scrolling four consultants sideways never loses whose week you are reading. */
.bs-bands-across{flex-direction:row;flex-wrap:nowrap;overflow-x:auto;gap:18px;align-items:flex-start}
.bs-bands-across .bs-band{flex:0 0 auto}
.bs-bands-across .bs-bandlab{position:static}
.bs-bands-across .bs-tgwrap{max-height:none}
/* The panel's own name sits above its week and STICKS to the top of the scroller, so a long day
   scrolled down does not lose whose column it is. */
.bs-bandlab{position:sticky;top:0;z-index:1;background:var(--panel);padding:0 2px 6px;
  display:flex;flex-direction:column;gap:1px;font-size:13px;
  border-bottom:2px solid var(--blue)}
.bs-bandlab .hint{font-size:11px;line-height:1.25}
/* FIVE COLUMNS THAT DO NOT SHRINK. `1fr` would squeeze three consultants' weeks into the panel and
   give each block 26px, which is two characters of a time — the thing the horizontal scroll exists
   to avoid. */
/* ---- ONE GRID PER BAND, AND WIDE ENOUGH (v3.553.0) -----------------------------------------
   Darren: *"If it needs to be wider each day to fit what we need to fit, that's fine. We can scroll
   across. That's why we've got so many different views. Don't squeeze it if it doesn't need to be
   squeezed."*
   So: a column per day at a width that fits a company name and an Outlook subject, a row per block,
   every cell PLACED (see booksheet.bandGridHtml). `grid-auto-rows:auto` makes every row as tall as
   its tallest cell in every column at once — which is what stops a booking card growing its column
   and drawing over the one beside it. The strip scrolls sideways; nothing is squeezed. */
.bs-banddays{display:grid;grid-template-columns:repeat(var(--bs-cols,5),var(--bs-col,190px));
  gap:6px;min-width:0;padding-top:6px;align-items:stretch}
.bs-cell{display:flex}
.bs-cell .bs-slot{height:auto}
.bs-cellgap{min-height:0}

/* ---- VIEW 3: ONE DAY AT A TIME, EVERY CONSULTANT IN IT (v3.523.0) --------------------------
   *"Monday the 28th for each consultant; to the right of that: Tuesday the 29th for each
   consultant."* So: a GROUP per date across the screen, a consultant column inside each group. The
   groups scroll sideways rather than shrinking, for the same reason the bands do — a squeezed
   column gives a block 26px, which is two characters of a time. */
.bs-byday{display:flex;gap:14px;overflow-x:auto;padding-bottom:8px;align-items:flex-start}
.bs-dayblock{flex:0 0 auto;min-width:0;border:1px solid var(--line);border-radius:8px;
  padding:6px 6px 8px;background:var(--panel)}
/* The date is the heading of its own group — once, above everybody's column, which is what fixed
   the misalignment he spotted when the name sat under the date in the other views. */
.bs-dayblockhead{position:sticky;top:0;z-index:1;background:var(--panel);display:flex;
  flex-direction:column;gap:1px;font-size:13px;padding:0 2px 6px;text-align:center;
  border-bottom:2px solid var(--blue)}
.bs-dayblockhead .hint{font-size:11px;line-height:1.2;font-variant-numeric:tabular-nums}
/* ---- THE BY-WEEKDAY GROUP IS ONE SCROLLER, NOT NINE (fixed v3.566.0) ------------------------
   Darren, 10 September 2026, with a screenshot: *"Small issue with by weekday view."*

   MEASURED: every consultant column in the group had its own vertical scrollbar, and every one of
   them was scrolled to a different place — so Darren Whitby's 9am and Hayden Nosatti's 9am, side by
   side in the same day, were at different heights on the screen. In the one view whose entire
   purpose is comparing people down a shared clock.

   TWO CAUSES, one shape. `.bs-tgwrap` carries `max-height` and `overflow-x:auto`, and CSS turns the
   OTHER axis into `auto` the moment one of them is not `visible` — so every wrap became vertically
   scrollable without anybody writing `overflow-y` anywhere. In the other views that is right and
   deliberate (one band, one scroller). Here there are three wraps per day plus an axis that is not
   in any of them, so it was three scrollers and a ruler that moved with none of them. v3.563.0's
   "open with the working day centred" then scrolled each of them independently, which is what made
   an old misalignment into a visible one.

   SO THE GROUP SCROLLS AND THE COLUMNS DO NOT. `.bs-daycols` takes the ceiling and the overflow;
   the wraps inside it are released. The axis is inside the same scroller, so the ruler and the
   blocks move together — which is the only arrangement in which "who can do Wednesday" can be read
   across.

   AND THE AXIS GETS ITS OWN TRACK. `grid-auto-columns` was applying the 84–132px consultant width
   to the ruler as well, and `.bs-tgaxis`'s `flex:0 0 54px` means nothing to a grid item — so the
   ruler was as wide as a person's column and the labels inside it overlapped. */
.bs-daycols{display:grid;grid-auto-flow:column;grid-template-columns:54px;
  grid-auto-columns:minmax(84px,132px);gap:6px;
  padding-top:6px;justify-content:start;align-items:start;
  max-height:var(--bs-bandmax,52vh);overflow:auto}
/* The ruler's own column: the same head-plus-body structure as a consultant's, so the hours line
   up with the blocks by construction rather than by a margin somebody has to keep in step. */
.bs-daycolaxis{min-width:0;display:flex;flex-direction:column;gap:4px}
/* `flex:0 0 54px` on `.bs-tgaxis` is a WIDTH inside the wrap, which is a row. In this column it is
   a HEIGHT, and it flattened the whole ruler to 54px tall. The width here comes from the grid
   track; the height is the axis's own `calc(--bs-span × --bs-px)`. */
.bs-daycolaxis .bs-tgaxis{flex:0 0 auto;margin-top:0}
.bs-daycols .bs-tgwrap{max-height:none;overflow:visible}
.bs-daycol{min-width:0;display:flex;flex-direction:column;gap:4px}
/* INITIALS ON THE COLUMN, names in the key below: five groups × four full names is twenty labels
   and no room left for a time. */
.bs-daycolhead{font-size:11px;color:var(--muted);text-align:center;letter-spacing:.04em}
/* TIGHTER BLOCKS HERE THAN ANYWHERE ELSE. Five weekdays × several people × every start time in the
   day is the densest of the three views; 28px is still a comfortable target and the sheet scrolls.
   (Measured: an uncapped column drew 331px-wide blocks and a 5,418px-tall panel.) */
.bs-daycol .bs-slot{min-height:28px;font-size:11.5px;padding:2px}
/* A weekday already past keeps its group so the week always reads Mon–Fri, and says why it is
   empty rather than looking like a day nobody is free on. */
.bs-daypad{opacity:.6}
.bs-padnote{text-align:center;font-size:11.5px;padding:10px 0}
.bs-whokey{margin:8px 0 0;line-height:1.4}

/* ---- A DAY SOMEBODY DOES NOT WORK -----------------------------------------------------------
   Not "taken", and it must not look like it: a booking is something the office can move and a
   four-day week is not. Flat, faint, and no hatching. */
.bs-off{border-style:dotted;border-color:var(--line);background:transparent;color:var(--muted);
  opacity:.55}

/* ---- THE MULTI-TICK CONSULTANT CONTROL ------------------------------------------------------ */
/* ---- WHAT THIS ENQUIRY ALREADY HAS (v3.519.0) ------------------------------------------------
   At the top of the booking side, tinted so it reads as context rather than as another form. It is
   the answer to *"it doesn't say what's already been booked"*, so it goes above everything and is
   not something you scroll to. */
/* ---- OUR LETTERHEAD ON THE QUOTE A CUSTOMER READS (v3.520.0) --------------------------------
   The logo beside our name, ABN, address and two ways to reach us. Capped in height rather than
   width: a landscape mark and a squarer one both have to sit on the same line as four lines of
   text, and height is the dimension that decides that. */
/* The page's own brand bar steps aside once the document's real letterhead has arrived — see the
   note in public/quote.html. The theme toggle beside it stays: it is page chrome, not branding. */
body.qp-haveletter .qp-top .qp-brand{display:none}

.qp-letter{display:flex;align-items:flex-start;gap:14px;flex-wrap:wrap;margin:0 0 22px;
  padding-bottom:14px;border-bottom:1px solid var(--line)}
.qp-ourlogo{max-height:56px;max-width:220px;width:auto;height:auto}
.qp-ourlines{display:flex;flex-direction:column;gap:1px;font-size:12.5px;color:var(--muted);
  line-height:1.45}
.qp-ourlines b{font-size:14px;color:var(--ink,inherit)}
@media (max-width:520px){ .qp-ourlogo{max-height:44px} }

/* ---- SUGGEST SOME TIMES (0193, v3.521.0) ----------------------------------------------------
   Above the list it fills, because that is the order it happens in. The reasons are a list rather
   than a sentence: three dates read as three lines and as one paragraph they are a wall. */
.bs-suggest{margin:0 0 10px;padding-bottom:10px;border-bottom:1px solid var(--line)}
/* ---- 0241 — HOW MANY TO SUGGEST, ON THE BUTTON THAT SUGGESTS (v3.631.0) --------------------
   Darren: *"What's important is how many dates should be chosen. Not how many we offer."* So the
   offer count stopped being a field of the booking and became a spinner on the one control it
   affects. Inline with the button, sized to the digits — a full-width form row for a number that
   gates nothing was the thing that made it look as important as the question above it. */
.bs-suggest{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.bs-sugn{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--muted)}
.bs-sugn input{width:4.5em;padding:4px 6px;font-variant-numeric:tabular-nums}
/* The explanation and the reasons are their own line under the row, not squeezed beside it. */
.bs-suggest > .hint,.bs-swhy,.bs-snote{flex:1 0 100%}
.bs-swhy{list-style:none;margin:6px 0 0;padding:0;display:flex;flex-direction:column;gap:2px;
  font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}
.bs-snote{display:block;margin-top:6px;line-height:1.4}

.bs-already{background:var(--grey-light);border:1px solid var(--line);border-left:3px solid var(--blue);
  border-radius:0 6px 6px 0;padding:8px 10px;margin:0 0 12px}
.bs-already h4{margin:0 0 6px;font-size:13px}
.bs-alist{list-style:none;margin:0 0 8px;padding:0;display:flex;flex-direction:column;gap:6px;
  font-size:12.5px}
.bs-alist .hint{font-size:11.5px}
.bs-aid{color:var(--muted);font-variant-numeric:tabular-nums}

.bs-crew{display:block}
.bs-crewlab{display:block;font-size:12px;color:var(--muted);margin-bottom:4px}
.bs-ticks{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:4px}
.bs-tickone{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line);
  border-radius:999px;padding:4px 10px 4px 8px;font-size:12.5px;cursor:pointer;background:var(--panel)}
.bs-tickone.on{border-color:var(--blue);background:var(--blue-lt,rgba(0,78,254,.07));font-weight:600}
.bs-tickone input{margin:0}

/* ---- WHO IS IN THE AREA (0228, v3.609.0) --------------------------------------------------
   Darren: *"potentially change the view so that that person is highlighted."* Highlighted, and
   nothing more: the nearest row gets a green edge and a tint, and every row stays a live control
   that ticks that consultant. Deliberately NOT a traffic light across the whole list — a red row
   would read as "do not send this person", and the closest consultant to a Melbourne job may be
   three hours away and still be the right answer. Only a flight is dimmed, and only because a
   flight is a different kind of decision rather than a worse one. */
.bs-near{display:block}
.bs-nearlist{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:4px}
.bs-nearone{display:inline-flex;align-items:baseline;gap:7px;border:1px solid var(--line);
  border-radius:999px;padding:4px 12px;font-size:12.5px;cursor:pointer;background:var(--panel);
  color:inherit;text-align:left;font:inherit;font-size:12.5px}
.bs-nearone:hover{border-color:var(--blue)}
.bs-nearone.on{border-color:var(--blue);background:var(--blue-lt,rgba(0,78,254,.07))}
.bs-nearone b{font-weight:600}
.bs-nearkm{color:var(--muted);font-size:11.5px;font-variant-numeric:tabular-nums}
/* THE NEAREST, and it is a border and a tint rather than a badge saying "BEST" — a label that
   states a conclusion invites somebody to stop reading the other rows. */
.bs-nearone.best{border-color:var(--good,#137337);
  background:color-mix(in srgb,var(--good,#137337) 8%,var(--panel));font-weight:600}
.bs-nearone.best .bs-nearkm{color:var(--good,#137337)}
.bs-nearone.far{opacity:.72}
.bs-nearone.far .bs-nearkm{font-style:italic}

/* ---- MOVING A BOOKING BY DRAGGING IT (0231, v3.610.0) --------------------------------------
   Darren: *"If we're worried about accidentally moving something there can be a button called
   editing mode."* So NOTHING here is live until the host carries `bs-mg-on` — a grid that is not in
   editing mode gets no grab cursor, no hover affordance and no resize grip, because an affordance
   that appears on a screen where it does nothing is worse than none. */
.bs-mg-on [data-bs-move]{cursor:grab}
.bs-mg-on [data-bs-move]:hover{outline:1px solid var(--blue);outline-offset:1px}
/* THE FOOT OF A BLOCK IS ITS LENGTH — *"we might want to shorten four hour into a two hour"*.
   A REAL NODE, not a strip of the block's own bottom edge: the first cut was an invisible `::after`
   with a geometry test behind it, and driving it in a browser showed pointerdown never reaching the
   block in those last pixels. A handle can be hit-tested, named and SEEN — and an invisible
   affordance is one nobody discovers, which is its own argument. Hidden until editing is on, so a
   screen that is only being read carries none of this furniture. */
.bs-mg-grip{display:none}
.bs-mg-on .bs-mg-grip{display:block;position:absolute;left:50%;transform:translateX(-50%);
  bottom:2px;width:34px;height:8px;border-radius:999px;cursor:ns-resize;
  background:color-mix(in srgb,var(--ink,#111) 28%,transparent)}
.bs-mg-on [data-bs-move]:hover .bs-mg-grip{background:var(--blue)}
html[data-theme="dark"] .bs-mg-on .bs-mg-grip{
  background:color-mix(in srgb,#fff 34%,transparent)}
.bs-mg-dragging{cursor:grabbing}
.bs-mg-dragging [data-bs-move]{cursor:grabbing}
/* THE ONE BEING DRAGGED STAYS PUT AND FADES. Moving the real block would make the ghost redundant
   and would leave a hole where it was — and the hole is the thing that tells you where it came
   from while you are deciding. */
.bs-mg-lifted{opacity:.35}
/* THE COLUMN IT WOULD LAND IN. Whole-column, because "which person" is half of what a drop decides
   and a ghost inside one column looks much like a ghost inside the next. */
.bs-mg-over{background:color-mix(in srgb,var(--blue) 7%,transparent)}
.bs-mg-ghost{position:absolute;z-index:6;pointer-events:none;border-radius:6px;
  border:2px dashed var(--blue);background:color-mix(in srgb,var(--blue) 12%,transparent)}
.bs-mg-ghost.bs-mg-resize{border-style:solid}
/* THE SENTENCE, which is the part that matters on the phone: somebody reading a new time back to a
   customer is listening, not looking. Above the ghost, never wider than it needs, never wrapping
   into the block it describes. */
.bs-mg-say{position:absolute;z-index:7;pointer-events:none;white-space:nowrap;
  font-size:12px;font-weight:600;padding:3px 8px;border-radius:999px;
  background:var(--ink,#111);color:var(--panel,#fff);box-shadow:0 2px 8px rgba(0,0,0,.25)}
html[data-theme="dark"] .bs-mg-say{background:var(--panel);color:var(--ink);
  border:1px solid var(--line)}
/* The toggle itself. Plain, and it says which state it is in rather than which state it would go
   to — a button labelled "Editing mode" that might be on or off is a button nobody trusts. */
.bs-editmode{display:inline-flex;align-items:center;gap:6px}
.bs-editmode.on{border-color:var(--blue);background:var(--blue-lt,rgba(0,78,254,.07));
  font-weight:600}

@media (max-width:900px){
  /* The panels keep their shape and scroll sideways — squeezing the day columns instead would make
     every block unreadable, which is the whole point of the scroll. Only the labels give ground. */
  .bs-bands{gap:10px}
  .bs-byday{gap:8px}
  .bs-daycols{grid-auto-columns:minmax(72px,104px)}
}
/* The width slider, in the same one-row control bar as the clock (v3.563.0). */
.bs-colw{display:inline-flex;align-items:center;gap:6px;font-size:12px}
.bs-colw input[type=range]{width:120px;accent-color:var(--blue,#004EFE)}
.bs-colwnum{font-variant-numeric:tabular-nums;min-width:34px;text-align:right}
.bs-key{display:flex;flex-wrap:wrap;gap:10px;margin:8px 0 0;font-size:12px;color:var(--muted)}
.bs-k{display:inline-flex;align-items:center;gap:5px}
.bs-k::before{content:"";width:12px;height:12px;border-radius:3px;border:1px solid var(--line)}
.bs-kfree::before{border-color:var(--ok);background:var(--green-soft,rgba(30,140,80,.18))}
.bs-kbusy::before{background:repeating-linear-gradient(135deg,var(--grey-light) 0 4px,transparent 4px 8px)}
.bs-kunc::before{border-color:var(--warn,#b26a00);border-style:dashed}
.bs-kpick::before{border-color:var(--blue);border-width:2px;background:var(--blue-soft,rgba(0,78,254,.12))}
/* ---- AND A FIFTH, FOR THE ROAD (v3.491.0) ---------------------------------------------------
   A block the drive rules out is unavailable like a taken one, and it is unavailable for a
   completely different reason — so it must not look identical to somebody else's booking. Taken is
   grey stripes; too far is a solid muted red with no stripes at all, because there is no booking
   in it to hint at. `.bs-tight` is not a refusal: it stays pickable and carries a warm edge to say
   "possible, but only just", with the minutes in its own tooltip. */
.bs-far{border-color:var(--bad,#b3261e);background:var(--red-soft,rgba(179,38,30,.09));
  color:var(--muted)}
.bs-tight{border-color:var(--warn,#b26a00);border-width:2px}
.bs-kfar::before{border-color:var(--bad,#b3261e);background:var(--red-soft,rgba(179,38,30,.14))}
/* What the travel check actually did, above the grid. Quieter than `.bs-warn`: on an onsite grid
   this line is always there, and a permanent orange banner is a banner people stop reading. */
.bs-travel{margin:0 0 8px;padding:7px 10px;border-radius:var(--r);background:var(--panel-2,rgba(0,0,0,.03));
  border-left:3px solid var(--blue);font-size:12.5px;color:var(--ink)}
/* The booking side. */
.bs-form{display:flex;flex-direction:column;gap:10px}
.bs-f{display:flex;flex-direction:column;gap:3px;font-size:12px;color:var(--muted)}
.bs-f>span{font-weight:600}
/* A field that takes the whole column rather than sitting in the pair grid. Its own class rather
   than an inline style, so the house rule that every markup class has a rule stays true. */
.bs-wide{grid-column:1/-1}
.bs-f input,.bs-f select{padding:7px 9px;border:1px solid var(--line);border-radius:8px;font:inherit;
  background:var(--panel);color:inherit}
.bs-f .hint{font-size:11.5px;line-height:1.35}
.bs-picked{border-top:1px solid var(--line);padding-top:10px}
.bs-picked h4{margin:0 0 6px;font-size:13px;display:flex;align-items:center;gap:6px}
.bs-pcount{font-weight:800;font-variant-numeric:tabular-nums;color:var(--blue-ink)}
.bs-plist{margin:0;padding-left:20px;display:flex;flex-direction:column;gap:6px}
/* ---- THREE FACTS PER ROW (v3.533.0) --------------------------------------------------------
   *"We need the consultants name next to each booking too"* and *"it should say onsite / remote /
   offline next to all the bookings."* A grid rather than space-between: two rows at the same time
   with different consultants have to line their names up, or the pair reads as a duplicate. The
   when takes the room it needs; the name takes what is left; the kind and the button are fixed. */
.bs-plist li{display:grid;grid-template-columns:minmax(0,auto) minmax(0,1fr) auto auto auto;
  align-items:center;gap:8px}
/* v3.1153.0 — the ↻ "Suggest another time" button: one glyph wide, so the row keeps its line. */
.bs-plist [data-bs-another]{padding:2px 7px;min-width:0}
.bs-pwhen{font-size:12.5px}
.bs-pwho{font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 0215 (v3.617.0) — BACK TO A CAPTION, and 0195's select is gone. One booking is one kind now,
   because one booking has to be one price to have one quote, so the row STATES the kind rather than
   offering to change it. `.bs-pkind` is kept for the select that older markup may still carry.
   `.bs-pkind-odd` takes the whole width on its own line: it is a sentence, not a label, and a
   sentence squeezed into a 150px column is unreadable at exactly the moment it matters. */
.bs-pkind{font-size:11px;padding:2px 4px;max-width:150px}
.bs-podd .bs-pkind{border-color:var(--blue);color:var(--blue-ink);font-weight:600}
.bs-pkind-at{font-size:11px;font-weight:600;color:var(--muted);white-space:nowrap}
/* MEASURED IN BOTH THEMES, and the first attempt failed one of them: `var(--blue)` here read 3.94:1
   on the dark panel at 11px bold, which is NORMAL text and needs 4.5. It was also the wrong colour
   for the meaning — this caption marks the row that disagrees with its booking, which is the same
   warning the sentence under it carries, so it takes the same ink rather than a second signal. */
.bs-podd .bs-pkind-at{color:var(--warn)}
.bs-pkind-odd{grid-column:1/-1;font-size:11.5px;font-weight:600;color:var(--warn-ink,#8a5300);
  background:var(--warn-wash,#fff3cd);border-left:3px solid var(--warn,#ffc107);
  padding:4px 8px;border-radius:4px}
/* 0198 (v3.550.0) — AND WHICH SITE, on its own line under the row. MEASURED: added as a fifth
   column beside the other four it made the picked list wider than the panel it sits in (two rows
   overflowing, the whole page scrolling sideways). A site name is the longest thing on the row and
   an address is longer still, so it gets the full width and the four short facts keep their grid.
   `grid-column:1/-1` rather than a wrap, because a grid does not wrap. */
/* 0257 (v3.625.0) — 0198's per-date site select is gone (the site belongs to the booking and is
   chosen at the top of the sheet), and it was the element taking a row of its own that pushed
   "Take it off" out of line with the date beside it. The rule is kept for older markup only. */
.bs-psite{grid-column:1 / 4;justify-self:start;max-width:100%;font-size:11px;padding:2px 4px}
.bs-ready{margin:8px 0 0;font-size:12px}
.bs-bad{color:var(--danger);font-weight:600}
.bs-acts{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end;border-top:1px solid var(--line);
  padding-top:10px}
.bs-msg{min-height:18px;font-size:12.5px}
@media(max-width:900px){
  .bs-wrap{grid-template-columns:1fr}
  .bs-right{border-top:1px solid var(--line);padding-top:12px}
  .bs-acts{justify-content:stretch}
  .bs-acts .btn{flex:1 1 auto;justify-content:center}
}
.ib-row.ib-red .ib-kind{color:var(--danger)}
/* The sentence that says why, on the row, in the meta line. Its own colour so the reason reads as
   part of the mark rather than as more timestamp. */
.ib-row.ib-red .ib-why{color:var(--danger);font-weight:600}
.ib-row.ib-amber .ib-why{color:var(--warn,#b26a00);font-weight:600}
/* ---- ON A PHONE THE ROW STACKS (v3.458.0) ---------------------------------------------------
   Measured at 420px: the three-column grid left the MESSAGE about 150px wide — "Rod / Electrical"
   on two lines, "Yeah / gday it …" on two more — while three buttons stacked down the right. The
   row was readable at 1440 and unusable on a phone, which is where somebody clearing the inbox on
   the way to a job actually reads it.

   Two columns and a third row: the icon and the message get the full width, and the actions sit
   under them spanning both, side by side, where there is room for their labels. */
@media (max-width:600px){
  .ib-row{grid-template-columns:auto 1fr;row-gap:8px}
  .ib-actions{grid-column:1 / -1;justify-content:flex-start;flex-wrap:wrap}
  .ib-media{grid-column:1 / -1}
  /* FOUR lines here rather than three: the column is narrower, so three is barely a sentence, and
     this is the screen where opening each one costs the most. */
  .ib-what{-webkit-line-clamp:4}
}
.ib-filters{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin:0 0 10px}
/* comms stats */
.cs-figs{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));margin:0 0 12px}
.cs-fig{border:1px solid var(--line);border-radius:10px;padding:10px 12px;background:var(--panel)}
.cs-fig b{display:block;font-size:1.6rem;line-height:1.1}
.cs-fig span{font-size:.8rem;color:var(--muted)}
.cs-fig.cs-bad b{color:var(--danger)}
/* 0262 — where they came from: the channel table, the tracking-number rows, the record's select */
.where-grid td,.where-grid th{text-align:right}.where-grid td:first-child,.where-grid th:first-child{text-align:left}
.where-grid tr.where-unknown td{font-style:italic}
.where-num{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin:0 0 8px}
.where-map{margin-top:12px}.enq-channel{display:block;margin:6px 0}
.cs-chart{width:100%;height:auto;display:block}
/* ---------- the week: one bar, then the grid (v3.384.0) ---------- */
.wk-barwrap{padding:10px 14px}
.wk-bar{display:flex;flex-wrap:wrap;align-items:center;gap:8px 16px}
.wk-nav{display:flex;align-items:center;gap:6px}
.wk-range{margin-left:6px;white-space:nowrap}
.wk-bar .wk-weekend-ctl{margin:0}
.wk-bar .wk-who{margin:0}
.wk-notes p.hint{margin:4px 0}
.wk-book b,.wk-own b{white-space:nowrap}
/* ================================================================================================
   TWO ARRANGEMENTS OF ONE WEEK (v3.501.0)
   Darren: *"we either need to show each consultant's calendar one under the other, similar to how
   they are now, or… all the consultants Mondays together, then all the consultants on Tuesdays…
   and as you scroll across, you scroll across one group of consultants at a time."*
   ================================================================================================ */
.wk-views{display:inline-flex;gap:4px}
/* A COLOUR PER CONSULTANT, in both arrangements, so switching does not mean re-learning who is who.
   A dot and a left rule — never the text colour and never a full background: eight consultants of
   eight backgrounds is a screen nobody can read, and two of the palette's eight are too light for
   white text and two too dark for black. */
.wk-dot{display:inline-block;width:9px;height:9px;border-radius:50%;
  background:var(--who,var(--muted));flex:0 0 auto}
.wk-rowwho{border-left:3px solid var(--who,var(--line))}
.wk-rowwho .wk-dot{margin-right:6px;vertical-align:baseline}
/* ---- BY DAY: a group per day, a column per consultant, snapped ------------------------------
   `scroll-snap-type:x mandatory` with `scroll-snap-align:start` on the groups is the "one group of
   consultants at a time" part: a swipe lands on a whole day rather than halfway between two. */
.wk-bdscroll{display:flex;gap:14px;overflow-x:auto;scroll-snap-type:x mandatory;
  padding-bottom:6px}
/* AS WIDE AS ITS PEOPLE, capped at the viewport. `--n` comes from the roster; 172px is the 150px
   column floor plus its gap, and 22px is this box's own padding and border — `box-sizing` is not
   global in this file, and without it a group measured 351px inside a 320px scroller (MEASURED in
   Chromium at 390px, v3.501.0). */
.wk-dg{scroll-snap-align:start;flex:0 0 auto;box-sizing:border-box;
  min-width:min(100%,calc(var(--n,3) * 172px + 22px));max-width:100%;
  border:1px solid var(--line);border-radius:var(--r);padding:8px 10px;background:var(--panel)}
.wk-dg.wk-weekend{background:var(--panel-2)}
.wk-dghead{margin:0 0 6px;font-size:13.5px}
/* Inside a day: one column per consultant. `auto-fit` with a 150px floor, so two consultants take
   half each and six scroll within the group rather than squeezing to unreadable. */
.wk-dgcols{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px}
.wk-dgcol{min-width:0;border-top:3px solid var(--who,var(--line));padding-top:6px}
.wk-dgwho{display:flex;align-items:center;gap:6px;font-size:12.5px;margin-bottom:4px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Today's group, and the weekend's, marked the same way as the table's columns. */
.wk-dg.wk-today{box-shadow:inset 0 3px 0 var(--orange)}
.wk-dg .wk-book,.wk-dg .wk-own{margin-bottom:4px}
.wk-dg .wk-empty{color:var(--muted);font-size:12.5px}
@media (max-width:640px){
  /* On a phone one day fills the viewport, which is what the snapping is for — and the column
     floor comes down so TWO consultants still fit side by side instead of one per row. MEASURED at
     390px: 298px of content, so 150px allowed one column and 120px allows two at 145px each. */
  .wk-dg{min-width:100%}
  .wk-dgcols{grid-template-columns:repeat(auto-fit,minmax(120px,1fr))}
}
/* ---------- support desk (v3.384.0) ---------- */
.sup-ok{color:var(--ok,#1a7f37);font-weight:700}
.sup-asked{color:var(--warn);font-weight:600}
.sup-here{border-left:4px solid var(--accent)}
.tipbar.supask{background:#fff7e6;border-color:#f0c36d}
.tipbar.supask .tb-ctl .btn{margin-left:6px}
html[data-theme="dark"] .tipbar.supask{background:#3a2e12;border-color:#7a5a1a}
/* ---------- invoice lines: description wide, amount small, picker compact (v3.384.0) ---------- */
.inv-lines{display:flex;flex-direction:column;gap:8px;margin:6px 0}
.inv-line{border:1px solid var(--line);border-radius:10px;padding:8px 10px;background:var(--panel)}
.inv-line-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.inv-line-row .inv-label{flex:1 1 260px;min-width:200px}
.inv-amt-wrap,.inv-qty-wrap{display:inline-flex;align-items:center;gap:4px}
.inv-amt-wrap .inv-amt{width:110px}
.inv-qty-wrap .inv-qty{width:64px}
.inv-line-from{margin:4px 0 0}
.inv-cat{margin-top:8px;border-top:1px dashed var(--line);padding-top:8px}
.inv-cat-q{width:100%;max-width:360px;margin-bottom:6px}
.inv-cat-list{display:flex;flex-direction:column;gap:2px;max-height:240px;overflow:auto}
.inv-cat-item{display:flex;justify-content:space-between;gap:12px;text-align:left;background:none;border:0;
  padding:6px 8px;border-radius:6px;cursor:pointer;font:inherit;color:var(--ink)}
.inv-cat-item:hover,.inv-cat-item:focus-visible{background:var(--panel-2,rgba(0,78,254,.08));outline:none}
.inv-cat-item[hidden]{display:none}
/* ---------- analytics as figures and charts (v3.384.0) ---------- */
.an-two{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));margin-top:8px}
.an-why{margin-top:12px}
.an-why summary{display:inline-block;list-style:none;cursor:pointer}
.an-why summary::-webkit-details-marker{display:none}
.secret-health{margin-top:10px}
/* ---------- the Easy Start adopts the real screens (v3.385.0) ---------- */
.wiz-adopted{margin-top:10px}
.wiz-adopted .stepbar{display:none}
.wiz-check.unknown{color:var(--warn);border-style:dashed}
.wiz-veh{display:flex;flex-direction:column;gap:6px;margin:8px 0}
.wiz-veh-row{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--panel);cursor:pointer}
.wiz-veh-lab{flex:1}
.wiz-veh-cost{color:var(--muted);font-size:.9rem}
/* The vehicle tick is a checkbox, and the wizard-body input rule above would stretch it. */
.wiz-veh-row input[type=checkbox]{width:18px;height:18px;max-width:18px;flex:0 0 auto;padding:0;margin:0}

/* ================================================================================================
   A ROW THAT OPENS, AND THE ROW YOU LAND ON (v3.529.0)
   ================================================================================================
   Darren, 9 September 2026: *"in the quotes list, we should be able to click and open the quotes,
   same with invoices, same with customer, site, task."*

   `.enq-rowgo` is a row that goes somewhere: the cursor and the hover say so before the click, and
   the focus ring is the same one every other control on the record uses — the rows carry
   `role="link"` and `tabindex="0"`, so a keyboard user has to be able to SEE where they are.

   `.row-flash` is the other end of the trip: the destination screen scrolls the row into view and
   runs this once, so nobody has to find the line they were already looking at. It fades to nothing
   rather than staying tinted, because a permanently highlighted row reads as a state of the record.
   `prefers-reduced-motion` gets the tint without the pulse. */
.enq-rowgo{cursor:pointer}
.enq-rowgo:hover{background:var(--grey-light)}
.enq-rowgo:focus-visible{outline:2px solid var(--blue);outline-offset:2px;border-radius:6px}
/* The name at the top of the record, and the site's name: a door, drawn as the heading it replaced
   rather than as a link in the middle of a sentence. */
.enq-openco{font:inherit;color:inherit;text-align:left;padding:0}
.enq-openco:hover,.enq-openco:focus-visible{color:var(--blue-ink);text-decoration:underline}
@keyframes rowFlash{
  0%{background:var(--blue-soft,rgba(0,78,254,.28))}
  60%{background:var(--blue-soft,rgba(0,78,254,.18))}
  100%{background:transparent}
}
.row-flash{animation:rowFlash 2.4s ease-out 1}
@media(prefers-reduced-motion:reduce){
  .row-flash{animation:none;background:var(--blue-soft,rgba(0,78,254,.18))}
}

/* ================================================================================================
   THE REAL LENGTH OF THE REAL THING (v3.554.0)
   ================================================================================================
   Darren: *"if we know what's there already (and it's a 1-hour, a 3-hour, or a 5-hour), then we
   should have a block that is exactly the size, from start to finish, of the time. If we need to
   stretch it vertically more, that's fine too."*

   So the vertical axis is MINUTES, at `--bs-px` per minute, and every block's height is its own
   duration. `--bs-span` is how many minutes the day covers; the column is that tall and nothing is
   compressed to fit. Overlapping entries share the width by lane (see booksheet.laneOut). */
/* ================================================================================================
   EVERY VIEW IS BY THE CLOCK (v3.556.0)
   ================================================================================================
   Darren, 9 September 2026: *"I would like to be able to see, on every view, how long visually
   everything is booked in for… If something is scheduled from 6:30 to 8:30, or let's say 6:30 to
   10:30, it should be one solid block, no matter which view you're on."*

   ---- EVERY INLINE CUSTOM PROPERTY IS DECLARED HERE WITH A DEFAULT ---------------------------
   These are set per element by the renderer (`--bs-top`, `--bs-mins`, `--bs-lane`, `--bs-lanes`,
   `--bs-at`, `--bs-span`, `--bs-con`). A bare `var()` that nothing declares resolves to NOTHING —
   the rule `test/uiaudit.test.mjs` polices — so each has a default that draws something sane if the
   inline value is ever missing: a nine-hour day, the top of the axis, one lane, the line colour.
   That is not decoration; it is the difference between a column with a gap in it and a column that
   silently collapses to nothing. */
/* ---- AND A DEFAULT MUST NOT BE A DECLARATION ON THE CHILD (v3.556.5) ------------------------
   FOUND BY MEASURING, not by reading, and it is the fault behind the screenshot that started this
   release. `.bs-tgcol,.bs-tgaxis{--bs-span:540}` looked like the same harmless default as the rest
   of this block. It is not: `--bs-span` is the ONLY one of these set on an ANCESTOR — the renderer
   writes it inline on `.bs-tg`, once, so one axis serves every band — and a declaration on the
   element beats a value inherited from its parent. So every column and every ruler was 540 minutes
   tall (486px at 0.9px/min) whatever window the grid had actually computed.

   MEASURED: `--bs-span` inline was 840. The columns were 486px. A 14-hour day drawn 9 hours tall.

   Three visible symptoms, all of which were being treated as separate complaints:
     * `.bs-tgwrap` scrolled VERTICALLY at all. Nobody designed a vertical scroller here; the box
       was simply too short for its own contents.
     * Anything after the ninth hour was placed past the bottom of the column — `--bs-top` is
       measured from the real window start, so a 5pm block on a 4am-6pm axis wants 702px in a 486px
       box.
     * The orange ring round today had its bottom edge in the middle of Thursday afternoon, which
       is what Darren was looking at when he said *"I don't understand what this silly orange
       outline is."* The ring was the messenger.

   So the default moves INTO the calc as a fallback, where it cannot shadow an inherited value.
   Same lesson as `--bs-con` on `.bs-tghead` below, found the same afternoon. */
.bs-tg{--bs-px:0.9px;--bs-span:540}
.bs-tghour,.bs-tgline{--bs-at:0}
.bs-tgev,.bs-clst{--bs-top:0;--bs-mins:30;--bs-lane:0;--bs-lanes:1}
.bs-tgday,.bs-tgev,.bs-conswatch,.bs-daycolhead{--bs-con:var(--line)}
/* ---- A BAND IS AS TALL AS IT NEEDS, UP TO A CEILING (v3.556.5) -----------------------------
   Once the span bug above was fixed, a fourteen-hour day drew its full 756px — correct, and three
   consultants of it is a 2,400px page. So the band gets a ceiling and scrolls inside it, which is
   also what gives the sticky heads something to stick TO: `position:sticky` pins against the
   nearest scrolling ancestor, so a box that never overflows is a box where the dates would scroll
   away with the page instead.

   `max-height`, not `height`: a short day still draws short, with no scrollbar and no empty band
   under it. `--bs-bandmax` so a screen with one band can give it more room than a screen with four
   without touching this rule. */
/* ---- THE LAST BLOCK OF THE DAY IS NOT UNDER THE SCROLLBAR (0231, v3.610.0) ----------------
   Found by driving a drag: `pointerdown` on the foot of a late-afternoon block never reached it,
   and `elementsFromPoint` at that y returned the grid rather than the block. `overflow-x:auto` with
   a `max-height` makes this a scroll container in both axes, and the horizontal scrollbar sits over
   the last ~11px of it. 8px of padding was not enough to clear it.
   It was never only a drag problem: the foot of a 4pm booking was being drawn under the scrollbar
   to READ as well, which is why the resize handle lives at `bottom:2px` and needs the room. */
.bs-tgwrap{--bs-bandmax:52vh;display:flex;gap:8px;overflow-x:auto;padding-bottom:20px;
  align-items:flex-start;max-height:var(--bs-bandmax)}
.bs-tgaxis{flex:0 0 54px;position:sticky;left:0;z-index:2;
  height:calc(var(--bs-span, 540) * var(--bs-px, 0.9px));margin-top:44px;background:var(--panel)}
/* ---- EVERY RULE THAT DOES THE ARITHMETIC CARRIES THE SCALE (fixed v3.566.0) ---------------
   `--bs-px` is declared on `.bs-tg` — the wrap — and `.bs-tgaxis`/`.bs-tgcol` have always written
   `var(--bs-px, 0.9px)` with the fallback. These two did not, and in the ONE place an axis is drawn
   outside a wrap (the by-weekday view, where one ruler serves four consultant columns) that made
   `top: calc(var(--bs-at) * var(--bs-px))` an invalid length — so every hour label collapsed to the
   top of the ruler, thirteen of them stacked on the same two pixels. That is the second half of
   what Darren photographed, and it has been there since the axis was hoisted out in v3.556.0.
   The fallback is the same 0.9px the other rules already use, so nothing that inherits changes. */
.bs-tghour{position:absolute;left:0;right:4px;top:calc(var(--bs-at) * var(--bs-px, 0.9px));
  font-size:11px;color:var(--muted);text-align:right;line-height:1;
  font-variant-numeric:tabular-nums}
.bs-tgdays{display:flex;gap:8px}
/* ---- HOW WIDE A DAY IS, AS A NUMBER SOMEBODY CAN MOVE (v3.563.0) --------------------------
   Darren, 9 September 2026: *"we can make the width of the blocks narrower. I would also like to
   have a slider that you can slide to make the block width wider or narrower as well."*
   210px was the fixed answer and it is the right one for a single week with customer names on the
   blocks; it is the wrong one for five consultants side by side, where what you want is the shape
   of everybody's week at once. So the width is a variable with the old value as its default, and
   the slider sets it on the grid — no renderer changes, and a screen that never sets it draws
   exactly what it drew before. */
.bs-tgday{flex:0 0 var(--bs-colw,210px);min-width:var(--bs-colw,210px)}
/* ---- THE NORMAL WORKING DAY, BEHIND EVERYTHING (v3.563.0) --------------------------------
   *"Can you put a marker or a faint dotted line, or even the background, on the normal hours that
   each of us does, so we can easily see where the normal time starts (even though there will be
   bookings or appointments in our calendar that go outside of that)?"*

   THE BACKGROUND, not a line, and faint enough to read through: a booking drawn on top of it must
   still be legible, and a 6am start outside the band should look like what it is — outside the
   band — without the band shouting. The two edges carry a dotted rule because *"where the normal
   time starts"* is a specific instant somebody is looking for, and an edge between two washes is
   easy to lose against a block that happens to start there. */
.bs-tgwork{position:absolute;left:0;right:0;pointer-events:none;z-index:0;
  top:calc(var(--bs-top) * var(--bs-px));height:calc(var(--bs-mins) * var(--bs-px));
  background:var(--work-wash,rgba(0,78,254,.045));
  border-top:1px dotted var(--work-edge,rgba(0,78,254,.45));
  border-bottom:1px dotted var(--work-edge,rgba(0,78,254,.45))}
/* ---- THE DAYS AND DATES DO NOT SCROLL (v3.556.5) -------------------------------------------
   Darren, 9 September 2026: *"I think the dates and days should not scroll and should stay at the
   top."*

   MEASURED before changing anything: the head's top went from +69px to −131px on a 200px scroll of
   `.bs-tgwrap` — straight off the top of the box, so a diary scrolled to the afternoon was five
   unlabelled columns. (`.bs-tgwrap` scrolls VERTICALLY as well as sideways, and not by anyone's
   choice: `overflow-x:auto` computes the other axis from `visible` to `auto`, which is how a box
   nobody gave a height to ended up being the scroller.)

   The head sticks to the top of it, and the hour rail sticks to the LEFT of it — same idea, other
   axis, and the reason a wide grid scrolled sideways stops being readable. Both need an opaque
   background or the blocks show through as they pass under. The head outranks the rail (z 3 over 2)
   so the rail's hour labels slide UNDER the dates rather than over them; they are siblings in one
   stacking context, so the numbers decide it. The rail keeps a positioned value either way — its
   hour labels are absolutely positioned against it. */
.bs-tghead{display:flex;flex-direction:column;align-items:center;gap:1px;height:44px;
  justify-content:center;font-size:13px;
  position:sticky;top:0;z-index:3;background:var(--panel);
  /* THE CONSULTANT'S COLOUR MOVES ONTO THE STICKY EDGE. It used to be only the day box's top
     border, which scrolls away — so a band scrolled down lost the one mark saying whose week it
     was. `--bs-con` is set inline on `.bs-tgday` and custom properties inherit, so this reads the
     same value with no second declaration.

     AND THE FALLBACK IS THE POINT, not belt-and-braces. Declaring `--bs-con` on the head as well
     would have looked tidier and broken it: a declaration on the ELEMENT beats a value inherited
     from its parent, so every head would have painted the default line colour and the whole
     colour-per-consultant feature would have quietly gone. `var(--bs-con, var(--line))` inherits
     the real colour where there is one and falls back where there is not. */
  border-bottom:2px solid var(--bs-con, var(--line))}
.bs-tgcol{position:relative;height:calc(var(--bs-span, 540) * var(--bs-px, 0.9px));
  border-left:1px solid var(--line)}
.bs-tgline{position:absolute;left:0;right:0;top:calc(var(--bs-at) * var(--bs-px, 0.9px));
  border-top:1px dashed var(--line);opacity:.5}
/* A BLOCK IS ITS OWN LENGTH. `--bs-mins` is the duration, so a five-hour booking is five times the
   height of a one-hour one — which is the whole request. Fifteen minutes is the floor so a short
   entry is still readable; its words still say the real time. */
.bs-tgev .bs-bk{font-size:9px;padding:0 3px}
.bs-tgev{position:absolute;top:calc(var(--bs-top) * var(--bs-px));
  height:calc(var(--bs-mins) * var(--bs-px) - 2px);
  left:calc(var(--bs-lane) / var(--bs-lanes) * 100%);
  width:calc(100% / var(--bs-lanes) - 3px);
  display:flex;flex-direction:column;align-items:flex-start;gap:1px;overflow:hidden;
  border:1px solid var(--line);border-radius:6px;padding:3px 5px;text-align:left;
  font:inherit;font-size:10.5px;line-height:1.15;background:var(--panel);color:inherit}
button.bs-tgev{cursor:pointer}
.bs-tgev-held{border-color:var(--orange,#FF6D00);
  background:repeating-linear-gradient(135deg,rgba(255,109,0,.14) 0 5px,transparent 5px 10px)}
.bs-tgev-unpaid{border-color:var(--blue,#004EFE);background:var(--blue-wash,rgba(0,78,254,.10))}
.bs-tgev-paid{border-color:var(--ok,#1E8C50);background:var(--green-soft,rgba(30,140,80,.10))}
.bs-tgev-theirs{border-color:var(--line);background:repeating-linear-gradient(135deg,
  var(--grey-light) 0 5px,transparent 5px 10px);color:var(--muted)}
/* ---- v3.1150.0 (8 October 2026) — THE OUTLOOK CATEGORY'S COLOUR ------------------------------
   Jarryd: *"sync the colour of the blocks to match the category colours in Outlook… Currently all
   calendar blocks look very similar."* The colour itself comes from the data (`--bs-cat`, and
   `--bs-catbg` its wash), never from this sheet. Somebody's own diary entry takes it as its face; our
   booking keeps its state face (tentative / unpaid / paid) and carries the category as a bar across
   the top. The consultant's colour stays on the left edge (`.bs-tgev-col`, later in the sheet). */
.bs-tgev-theirs.bs-tgev-cat{background:var(--bs-catbg, transparent);border-color:var(--bs-cat, var(--line));
  color:var(--ink)}
.bs-tgev-cat:not(.bs-tgev-theirs){box-shadow:inset 0 3px 0 var(--bs-cat, var(--line))}
.bs-catkey{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:8px 0 2px;font-size:12px}
.bs-catkeyh{font-weight:700;color:var(--muted);margin-right:2px}
.bs-catchip{display:inline-flex;align-items:center;gap:2px;padding:2px 8px;border-radius:999px;
  border:1px solid var(--bs-cat, var(--line));background:var(--bs-catbg, transparent);color:var(--ink)}
.bs-catkey .hint{flex-basis:100%}
.bs-caltitlenow{display:block;margin-top:3px;color:var(--muted)}
.bs-caltitlenow b{color:var(--ink);font-weight:600}
/* ---- 0205 — TRAVEL, AND IT HAS TO LOOK LIKE NEITHER (v3.562.0) -----------------------------
   Darren: *"block out the travel in our system and call it travel."* It is not a booking (nobody is
   being served) and it is not somebody's own diary entry (we put it there), so it gets neither
   face. A dashed edge says "held, but not a job"; the wash is deliberately weak because a week
   with six drives on it must still read as a week of work, not a week of driving.
   THE LEFT EDGE STILL CARRIES THE PERSON'S COLOUR through `.bs-tgev-col`, which is the whole point
   of the rail — in a combined grid the drive and the visit it serves should obviously be the same
   person's. */
.bs-tgev-travel{border-style:dashed;border-color:var(--muted,#6b6b6b);
  background:repeating-linear-gradient(135deg,rgba(120,120,120,.10) 0 6px,transparent 6px 12px);
  color:var(--muted)}
.bs-tgev-travel .bs-tgwhat{font-weight:600}
.bs-ktravel::before{border-color:var(--muted,#6b6b6b);border-style:dashed;
  background:repeating-linear-gradient(135deg,rgba(120,120,120,.14) 0 4px,transparent 4px 8px)}
.bs-tgwhen{font-weight:700;font-variant-numeric:tabular-nums;line-height:1.15;white-space:nowrap}
.bs-tgwhat{font-weight:700;line-height:1.2;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;max-width:100%}
.bs-tgwho{color:var(--muted);line-height:1.2;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;max-width:100%}

/* ---- SEVEN TICKS FOR THE WORKING WEEK (v3.556.0) -------------------------------------------
   *"like a tick box, an on/off, or a clock, so that the data can't be wrong."* Seven small targets
   in a row, wrapping on a phone; the label is the day and the tick is the answer. */
.edge-wdays{border:1px solid var(--line);border-radius:8px;padding:6px 10px 8px}
.edge-wdays legend{font-weight:700;font-size:13px;padding:0 4px}
.edge-daybox{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
.edge-day{display:inline-flex;align-items:center;gap:4px;font-size:12px;padding:3px 8px;
  border:1px solid var(--line);border-radius:999px;cursor:pointer;background:var(--panel)}
.edge-day:has(input:checked){border-color:var(--blue);font-weight:700;background:var(--blue-lt)}
.edge-day input{margin:0}

/* ---- CHOOSING A CONSULTANT'S COLOUR (0200, v3.556.0) ---------------------------------------
   *"we should be able to set a colour for each consultant… Possibly, in each user, we say
   'consultant can be scheduled,' and then we can pick a colour in each user settings."*
   Swatches rather than a text box (his own data-entry rule), each one carrying its NAME as well as
   its colour so the choice is readable without seeing it. The tick that selects one is the radio's
   own focus ring plus a filled border, because a coloured square with a coloured tick on it is the
   one thing this control cannot say clearly. */
.edge-colour{border:1px solid var(--line);border-radius:8px;padding:8px 10px;margin-top:10px}
.edge-colour legend{font-weight:700;font-size:13px;padding:0 4px}
.edge-swatches{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
.edge-swatch{display:inline-flex;align-items:center;gap:5px;font-size:12px;padding:3px 8px 3px 5px;
  border:2px solid var(--line);border-radius:999px;cursor:pointer;background:var(--panel)}
.edge-swatch:has(input:checked){border-color:var(--bs-con,var(--blue));font-weight:700}
.edge-swatch input{margin:0}
.edge-swatchdot{width:12px;height:12px;border-radius:3px;background:var(--bs-con);
  display:inline-block;flex:0 0 auto}
.edge-swatch-auto{--bs-con:var(--blue)}

/* ---- THE STARTS, BEHIND WHAT IS REALLY THERE (v3.556.0) -------------------------------------
   *"we can always schedule anything as every half an hour."* One button per candidate start, half
   an hour tall, laid down the column at its own minute. The layer fills the column so a click
   anywhere in a gap lands on the right rung; the layer ABOVE it (`.bs-clevs`) does not take the
   mouse except on its own blocks, so a booking still opens its enquiry and everything else picks. */
.bs-clstarts{position:absolute;inset:0}
.bs-clevs{position:absolute;inset:0;pointer-events:none}
.bs-clevs>*{pointer-events:auto}
/* ---- A FREE RUNG IS INVISIBLE UNTIL IT IS WANTED (v3.556.0) --------------------------------
   MEASURED, and the first draft was wrong: nineteen labelled dashed boxes per day per person is a
   wall of furniture with the actual diary lost inside it — Darren's whole complaint about the old
   grid, reintroduced from the other direction. A real diary shows what is ON it and lets you click
   empty space; the AXIS says what time that is, which is what an axis is for.
   So a rung is a hit target with no ink at all, and it announces itself — a tint, a border and its
   own time — on hover, on keyboard focus, and when it is on the booking. */
.bs-slot.bs-clst{position:absolute;left:0;right:0;width:auto;
  top:calc(var(--bs-top) * var(--bs-px));
  height:calc(var(--bs-mins) * var(--bs-px));min-height:0;
  padding:0 5px;border:1px solid transparent;border-radius:4px;background:transparent;
  justify-content:center;align-items:flex-start;font-size:10.5px}
.bs-slot.bs-clst .bs-clsttime{opacity:0;transition:opacity .12s ease}
.bs-slot.bs-clst:hover:not(:disabled),.bs-slot.bs-clst:focus-visible{z-index:3;
  border-color:var(--blue);border-style:dashed;background:var(--blue-lt)}
.bs-slot.bs-clst:hover:not(:disabled) .bs-clsttime,
.bs-slot.bs-clst:focus-visible .bs-clsttime{opacity:1}
/* ---- ON THE BOOKING (0238, v3.612.0) ------------------------------------------------------
   Darren, with a 9am and a 9:30am offer side by side: *"somehow it's doing a weird overlap with the
   orange circle... I like that it shows side by side. Keep that."*

   The side-by-side is the OFFER blocks and they lane correctly. What collided is this layer: a rung
   is the full width of the column, so a picked one drew its own orange box and its own time ACROSS
   a pair of half-width offer blocks that already said `9–10am` and `9:30–10:30am`.

   So a picked rung is no longer a box. It keeps its hit area — this is how a time is unpicked, and
   losing that would leave the list as the only way — and it keeps the tick, which is the one thing
   the offer block does not say. What goes is the border, the fill and the repeated time: three
   pieces of ink for a fact already drawn underneath it. `z-index` drops BELOW the offer blocks for
   the same reason, so the block is what you see and the rung is what you press where the block is
   not. */
.bs-slot.bs-clst.bs-on{border-color:transparent;background:transparent;z-index:1}
.bs-slot.bs-clst.bs-on .bs-tick{color:var(--orange,#FF6D00);font-weight:700;
  /* Right of the column and clear of the lane blocks' own top-right corner. */
  right:2px;top:0}
/* Hover still says which rung you are on, picked or not — it is the only affordance that says a
   press will take it OFF. */
.bs-slot.bs-clst.bs-on:hover:not(:disabled){border-color:var(--orange,#FF6D00);
  border-style:dashed;background:transparent;z-index:4}
/* NOTHING TO PRESS, NOTHING TO SEE. A day somebody does not work, an hour already taken, or a rung
   in the past: the block over it says what is there, and a disabled ghost adds nothing. */
.bs-slot.bs-clst:disabled{border-color:transparent;background:transparent;cursor:default}
.bs-slot.bs-clst:disabled .bs-clsttime{opacity:0}
.bs-clst .bs-tick{position:absolute;right:3px;top:1px;font-size:10px}
.bs-clnone{position:absolute;left:0;right:0;top:40%;margin:0;font-size:11.5px}

/* ---- AN OFFER IS DRAWN AT ITS REAL LENGTH TOO ----------------------------------------------
   *"if I start at 6 on a 4-hour block, it'll finish at 10. I could offer 6:30 to 10:30, and I could
   offer 7 to 11. There would be three overlapping tentative bookings."* Three picks half an hour
   apart are three duration-long blocks, side by side in their own lanes — which only reads as three
   offers if each one is the height of the work. Orange, dashed, and clearly not yet a booking. */
.bs-tgev-pick{border:2px dashed var(--orange,#FF6D00);
  background:repeating-linear-gradient(135deg,rgba(255,109,0,.20) 0 6px,transparent 6px 12px);
  z-index:2}
/* ---- 0241 — AND IT IS GLASS, EXCEPT FOR ITS OWN TIME (v3.626.0) ----------------------------
   Darren, 11 September 2026: *"I'm trying to create a 10am-12pm option but it selects the existing
   one and doesn't let me book another option. How do we fix the UI so we can select another time
   under the existing one?"*

   An offer is drawn at the LENGTH OF THE WORK, in the layer above the start rungs — so a two-hour
   offer owned every click in those two hours, and the only thing it does with one is take itself
   off. The four half-hours under it could not be offered at all.

   `pointer-events:none` hands those clicks back to the rung underneath, and the time at the top of
   the block stays live as the offer's own handle — so taking one off is still one click ON the
   thing being taken off, and offering 10am under a 9-11am offer is one click where 10am is. The
   selector is `.bs-clevs>.bs-tgev-pick` so it outweighs `.bs-clevs>*`, which turns the pointer on
   for every block in the layer. */
.bs-clevs>.bs-tgev-pick{pointer-events:none}
.bs-tgev-pick .bs-tgoff{pointer-events:auto;cursor:pointer;border-radius:3px;padding:0 2px;
  margin:-1px -2px}
.bs-tgev-pick .bs-tgoff:hover{background:var(--orange,#FF6D00);color:#fff}

/* ---- WHOSE COLUMN, WHOSE BLOCK (v3.556.0) --------------------------------------------------
   *"even if the column or the outside border is where we see the colour… that will at least make it
   much easier to determine who's who in a combined grid."* A rail down the day and a thick left
   edge on the block. NEVER the background: the background already carries tentative / unpaid /
   paid / theirs, which is the more important fact and cannot be given up for identity. */
.bs-tgday{border-top:3px solid var(--bs-con);border-radius:3px 3px 0 0}
.bs-tgev-col{border-left:4px solid var(--bs-con)}
.bs-conswatch{display:inline-block;width:12px;height:12px;border-radius:3px;
  background:var(--bs-con);vertical-align:middle;margin-left:6px;flex:0 0 auto}
.bs-daycolhead{border-bottom:3px solid var(--bs-con);padding-bottom:2px}

/* ---- TODAY, IN THE MIDDLE — A WASH AND A PILL, NEVER A BOX (v3.556.5) ----------------------
   Darren, 9 September 2026, on a screenshot of the ring v3.556.0 shipped: *"I don't understand what
   this silly orange outline is. Is that supposed to be today? I think you need to remove it and do
   something better than that."*

   HE IS RIGHT, AND THE REASON IS GEOMETRY. The ring was drawn on `.bs-tgcol` — the column, which is
   `--bs-span` MINUTES tall and lives inside a box that scrolls. So its bottom edge was never at the
   bottom of anything a person could see: it sat wherever the scroll happened to be, which is the
   dashed orange line cutting across the middle of Thursday afternoon in his screenshot. A closed
   shape drawn around content taller than its viewport cannot be closed on screen. Making it a
   dashed border, or a thinner one, or a different orange would all have shipped the same fault.

   SO: NOTHING WITH A BOTTOM EDGE. Two marks, neither of which can be clipped —
     1. a wash down the whole column, which has no edge to land in the wrong place, and
     2. the date itself in an orange pill in the head, which now STICKS to the top (below), so the
        one mark that names the day is the one mark that is always on screen.
   The weekday goes heavier too. Not WIDER: a wider column would push the days either side out of
   alignment across three stacked bands, which is the fault v3.553.0 fixed.
   The centring itself is `booksheet.centreToday`, run after the draw. */
.bs-tgcol.bs-tgtoday{background:rgba(255,109,0,.06)}
.bs-tgdaytoday>.bs-tghead>b{font-weight:800}
.bs-tgdaytoday>.bs-tghead>.hint{background:var(--orange,#FF6D00);color:#fff;font-weight:700;
  border-radius:999px;padding:1px 8px;letter-spacing:.02em}
/* A COLUMN WITH NO HEAD (the by-weekday view supplies its own) keeps the axis alignment: the head
   is 44px tall in every other view, and a column that skips it would start 44px higher. */
.bs-tgdaybare>.bs-tgcol{margin-top:0}
.bs-tgdaybare{flex:0 0 auto;min-width:120px}
.bs-daycol .bs-tgwrap{gap:0}
.bs-daycol .bs-tgday{min-width:110px;flex:1 1 auto}

/* The urgent mark in the dark theme: the same warm amber, taken down to a tone that does not glow
   off a dark panel, with the text lifted rather than the ground. */
:root{--urg-bg:#FFF1E0;--urg-ink:#9A4B00;--urg-line:#F0C48A}
html[data-theme="dark"]{--urg-bg:rgba(240,150,50,.16);--urg-ink:#FFC684;--urg-line:rgba(240,150,50,.45)}

/* ---- 0214 (v3.618.0) — WAITING ON OUR APPROVAL, ON THE QUOTE ROW --------------------------
   It sits inside a `.hint` stack under the customer's status, so without a colour of its own it
   reads as one more grey line in a column of grey lines — and this is the one that says the chain
   has stopped. The warning ink, which is the same signal the "Quotes to approve" tile uses and the
   same one the booking sheet's disagreement line uses. Measured in both themes. */
.q-needsok{color:var(--warn);font-weight:600}

/* ---- 0250 (v3.623.0) — HOW WIDE THE IGNORE RULE GOES ----------------------------------------
   Under the button it belongs to, not beside it: a scope control floating among six buttons belongs
   to none of them. Full width of the actions column and indented under the wide button above it, so
   the pairing reads as one thing with a setting rather than a seventh option. */
/* BOTH LAYOUTS. `.ia-acts` is a flex row in the list and a two-column GRID inside `.ia-decide`
   (styles.css), so a full-width child needs `grid-column` for one and `flex-basis` for the other.
   Measured in both. */
.ia-hush{grid-column:1/-1;flex:1 0 100%;display:flex;align-items:center;gap:6px;margin-top:2px;
  font-size:11.5px}
.ia-hush select{font-size:11.5px;padding:2px 4px;max-width:190px}

/* ---- 0252 (v3.624.0) — THE QUOTE THAT PRICES A BOOKING, ON ITS ROW --------------------------
   Beside the value, because it explains it. Two states and they must not read alike: an approved
   price is settled (quiet, `--ok`) and one waiting on us is the row's only actionable fact
   (`--warn`, the same ink the quote row and the office tile use for the same thing — one signal for
   "waiting on our approval" wherever it appears). Border only; a filled chip here would outweigh
   the stage and the clock, which is what somebody is scanning for. */
.bkg-quote{font-size:11px;font-weight:600;padding:1px 7px;border:1px solid var(--line);
  border-radius:999px;white-space:nowrap}
.bkg-quote-ok{border-color:var(--ok);color:var(--ok)}
.bkg-quote-wait{border-color:var(--warn);color:var(--warn)}

/* ---- 0254 (v3.625.0) — THE TWO BANDS ON THE OFFICE FRONT PAGE ------------------------------
   The heading IS the grouping, so it has to read as a label on a group and not as a section title
   competing with the panel's own: small, uppercase, muted, tight to the row under it. The second
   band gets the gap; the first sits straight under the panel edge. */
.off-band + .off-band{margin-top:14px}
.off-bandlab{margin:0 0 6px;font-size:.72rem;text-transform:uppercase;letter-spacing:.06em;
  color:var(--muted);font-weight:700}

/* ---- 0245 — THE EASY START HOLDS THE REAL SCREENS (v3.636.0) -----------------------------------
   Darren: *"it should be step-by-step in the one guided start without needing to go to any other
   screen in the middle."* Two small rules, both about a borrowed panel sitting inside a step. */

/* The kind tiles are gone when the guide has pinned the importer to one kind, so the empty wrapper
   must not leave a gap where a question used to be. */
.wf-step.wf-pinned{ display:none; }

/* Which app you are connecting, with the way back to the others. Reads as a heading for the
   borrowed form rather than as another control in the step. */
.wiz-conn-head{
  display:flex; align-items:center; gap:10px;
  margin:0 0 12px; padding:0 0 10px;
  border-bottom:1px solid var(--line);
}

/* ---- 0247 — THE EXPLANATION ON EVERY EASY START STEP (v3.638.0) --------------------------------
   Darren: *"full explanations, full helpful pop-ups, so the first time you do it you get nice simple
   guided instructions one step at a time."* Open on a step nobody has finished yet, folded to one
   line afterwards — so a first-timer reads everything and a second workspace is not a wall of text
   they have already read. */
.wiz-help{
  margin:12px 0 16px; padding:0;
  border:1px solid var(--line); border-left:4px solid var(--blue);
  border-radius:10px; background:var(--panel-2);
}
.wiz-help>summary{
  cursor:pointer; padding:10px 14px; font-weight:700; color:var(--blue-ink);
  list-style:none;
}
.wiz-help>summary::-webkit-details-marker{ display:none; }
.wiz-help>summary::before{ content:'▸ '; }
.wiz-help[open]>summary::before{ content:'▾ '; }
.wiz-help .wh-body{ padding:0 14px 12px; }
.wiz-help .wh-row{
  display:flex; gap:10px; align-items:flex-start;
  padding:7px 0; line-height:1.5;
}
.wiz-help .wh-row+.wh-row{ border-top:1px solid var(--line); }
.wiz-help .wh-e{ flex:0 0 auto; font-size:15px; line-height:1.5; }

/* The guide's own honest way out, beside the step count rather than beside Next: it is the frame,
   not a step. */
.wiz-park{ margin-left:10px; font-size:13px; }

/* ================================================================================================
   0363 — THE SIMPRO MCP CONNECTOR ADMIN SCREEN (v3.724.0)
   ------------------------------------------------------------------------------------------------
   Darren: *"Can we build an admin module app in our app suite that only Platinum Consultants
   logins would even be able to see in the list?"*

   A FORM GRID, not a column of full-width boxes. Sixteen settings down one 1200px page is a lot of
   scrolling for something somebody fills in once per customer; two or three to a row keeps the
   whole customer on one screen, which is what makes "is this the right business?" answerable at a
   glance. It collapses to one column under 640px rather than shrinking the boxes, because a
   240px-wide box holding a full Simpro base URL shows about six characters of it.
   ============================================================================================= */
.fldgrid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));margin:12px 0}
.fld{display:grid;gap:5px}
.fld>span{font-size:13px;font-weight:600;color:var(--muted)}
.fld input,.fld select{width:100%;padding:10px 12px;border:1px solid var(--line);
  border-radius:var(--r,10px);font:inherit;background:var(--panel);color:var(--ink);min-height:44px}
.fld small{line-height:1.35}
@media (max-width:640px){ .fldgrid{grid-template-columns:1fr} }
/* The new-sign-in fields sit inside the customer panel, so they are marked off from the customer's
   own settings above them — otherwise "email" reads as a second contact address for the business. */
.mcp-newuser{border-top:1px solid var(--line);padding-top:12px;margin-top:14px}
.mcp-num{text-align:right;font-variant-numeric:tabular-nums}
.mcp-act{text-align:right;white-space:nowrap}
/* THE OPEN CUSTOMER IS MARKED IN THE LIST. The panel below names it too, but the list is what
   somebody's eye goes back to, and an unmarked list after pressing Open reads as nothing happening. */
.mcp-table tr.mcp-on{background:color-mix(in srgb, var(--blue) 8%, transparent)}
/* State is a word, not a colour alone: "paused" and "churned" are different facts and a reader
   should not have to learn which shade means which. */
.mcp-status{display:inline-block;padding:1px 8px;border-radius:10px;font-size:.78rem;
  font-weight:700;background:var(--panel-2);color:var(--muted)}
.mcp-status.mcp-active{background:color-mix(in srgb, var(--ok) 16%, transparent);color:var(--ok)}
.mcp-status.mcp-paused{background:color-mix(in srgb, var(--warn,#b26a00) 16%, transparent)}
.mcp-status.mcp-churned{background:color-mix(in srgb, var(--danger) 12%, transparent);color:var(--danger)}

/* ---- 0373 — THE ZOOM OUTCOME TABLE ANSWERS "AND SO WHAT?" (v3.725.0) --------------------------
   Darren: *"I don't know how to make decisions on these. There's no way to click them or know what
   to do."* The last column used to hold three quiet grey fragments reading "a decision", which is
   the same colour as the explanatory note above them — so the one thing on the row that answers
   "do I need to act?" looked like more prose. Owed rows are marked; settled rows stay grey, which
   is now meaningful rather than uniform. */
.alz-verdict{white-space:normal;min-width:9em}
.alz-owed{font-weight:700;color:var(--warn,#b26a00)}
.alz-verdict .btn.tiny{margin-top:6px}

/* ---- 0374 — A RULE THAT CAN NEVER FIRE (v3.725.0) --------------------------------------------
   Eight of twenty-six live rules had never matched an email and never could, and the screen said
   "Nothing has matched it yet" against every one of them — the same sentence a rule written last
   Tuesday for a quarterly email carries. The two had to stop looking identical.

   AN EDGE, NOT A FADE. `.mr-off` (a rule switched off) already dims the row, and dead is not off:
   these rules are ON, the person meant them, and they are worth READING before deciding to remove
   one. So it is a left rule and a coloured sentence at full contrast — the same device the inbox
   uses for a red row, and for the same reason. */
.mr-row.mr-dead{box-shadow:inset 3px 0 0 var(--warn,#b26a00)}
.mr-deadwhy{display:block;margin-top:2px;font-size:.85rem;color:var(--warn,#b26a00);line-height:1.35}
.mr-deadnote{border-left:3px solid var(--warn,#b26a00);padding-left:10px}

/* 0375 — what a connector customer IS, now that its settings live in a window. A read-out rather
   than eleven inputs: the panel answers "is this the right business?" at a glance, and one button
   opens the form. */
.mcp-facts{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));
  margin:12px 0}
.mcp-fact{display:grid;gap:2px}
.mcp-fact>span{font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;
  letter-spacing:.03em}
.mcp-fact>b{font-size:15px;word-break:break-word}

/* ---- 0382 — A SEARCH OVER FIFTY-NINE, AND THE CUSTOMER IN A WINDOW (v3.730.0) ----------------
   Darren: *"we will need a search box at the top, and when we click Open, it should pop up as a
   window, not all the way down at the bottom."* The bar keeps the search, Add and Reload on one
   line at desktop width and stacks on a phone rather than squeezing the input to nothing. */
.mcp-bar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:10px 0}
.mcp-find{flex:1 1 260px;min-width:0;font:inherit;padding:10px 12px;border-radius:10px;
  min-height:44px;border:1px solid var(--line);background:var(--panel-2,var(--panel));
  color:var(--ink)}
.mcp-found{white-space:nowrap}
/* The customer window is wider than a form: it carries the facts row and the whole sign-in table. */
.sheet-mcp .mcp-facts{margin-top:4px}

/* ---- KEY MOMENTS: WHAT IS STILL TO SET UP (v3.1106.0, 29 September 2026) -----------------------
   Darren: *"I don't see anywhere that I can look at it or do anything with it."* The checklist that
   public/zoommoments.js draws at the top of the key-moments panel: one line per link in the chain,
   ticked or not from the server's own answer, the fix under each unticked one. A tick AND a word
   (the `.sr-only` "done / still to do"), never colour alone. Tokens only, so dark mode follows. */
.zm-setup{margin:6px 0 10px;padding:8px 12px;border:1px solid var(--line);border-radius:var(--r);
  background:var(--panel)}
.zm-setup>summary{cursor:pointer}
.zm-check{list-style:none;margin:6px 0 2px;padding:0;display:flex;flex-direction:column;gap:6px}
.zm-ck{display:flex;align-items:flex-start;gap:8px}
.zm-ck-mark{flex:0 0 20px;height:20px;border-radius:50%;display:inline-flex;align-items:center;
  justify-content:center;font-size:12px;font-weight:800;border:1.5px solid var(--line);color:var(--muted)}
.zm-ck-ok .zm-ck-mark{border-color:var(--ok,#15803d);color:var(--ok,#15803d)}
.zm-ck-no .zm-ck-words{font-weight:600}
.zm-ck-body{display:flex;flex-direction:column;gap:2px;min-width:0}
.zm-ck-fix{font-size:13px}
/* v3.1120.0 (30 September 2026) — THE MOMENTS AT THE TOP: the timed-transcript strip, the chip rows
   (the shared `.chips .chip` look, `.on` = the pressed one), the search box and one compact card per
   moment. Tokens only, so dark mode follows; the chips wrap rather than scroll a phone sideways. */
.zm-timed{margin:var(--sp-2,8px) 0}
.zm-timed .pill{margin-right:4px}
.zm-browse{margin:var(--sp-3,12px) 0 var(--sp-4,16px)}
.zm-chips{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0}
.zm-search{display:flex;align-items:center;gap:8px;flex:1 1 320px;max-width:560px}
.zm-search input{flex:1;min-width:0}
.zm-row{margin:8px 0;padding:10px 12px;border-radius:var(--rad-sm,8px);background:var(--s-sunk,var(--panel-2))}
.zm-row p{margin:2px 0}

/* =================================================================================================
   v3.1111.0 (30 September 2026) — CONTRAST, MEASURED RATHER THAN EYEBALLED
   -------------------------------------------------------------------------------------------------
   test/v31111a11y walks every screen, staff and customer, desktop and phone, light and dark, and
   measures each piece of text against the colour actually behind it (4.5:1, or 3:1 for large text).
   The token changes above (`--*-ink`, `--grey`, `--ok-soft`) cleared about nine in ten findings;
   these are the rest, one component at a time. The brand blue and orange are kept as FILLS
   everywhere — where the text on a fill failed, the text changed, not the fill.
   ================================================================================================= */
/* The "?" help dot: white on --grey-mid was 1.9:1, and inside a .field it took the label's grey
   (#5B6070 on #BCBEC0, 3.4:1). A darker dot with white on it is 6.3:1; dark theme 9.9:1. */
.help,label span.help,.field span.help{background:#625E5F;color:#fff}
.help:hover,label span.help:hover,.field span.help:hover{background:var(--blue);color:#fff}
html[data-theme="dark"] .help,html[data-theme="dark"] label span.help,html[data-theme="dark"] .field span.help{background:#3A393B;color:#F5F4F4}
html[data-theme="dark"] .help:hover,html[data-theme="dark"] label span.help:hover,html[data-theme="dark"] .field span.help:hover{background:var(--blue);color:#fff}
/* "Hide this" was the link blue at 62% opacity: 3.0:1 light, 2.3:1 dark. Quiet by colour instead. */
.linkbtn.xpl-hide{opacity:1;color:var(--muted)}
.linkbtn.xpl-hide:hover{color:var(--accent-ink)}
/* TEXT ON THE BRAND ORANGE takes ink, not white: white on #FF6D00 is 2.8:1, #141726 on it 6.3:1
   (and 5.2:1 on the dark theme's #EA5F00). Count badges, the "Saves $…" ribbon, the chart's value
   labels, today's date on the calendar, the Join star. Same for the teal chart segment (5.7:1). */
.cm-n,.et-save,.hb-ch3 .hb-val,.bs-tgdaytoday>.bs-tghead>.hint,
.sidebar .tab.edge-join .star{color:#231F20}
/* ORANGE AS TEXT reads the orange ink (#A84300 light, 5.6:1 on --orange-soft; #FF8A3D dark). */
.tk-open,.co-chip,.conn-pill.soon,.caution,.adv .adv-lbl b,.action-cta,.cd-star,.sidebar .tab .star,
html[data-theme="dark"] .stage-badge,html[data-theme="dark"] .badge{color:var(--orange-ink)}
.action-card:not(.featured) .action-cta{color:var(--accent-ink)}
#emp-grid thead th[data-tip]:hover .hdr-i{color:var(--orange-ink)}
html[data-theme="dark"] .sidebar .tab.edge-tier-bronze .star{color:#D08A4A}
/* v3.1139.0 — the light-theme silver and gold stars were darkened to clear 4.5:1 on the active tab, so the dark theme
   gets its own light versions, the way bronze already had (#D08A4A). */
html[data-theme="dark"] .sidebar .tab.edge-tier-silver .star{color:#C3CAD6}
html[data-theme="dark"] .sidebar .tab.edge-tier-gold .star{color:#E2B54A}
/* A STATUS PILL IN THE STATUS'S OWN COLOUR is a colour somebody picks, so no one hex can be checked
   in advance. Half the status colour and half the ink: every hue in the palette clears 4.5:1 on its
   own tint in both themes (worst measured 4.7:1, a green), and it still reads as its colour. */
.enq-pill,.enq-stchip{color:color-mix(in srgb,var(--pill,#6B7280) 50%,var(--ink))}
.enq-stchip.on{color:#fff}
/* The danger confirm: white on the dark theme's light red was 2.8:1; --toast-bad is the red made
   for white text (5.4:1 light, 6.9:1 dark). */
.btn.hm-godanger{background:var(--toast-bad);border-color:var(--toast-bad)}
/* A GHOST danger button was painted solid red by `.btn.danger` (same weight, later rule) with red
   text on it — 1.0:1, "Replace my data with the demo set…" invisible on Your team. */
.btn.ghost.danger{background:transparent}
/* FADED BY OPACITY, which takes the colour below the floor however good the colour is. Each keeps its
   quieter look through a muted colour instead. */
.app-build-stamp,.push-note.push-diag,.alib-folders .btn b,.wif-topsub,.es-terminal,
.emp-groupbar .egb-n,.emp-groupbar .egb-tab.on .egb-n,#emp-grid thead th .hdr-i{opacity:1}
tbody tr.empgrp .egh-rename{opacity:1}
.asw-key{opacity:1;color:var(--muted)}
.edge-partial td{opacity:1;color:var(--muted)}
.bs-weekend{opacity:1;background:var(--panel-2)}

/* =================================================================================================
   v3.1119.0 (30 September 2026) — UI REFRESH v2: THE SHARED COMPONENTS
   -------------------------------------------------------------------------------------------------
   Darren approved the mock ("Looks good. Let's do it!"). The rules the mock laid over the top were
   folded into the rules they overrode (panels, buttons, fields, tables, headings, the sidebar, the top
   bar, the page header); what is below is NEW furniture the screens did not have yet, so later work on
   each screen moves its own "Nothing here…" lines, status words and totals onto these rather than
   drawing its own. Listed at the top of this file too.
   ================================================================================================= */
/* ONE STAT TILE — a small-caps label, a big tabular figure, a quiet line under it. */
.stat{display:flex;flex-direction:column;gap:2px;background:var(--s-raised);border:1px solid var(--s-line-2);
  border-radius:var(--rad);box-shadow:var(--sh-1);padding:var(--sp-4);text-align:left;min-width:0}
.stat-l{font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.stat-n{font-size:var(--fs-2xl);font-weight:750;letter-spacing:-.02em;line-height:1.1;color:var(--ink);font-variant-numeric:tabular-nums}
.stat-s{font-size:var(--fs-sm);color:var(--muted)}
.stat.bad .stat-n{color:var(--s-bad)}
.stat.warn .stat-n{color:var(--s-warn)}
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:var(--sp-3);margin:0 0 var(--sp-4)}
.con-stat{border-color:var(--s-line-2);border-radius:var(--rad);background:var(--s-raised);box-shadow:var(--sh-1)}
.con-stat-lab{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}

/* EMPTY STATE — an icon, one bold line, one sentence, one action. In place of a grey "Nothing here". */
.empty-state{display:flex;align-items:center;gap:var(--sp-4);padding:20px 22px;margin:6px 0;border:1px dashed var(--line);
  border-radius:var(--rad);background:var(--panel);color:var(--muted)}
.empty-state .es-ico{width:44px;height:44px;border-radius:12px;display:grid;place-items:center;flex:none;
  background:var(--s-accent-soft);color:var(--blue-ink)}
.empty-state .es-ico svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.empty-state .es-text{flex:1;min-width:0}
.empty-state b{display:block;color:var(--ink);font-size:var(--fs-md);margin-bottom:2px}
.empty-state .btn{flex:none}
@media(max-width:560px){.empty-state{flex-wrap:wrap}}

/* TOAST — the placed card (the house one, .h-toast, is in styles.css). Ink on light; light on dark. */
.toast{position:fixed;right:24px;bottom:24px;z-index:9999;display:flex;align-items:center;gap:12px;
  background:#231F20;color:#fff;border-radius:12px;padding:12px 14px;box-shadow:var(--sh-3);font-size:var(--fs-md);
  max-width:min(460px,calc(100vw - 32px))}
.toast small{display:block;color:#CFCBCC;font-size:12.5px}
.toast button{background:transparent;border:0;color:#fff;font:inherit;font-weight:700;margin-left:auto;padding:6px 8px;
  border-radius:8px;cursor:pointer}
html[data-theme="dark"] .toast{background:#F5F4F4;color:#231F20}
html[data-theme="dark"] .toast small{color:#4A4647}
html[data-theme="dark"] .toast button{color:#0038B8}

/* SKELETON rows, for a screen to show while it reads instead of a "Loading…" sentence. */
.skel{display:grid;gap:10px}
.skel i{display:block;height:14px;border-radius:7px;background:linear-gradient(90deg,var(--s-sunk) 0%,var(--b-50) 40%,var(--s-sunk) 80%);
  background-size:200% 100%;animation:skelshim 1.4s linear infinite}
html[data-theme="dark"] .skel i{background:linear-gradient(90deg,var(--s-raised) 0%,#232224 40%,var(--s-raised) 80%);background-size:200% 100%}
@keyframes skelshim{to{background-position:-200% 0}}
@media (prefers-reduced-motion: reduce){.skel i{animation:none}}

/* =================================================================================================
   v3.1121.0 (30 September 2026) — UI REFRESH v2, STEP 5: THE TOOL APPS
   -------------------------------------------------------------------------------------------------
   The look Darren approved in the mock ("Looks good. Let's do it!"), carried onto the tools people
   actually run: the Overhead Calculator (every step, the Easy start and the What-if planner), the two
   Health Checks, QRS, the Archivers, Supplier Price Check, Public Holidays, Simpro Transfer, the
   Supplier File Reader, the Simpro snapshot and the overhead push on Export.
   Nothing here changes what a screen DOES — no id, handler or field moved — only how its parts are
   drawn, and every colour is a TOKEN (so the dark theme follows without a second set of rules;
   test/v31121ui8 reads this block and fails on a literal colour). The parts, one at a time:
     * THE GUIDED-START CARD (.aw-card) is the calm blue-50 card of the mock with a blue badge, not a
       white box outlined in the brand blue shouting over the tool underneath it.
     * STEP HEADINGS. Every "1 · Connection"-style heading gets its number drawn as a round badge
       (public/toollook.js wraps the number in place; the words, the order and the text a screen reader
       hears are exactly what they were). The Business Health Check's own badges (.bh-num) match.
     * THE STEPPER on the Easy start and every per-app guided start (.wizard-steps / .wstep): the step
       you are on is the brand blue, a step you have done is green with a tick, a step you cannot reach
       yet is quiet but READABLE (it was faded to half opacity, under 3:1). On a phone it is one row
       that scrolls inside itself, rather than thirteen chips stacked above the question.
     * PROGRESS BARS — the run board (.sbd-bar), Simpro Transfer and Public Holidays — are one shape:
       a sunk track and a brand-blue fill, rounded, from tokens.
     * THE REASSURANCE LINE (.gd-safe) is the ok wash from the tokens (it was a hand-typed green that
       sat too light on the dark panel).
     * STAT TILES: the dashboard's Key figures and the tool result tiles read their figure in the blue
       ink, two to a row where there is room, as the mock draws them.
     * "MORE EXPORTS" on Your rate: the three secondary downloads fold into one menu on the hero, so
       "Open the full report" is the one orange action on it.
     * SIMPRO TRANSFER's two sides are two cards with the label above its box, instead of a fieldset
       frame with the label jammed beside a full-width select.
   ================================================================================================= */
/* the guided-start card */
.aw-card{background:linear-gradient(90deg,var(--s-accent-soft) 0%,var(--s-panel) 72%);border:1px solid var(--s-line-2);
  border-left-width:1px;border-radius:var(--rad);box-shadow:var(--sh-1);padding:var(--sp-3) var(--sp-4)}
.aw-badge{background:var(--b-500);color:var(--s-hero-ink);border-radius:var(--rad-pill);letter-spacing:.06em}
.aw-card-txt{color:var(--ink)}

/* step headings: the number as a badge (drawn by public/toollook.js) */
.tlook-n{display:inline-flex;align-items:center;justify-content:center;min-width:26px;height:26px;padding:0 6px;
  margin-right:var(--sp-2);border-radius:var(--rad-pill);background:var(--s-accent-soft);color:var(--blue-ink);
  font-size:var(--fs-sm);font-weight:800;line-height:1;vertical-align:.12em;font-variant-numeric:tabular-nums}
.tlook-sep{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.wf-n{background:var(--s-accent-soft);color:var(--blue-ink)}
.bh-num{background:var(--s-accent-soft);color:var(--blue-ink);border:0;font-weight:800}
.bh-card{border-color:var(--s-line-2);border-radius:var(--rad);background:var(--s-panel)}

/* the stepper (Easy start, and every per-app guided start) */
.wizard{border-radius:var(--rad);border-color:var(--s-line-2);box-shadow:var(--sh-1);overflow:hidden}
.wizard-steps{background:var(--s-panel);border-bottom:1px solid var(--s-line-2);gap:var(--sp-2)}
.wstep{border-radius:var(--rad-pill);background:var(--s-panel);border:1px solid var(--s-line);color:var(--ink-2);
  font-weight:600;box-shadow:none;opacity:1}
.wstep.done{background:var(--s-ok-soft);border-color:transparent;color:var(--s-ok)}
.wstep.active,.wstep.active.clickable:hover{background:var(--b-500);border-color:var(--b-500);color:var(--s-hero-ink);box-shadow:var(--sh-1)}
.wstep.locked{opacity:1;background:var(--s-sunk);border-color:transparent;color:var(--muted)}
.wstep.clickable:hover{border-color:var(--b-500);color:var(--blue-ink)}
@media (max-width:640px){
  .wizard-steps{flex-wrap:nowrap;overflow-x:auto;overscroll-behavior-x:contain;scrollbar-width:thin;padding:var(--sp-3)}
  .wstep{flex:none;white-space:nowrap}
}

/* progress bars, one shape */
.sbd-bar,.hol-prog,.ax-bar-track{height:8px;border-radius:var(--rad-pill);background:var(--s-sunk);border:0;overflow:hidden}
.sbd-bar>i,.hol-prog span,.ax-bar-fill{display:block;height:100%;border-radius:var(--rad-pill);background:var(--b-500);transition:width .4s ease}
.ax-bar-track{margin:var(--sp-2) 0 var(--sp-1)}
.ax-bar-fill.is-unknown{opacity:.35}
@media (prefers-reduced-motion:reduce){.sbd-bar>i,.ax-bar-fill{transition:none}}
.stepboard{border-color:var(--s-line-2);border-radius:var(--rad);background:var(--s-panel)}
.sbd-row.is-todo{opacity:1;color:var(--muted)}
.sbd-row.is-now .sbd-mark{color:var(--blue-ink)}
.sbd-row.is-done .sbd-mark{color:var(--s-ok)}

/* The connection line on the archivers and the health checks: the COMPANY chip stays the loud orange one
   (it answers "which company am I about to touch?"), and the instance chip beside it is the quiet
   corroboration styles.css meant it to be — the orange .co-chip rule above was also painting it. */
.co-chip.co-chip-host{background:var(--s-sunk);color:var(--muted);border:1px solid var(--s-line);font-weight:500}

/* the reassurance line */
.gd-safe{background:var(--s-ok-soft);border-left:4px solid var(--s-ok);border-radius:var(--rad-sm);color:var(--ink)}

/* The health check's "Did you know" findings, its AI briefing and its follow-up questions are `div.na`, which
   no stylesheet had ever defined: the words ran straight into their coloured left rule. A sunk card now. */
div.na{padding:var(--sp-3) var(--sp-4);background:var(--s-sunk);border:1px solid var(--s-line-2);border-radius:var(--rad-sm);color:var(--ink)}
div.na>b:first-child{display:block;margin-bottom:2px}

/* stat tiles in the tools' results */
#view-dashboard #d-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:var(--sp-3)}
#view-dashboard #d-cards .card{margin:0;padding:var(--sp-3) var(--sp-4)}
#view-dashboard #d-cards .card .v,#view-whatif .cards .card .v,#view-bizhealth .cards .card .v,#view-cathealth .cards .card .v,
#view-spendcheck .cards .card .v{color:var(--blue-ink);font-variant-numeric:tabular-nums}

/* A figure like $333,351.56 in a half-width tile on a phone ran past the tile's edge (What-if, at 375px). */
.cards .card{min-width:0}
@media (max-width:480px){.cards .card .v{font-size:var(--fs-xl);overflow-wrap:anywhere}}

/* A ghost DANGER button on the brand band ("Replace my data with the demo set…" on Your team) drew red text on
   the blue: under 2:1. On the band it reads in the band's own ink with a dashed edge — legible, and still the
   quietest thing there (a solid red would make the one destructive action the loudest; it asks before it acts). */
.view-head .btn.ghost.danger{background:transparent;border:1px dashed var(--s-hero-muted);color:var(--s-hero-ink)}

/* the Your rate hero: the brand band's gradient and radius, like every page header */
.rate-hero{background:var(--s-hero);border-radius:var(--rad-lg);box-shadow:var(--sh-2)}
.rate-hero .hero-actions{align-items:center}
.wizard-body h3{color:var(--ink)}

/* "More exports" on the Your rate hero */
.hero-more{position:relative;display:inline-block}
.hero-more>summary{list-style:none;cursor:pointer}
.hero-more>summary::-webkit-details-marker{display:none}
.hero-more>summary::after{content:"";display:inline-block;width:6px;height:6px;margin-left:8px;border-right:2px solid currentColor;
  border-bottom:2px solid currentColor;transform:translateY(-2px) rotate(45deg)}
.hero-more[open]>summary::after{transform:translateY(1px) rotate(-135deg)}
.hero-more-menu{position:absolute;z-index:20;top:calc(100% + 6px);left:0;display:flex;flex-direction:column;gap:var(--sp-1);
  min-width:220px;max-width:calc(100vw - 32px);padding:var(--sp-2);background:var(--s-panel);border:1px solid var(--s-line);
  border-radius:var(--rad);box-shadow:var(--sh-3)}
@media (max-width:640px){
  /* On a phone the menu opens IN the hero, full width, rather than floating off the right-hand edge. */
  .hero-more{display:block;width:100%}
  .hero-more-menu{position:static;min-width:0;max-width:none;margin-top:var(--sp-2)}
}
.hero-actions .hero-more-menu .btn{background:transparent;border-color:transparent;color:var(--ink);justify-content:flex-start;text-align:left;width:100%}
.hero-actions .hero-more-menu .btn:hover{background:var(--s-accent-soft);color:var(--blue-ink)}

/* Simpro Transfer: each side a card, the label over its box */
.ax-sides{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,340px),1fr));gap:var(--sp-3);margin:0 0 var(--sp-3)}
.ax-side{border:1px solid var(--s-line-2);border-radius:var(--rad);background:var(--s-sunk);padding:var(--sp-3) var(--sp-4) var(--sp-2);margin:0}
.ax-side>legend{float:left;width:100%;padding:0;margin:0 0 var(--sp-2);font-weight:700;color:var(--ink)}
.ax-side>legend+*{clear:both}
.ax-side>label{display:flex;flex-direction:column;gap:var(--sp-1);margin:0 0 var(--sp-2);font-size:var(--fs-sm);font-weight:600;color:var(--ink-2)}
.ax-side>label>select{width:100%;font-weight:400}
#assetxfer-body>.panel>label{display:flex;flex-direction:column;gap:var(--sp-1);max-width:420px;font-size:var(--fs-sm);font-weight:600;color:var(--ink-2)}
#assetxfer-body>.panel>label>input{width:100%;font-weight:400}

/* empty states and skeletons the tools draw while they read */
.tlook-skel{max-width:560px;margin:var(--sp-2) 0}
.empty-state.tlook-empty{margin:var(--sp-3) 0 0}

/* THE PAGE HEADER's own parts (the band itself is `.view-head` in styles.css). */
.page-head .ph-main{min-width:0;flex:1 1 360px}
.page-head .view-head-actions{align-self:center;justify-content:flex-end}

/* WHO HAS BEEN CHANGING WHAT — the search, the two dates and the chips (v3.1120.0, 30 September 2026).
   One wrapping row of fields so a phone stacks them instead of scrolling sideways; the search box takes
   the spare width because it is the control people reach for first. Tokens only, so both themes follow. */
.clog-controls{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--sp-2);margin:var(--sp-3) 0 var(--sp-2)}
.clog-controls label{display:flex;flex-direction:column;gap:var(--sp-1);min-width:0}
.clog-controls .clog-q{flex:1 1 260px}
.clog-controls input{min-width:0;max-width:100%}
.clog-bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--sp-2)}
.clog-bar .seg-chips{margin:0}
.clog-bar .clog-who{display:flex;align-items:center;gap:var(--sp-2)}
.clog-facet{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-2);margin-top:var(--sp-2)}
.clog-facet .seg-chips{margin:0}
.clog-count{margin-top:var(--sp-2)}

/* ---------- "BEFORE THEY JOIN": THE EDGE SALES PAGE (v3.1120.0, 30 September 2026) ----------
   public/edgesell.js draws it for a signed-in non-member. Tokens only, so the dark theme follows without a
   second set of rules; phone-first (one column, then two, then the grid), and nothing wider than the screen:
   every picture is max-width:100% inside a box that clips. The hero is the brand gradient (--s-hero) with the
   one orange action on the page (the Join button, ink on orange — white on it is 2.8:1). */
.sell-page{display:flex;flex-direction:column;gap:var(--sp-6);max-width:1180px;margin:0 auto;min-width:0}
.sell-page section{min-width:0}
.sell-hero{display:grid;grid-template-columns:1fr;gap:var(--sp-5);align-items:center;background:var(--s-hero);color:var(--s-hero-ink);
  border-radius:var(--rad-lg);padding:var(--sp-6) var(--sp-4);box-shadow:var(--sh-2);overflow:hidden}
.sell-hero h1{color:var(--s-hero-ink);font-size:var(--fs-2xl);line-height:1.15;letter-spacing:-.01em;margin:0 0 var(--sp-3);font-weight:800}
.sell-eyebrow{margin:0 0 var(--sp-2);font-size:var(--fs-xs);font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--s-hero-muted)}
.sell-lead{margin:0 0 var(--sp-4);font-size:var(--fs-lg);line-height:1.5;color:var(--s-hero-muted);max-width:60ch}
.sell-cta{display:flex;flex-wrap:wrap;gap:var(--sp-2)}
.sell-cta .btn,.sell-final .btn{font-size:16px;padding:12px 20px;min-height:44px;text-decoration:none;display:inline-flex;align-items:center}
.sell-hero .sell-tour{background:transparent;color:var(--s-hero-ink);border-color:var(--s-hero-muted)}
.sell-hero .sell-tour:hover{background:var(--b-700);color:var(--s-hero-ink)}
.sell-from{margin:var(--sp-3) 0 0;font-weight:600;color:var(--s-hero-ink)}
.sell-points{display:flex;flex-wrap:wrap;gap:var(--sp-2) var(--sp-4);list-style:none;margin:var(--sp-4) 0 0;padding:0;font-size:var(--fs-sm);color:var(--s-hero-muted)}
.sell-points li::before{content:"\2713";margin-right:6px;font-weight:700;color:var(--o-500)}
.sell-hero-pic{display:none}
.sell-hero-pic img{display:block;width:100%;height:auto;border-radius:var(--rad);box-shadow:var(--sh-3);transform:rotate(-1.5deg)}
.sell-head{margin:0 0 var(--sp-4)}
.sell-head h2{margin:0 0 var(--sp-1);font-size:var(--fs-xl);color:var(--ink)}
.sell-head p{margin:0;color:var(--muted);max-width:70ch}
/* the showcase */
.sell-carousel{position:relative;background:var(--s-panel);border:1px solid var(--s-line);border-radius:var(--rad-lg);box-shadow:var(--sh-1);
  padding:var(--sp-3);overflow:hidden}
.sell-carousel:focus-visible{outline:3px solid var(--b-500);outline-offset:2px}
.sell-slide{margin:0}
.sell-slide[hidden]{display:none}
.sell-shot{border-radius:var(--rad);overflow:hidden;border:1px solid var(--s-line);background:var(--s-sunk);aspect-ratio:1042/800}
.sell-shot img{display:block;width:100%;height:100%;object-fit:cover;object-position:top left}
.sell-slide figcaption{display:flex;flex-direction:column;gap:2px;padding:var(--sp-3) var(--sp-1) 0;min-height:4.2em}
.sell-slide figcaption b{font-size:var(--fs-lg);color:var(--ink)}
.sell-slide figcaption span{color:var(--muted)}
.sell-ctrl{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:var(--sp-2);padding-top:var(--sp-3)}
.sell-arrow{width:44px;height:44px;border-radius:var(--rad-pill);border:1px solid var(--s-line);background:var(--s-raised);color:var(--ink);
  font-size:24px;line-height:1;cursor:pointer}
.sell-arrow:hover{background:var(--s-accent-soft);color:var(--blue-ink)}
.sell-dots{display:flex;flex-wrap:wrap;justify-content:center;gap:2px}
/* 24px targets around an 10px dot (WCAG 2.5.8), the shown one a wider brand-blue pill */
.sell-dot{width:24px;height:24px;border:0;background:transparent;padding:0;cursor:pointer;position:relative}
.sell-dot::after{content:"";position:absolute;left:7px;top:7px;width:10px;height:10px;border-radius:var(--rad-pill);background:var(--grey-mid)}
.sell-dot[aria-current="true"]::after{left:3px;width:18px;background:var(--b-500)}
.sell-pause{margin-left:var(--sp-2)}
/* what you get */
.sell-grid{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:1fr;gap:var(--sp-3)}
.sell-card{display:flex;flex-direction:column;gap:4px;background:var(--s-panel);border:1px solid var(--s-line);border-radius:var(--rad);
  box-shadow:var(--sh-1);padding:var(--sp-4)}
.sell-card b{color:var(--ink);font-size:var(--fs-md)}
.sell-card span{color:var(--muted);font-size:var(--fs-sm);line-height:1.5}
.sell-ic{width:40px;height:40px;border-radius:var(--rad-in);background:var(--s-accent-soft);color:var(--blue-ink);display:inline-flex;
  align-items:center;justify-content:center;margin-bottom:var(--sp-2)}
.sell-ic .ico{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
/* the plans: the existing block, without its old heading */
.sell-plans:focus{outline:none}
.sell-planpanel{margin:0}
.sell-plans .edge-who input{font-size:16px}
/* questions */
.sell-q{background:var(--s-panel);border:1px solid var(--s-line);border-radius:var(--rad);padding:0 var(--sp-4);margin:0 0 var(--sp-2)}
.sell-q summary{cursor:pointer;font-weight:600;color:var(--ink);padding:var(--sp-3) 0;min-height:44px;display:flex;align-items:center}
.sell-q p{margin:0 0 var(--sp-3);color:var(--muted);line-height:1.55}
/* the last word */
.sell-final{text-align:center;background:var(--s-accent-soft);border-radius:var(--rad-lg);padding:var(--sp-6) var(--sp-4)}
.sell-final h2{margin:0 0 var(--sp-2);font-size:var(--fs-2xl);color:var(--ink)}
.sell-final p{margin:0 auto var(--sp-4);color:var(--muted);max-width:56ch}
/* On a phone the dots take their own row above the arrows, rather than an arrow being stranded on a line alone. */
@media (max-width:639px){ .sell-dots{order:-1;flex-basis:100%} }
@media (min-width:640px){
  .sell-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .sell-hero{padding:var(--sp-7) var(--sp-6)}
  .sell-hero h1{font-size:34px}
}
@media (min-width:1000px){
  .sell-hero{grid-template-columns:minmax(0,1.15fr) minmax(0,1fr)}
  .sell-hero h1{font-size:var(--fs-3xl)}
  .sell-hero-pic{display:block}
  .sell-grid{grid-template-columns:repeat(4,minmax(0,1fr))}
  /* the picture and its line side by side, so a 1042x800 screen is not a whole screen tall */
  .sell-slide{display:grid;grid-template-columns:minmax(0,1.9fr) minmax(0,1fr);gap:var(--sp-5);align-items:center}
  .sell-slide figcaption{padding:0;min-height:0;gap:var(--sp-2)}
  .sell-slide figcaption b{font-size:var(--fs-2xl);line-height:1.2}
  .sell-slide figcaption span{font-size:var(--fs-lg);line-height:1.5}
  .sell-ctrl{justify-content:flex-start}
}
@media (prefers-reduced-motion:reduce){ .sell-hero-pic img{transform:none} }

/* =====================================================================================================
   ONE SEARCH BOX — `.search-field` (v3.1120.0, 30 September 2026). UI refresh v2, rollout step 2.
   Darren: *"Search boxes look cheap. The UI/UX overhaul must fix them."* The component, drawn by
   searchFieldHtml() or put round an existing box by the enhancer (both in public/ui.js, which says why
   it wraps and never re-creates):

       <span class="search-field [sf-inline] [is-primary] [is-loading] [has-count]">
         <svg class="sf-icon"/> <input class="sf-in" type="search"> <span class="sf-count"/>
         <kbd class="sf-kbd">/</kbd> <button class="sf-clear" aria-label="Clear search"/>
       </span>

   * 40px tall, 16px text (an iPhone zooms into anything smaller and stays zoomed), 10px round, the
     hairline token, a darker line on hover, the brand-blue focus ring with its 3px soft halo.
   * The × shows only when there is text (`:placeholder-shown`, so a value a screen sets in code is
     seen too — the enhancer gives a box with no placeholder a single space for this).
   * The "/" chip shows on a desktop, on the page's primary search, while it is empty and not focused.
   * `.is-loading` (searchFieldBusy) swaps the magnifier for a spinner; `.has-count` shows the count.
   * `!important` on the box's paint and size, and only there: the thirty boxes this replaces each had a
     padding, a border or a radius typed on them (some inline), and the point is that they stop
     differing. The wrapper carries the old box's LAYOUT (flex, width, margins), set by ui.js.
   Contrast, measured by test/v31120srch: text and placeholder 4.5:1+ on the field in both themes. */
.search-field{position:relative;display:flex;align-items:center;min-width:0;box-sizing:border-box;vertical-align:middle}
.search-field.sf-inline{display:inline-flex}
.search-field>input.sf-in{flex:1 1 auto !important;width:100% !important;min-width:0 !important;max-width:none !important;margin:0 !important;
  height:40px !important;min-height:40px !important;box-sizing:border-box !important;
  padding:0 40px 0 38px !important;font-size:16px !important;line-height:1.25 !important;font-family:var(--font) !important;
  border:1px solid var(--s-line) !important;border-radius:var(--rad-in) !important;background:var(--s-raised) !important;color:var(--ink) !important;
  box-shadow:none;outline:0 !important;appearance:none;-webkit-appearance:none;
  transition:border-color .12s,box-shadow .12s,background-color .12s}
.search-field>input.sf-in::placeholder{color:var(--muted);opacity:1}
.search-field>input.sf-in::-webkit-search-cancel-button,.search-field>input.sf-in::-webkit-search-decoration{-webkit-appearance:none;appearance:none;display:none}
.search-field:hover>input.sf-in{border-color:var(--s-line-hover) !important}
.search-field>input.sf-in:focus,.search-field>input.sf-in:focus-visible{border-color:var(--b-500) !important;box-shadow:var(--s-focus) !important}
html[data-theme="dark"] .search-field>input.sf-in:focus{border-color:var(--b-400) !important}
.search-field .sf-icon{position:absolute;left:12px;top:50%;width:18px;height:18px;margin-top:-9px;pointer-events:none;z-index:1;
  color:var(--muted);fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;transition:color .12s}
.search-field:focus-within .sf-icon{color:var(--blue-ink)}
/* The clear button: a 28px target inside the field, ink on a soft circle when hovered. */
.search-field .sf-clear{position:absolute;right:6px;top:50%;width:28px;height:28px;margin-top:-14px;padding:0;border:0;border-radius:var(--rad-pill);
  display:grid;place-items:center;background:transparent;color:var(--muted);cursor:pointer;z-index:1}
.search-field .sf-clear svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round}
.search-field .sf-clear:hover{background:var(--s-sunk);color:var(--ink)}
.search-field .sf-clear:focus-visible{outline:2px solid var(--b-500);outline-offset:1px}
.search-field>input.sf-in:placeholder-shown~.sf-clear{display:none}
/* The "/" chip. Desktop only: a phone has no "/" key worth advertising. */
.search-field .sf-kbd{display:none;position:absolute;right:10px;top:50%;margin-top:-11px;height:22px;min-width:22px;padding:0 6px;box-sizing:border-box;
  border:1px solid var(--s-line);border-bottom-width:2px;border-radius:6px;background:var(--s-panel);color:var(--muted);
  font:600 12px/19px var(--font);text-align:center;pointer-events:none}
@media (hover:hover) and (pointer:fine) and (min-width:901px){
  .search-field:is(.is-primary,.sf-hint)>input.sf-in:placeholder-shown:not(:focus)~.sf-kbd{display:block}
}
/* The result count, between the text and the ×. */
.search-field .sf-count{display:none;position:absolute;right:40px;top:50%;transform:translateY(-50%);pointer-events:none;
  font-size:var(--fs-xs);font-weight:600;color:var(--muted);white-space:nowrap;font-variant-numeric:tabular-nums}
.search-field.has-count .sf-count{display:block}
.search-field.has-count>input.sf-in{padding-right:96px !important}
/* Running: the magnifier becomes a spinner (a ring with a blue quarter). */
.search-field.is-loading .sf-icon{visibility:hidden}
.search-field.is-loading::before{content:"";position:absolute;left:13px;top:50%;width:16px;height:16px;margin-top:-8px;box-sizing:border-box;z-index:1;
  border:2px solid var(--s-line);border-top-color:var(--b-500);border-radius:50%;animation:sfspin .7s linear infinite}
html[data-theme="dark"] .search-field.is-loading::before{border-top-color:var(--b-300)}
@keyframes sfspin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion: reduce){.search-field.is-loading::before{animation-duration:2s}}

/* ---- WHAT SITS BESIDE A SEARCH BOX LINES UP WITH IT --------------------------------------------------
   A select, a date box or a button on the same row as a search is 40px too, on the same 10px radius and
   the same hairline — the half-pixel steps between a 39px select, a 35px button and a 40px search are
   what made a filter row look assembled rather than designed. "The same row" is: a sibling of the
   search, or a sibling of the label the search is in. */
:is(:has(>.search-field),:has(>label>.search-field)):not(label)>:is(select,input:where([type=date],[type=text],[type=number],:not([type])):not(.sf-in),.btn:not(.tiny):not(.small)),
:is(:has(>.search-field),:has(>label>.search-field)):not(label)>label>:is(select,input:where([type=date],[type=text],[type=number],:not([type])):not(.sf-in)){
  min-height:40px;height:40px;border-radius:var(--rad-in);box-sizing:border-box}
:is(:has(>.search-field),:has(>label>.search-field)):not(label)>:is(select,input:where([type=date],[type=text],[type=number],:not([type])):not(.sf-in)),
:is(:has(>.search-field),:has(>label>.search-field)):not(label)>label>:is(select,input:where([type=date],[type=text],[type=number],:not([type])):not(.sf-in)){
  border:1px solid var(--s-line);background:var(--s-raised);padding-top:0;padding-bottom:0}
:is(:has(>.search-field),:has(>label>.search-field)):not(label)>.btn:not(.tiny):not(.small){display:inline-flex;align-items:center;justify-content:center}
/* The drawer's tool row centres its select on the 40px box rather than stretching it. */
.cm-tools{align-items:center}

/* ---- FILTER CHIPS — `.chips` and the filter rows beside a search, drawn like .pill -----------------
   A filter chip is a pill you can press: the pill's radius and weight on the raised surface with a
   hairline; the chosen one is the brand blue (the soft wash, blue ink, a blue ring) — the same soft blue
   as the sidebar's current item, so "this one is on" reads the same everywhere. 32px tall, so a row of
   them sits on a 40px search box's centre line without looking like a second, smaller form. */
.chips{background:transparent;border:0;padding:0;gap:6px;width:auto}
:is(.chips,.bkg-chips,.acad-chipset,.bia-chips)>:is(button,.chip){display:inline-flex;align-items:center;gap:6px;
  min-height:32px;padding:0 13px;border:1px solid var(--s-line);border-radius:var(--rad-pill);background:var(--s-raised);color:var(--ink-2);
  font:600 var(--fs-sm)/1 var(--font);box-shadow:none;cursor:pointer}
:is(.chips,.bkg-chips,.acad-chipset,.bia-chips)>:is(button,.chip):hover{border-color:var(--b-300);color:var(--ink);background:var(--s-raised)}
:is(.chips,.bkg-chips,.acad-chipset,.bia-chips)>:is(button,.chip):is(.on,.primary,[aria-pressed="true"],[aria-selected="true"]){
  background:var(--s-accent-soft);border-color:var(--b-500);color:var(--blue-ink);box-shadow:none}
html[data-theme="dark"] :is(.chips,.bkg-chips,.acad-chipset,.bia-chips)>:is(button,.chip):is(.on,.primary,[aria-pressed="true"],[aria-selected="true"]){border-color:var(--b-400)}
:is(.chips,.bkg-chips,.acad-chipset,.bia-chips)>:is(button,.chip):focus-visible{outline:0;box-shadow:var(--s-focus)}
:is(.chips,.bkg-chips,.acad-chipset,.bia-chips)>:is(button,.chip):is(.on,.primary) :is(.hint,.bia-n,.bkg-count){color:inherit;opacity:.85}

/* =====================================================================================================
   THE CUSTOMER AND MEMBER SCREENS (v3.1121.0, 30 September 2026). UI refresh v2, rollout step 3.
   -----------------------------------------------------------------------------------------------------
   The member home, the calendar, the Academy (the catalogue, a course, the lesson player, the
   certificate), the Resource Library, My Account (its Notifications section is the notifications screen a
   customer has), My Company Details, My team, and the sign-in / sign-up / two-step cards — everything a
   paying customer looks at. The foundation (the tokens, .stat, .empty-state, .skel, the page header) was
   already live; this is those screens moved onto it, with nothing behaviour-shaped touched: no id, no
   handler, no words.

   TOKENS ONLY. Every colour in this block is a var(--…) — test/v31121ui6 reads everything from the banner
   above to the next banner (or the end of the file) and fails on a hex, rgb() or named colour in a rule
   (and test/v31120sell reads everything after the sales page's block, which includes this one). The glass
   washes on the tier hero are tokens, --s-glass and --s-glass-line, declared with the others at the top.

   PHONE FIRST: at 375px nothing is wider than the screen (the same test measures every element), every
   field a customer types into is 16px (under that an iPhone zooms in when it is tapped and stays zoomed),
   and every tap target in the new parts is at least 40px tall.
   ===================================================================================================== */

/* ---- skeleton rows: three widths so the bars read as lines of text, not as a progress bar ---- */
.skel-block{padding:var(--sp-5) var(--sp-4);background:var(--s-panel);border:1px solid var(--s-line-2);border-radius:var(--rad);box-shadow:var(--sh-1);max-width:min(100%, var(--panel-max, 100%))}
/* v3.1127.0 — the friendly loading line above the bars (loadSkel). Plain text, never animated. */
.skel-words{display:block;font-size:var(--fs-sm);color:var(--muted);margin-bottom:2px}
.skel-block i:nth-child(3n+2){width:62%}
.skel-block i:nth-child(3n+3){width:92%}
.skel-block i:nth-child(3n+4){width:76%}

/* ---- empty states inside the customer screens: the one sentence at body size, the action on the right ---- */
.empty-state .es-text>span{display:block;font-size:var(--fs-sm);line-height:1.5;color:var(--muted)}
.edge-dash .empty-state,#edgecal-body .empty-state,#academy-body .empty-state,#library-body .empty-state{margin:var(--sp-2) 0 0}
/* On a phone the sentence keeps the width beside the icon and the one action takes its own line under it,
   instead of three words a line squeezed between the icon and the button. */
@media(max-width:600px){
  .empty-state{flex-wrap:wrap;gap:var(--sp-3);padding:var(--sp-4)}
  .empty-state .es-text{flex:1 1 calc(100% - 60px)}
  .empty-state>.btn{margin-left:56px;min-height:40px}
}

/* ---- THE MEMBER HOME: the tier hero is the page header (breadcrumb, title, figures, chips) ---- */
.edge-hero{border-radius:var(--rad-lg);padding:var(--sp-5) var(--sp-5) var(--sp-5);box-shadow:var(--sh-2);overflow:hidden;position:relative}
.edge-hero .crumbs{margin:0 0 var(--sp-2);color:var(--s-hero-ink)}
.edge-hero .crumbs span,.edge-hero .crumbs i{color:var(--s-hero-ink);opacity:.85}
.edge-hero .crumbs b{color:var(--s-hero-ink)}
.edge-hero h1{font-size:var(--fs-2xl);letter-spacing:-.01em;font-weight:800}
.edge-hero-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:var(--sp-2);margin:0 0 var(--sp-4);max-width:640px}
.edge-hero .edge-hstat{background:var(--s-glass);border:1px solid var(--s-glass-line);box-shadow:none;padding:var(--sp-3) var(--sp-4);border-radius:var(--rad);backdrop-filter:blur(6px)}
.edge-hero .edge-hstat .stat-n{color:var(--s-hero-ink);font-size:var(--fs-xl)}
.edge-hero .edge-hstat .stat-l{color:var(--s-hero-ink);letter-spacing:.04em;text-transform:none;font-size:var(--fs-xs);font-weight:600}
.edge-dash{gap:var(--sp-4)}
.edge-dash-main>.panel,.edge-dash-side>.panel{border-radius:var(--rad);margin:0 0 var(--sp-4)}
.edge-carousel{border-radius:var(--rad);border-color:var(--s-line-2);box-shadow:var(--sh-1)}
.edge-carousel.empty{font-style:normal;border-style:dashed;box-shadow:none;background:transparent}
.edge-thisweek .edge-inc-head{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);flex-wrap:wrap}
.edge-week-row{border-radius:var(--rad-in)}
.edge-benefits .edge-inc-row{border-radius:var(--rad-in)}
@media(max-width:600px){
  .edge-hero{padding:var(--sp-4);border-radius:var(--rad)}
  .edge-hero h1{font-size:var(--fs-xl)}
  .edge-hero-stats{grid-template-columns:repeat(auto-fit,minmax(92px,1fr))}
  .edge-hero .edge-hstat{padding:var(--sp-2) var(--sp-3)}
  .edge-hero .edge-hstat .stat-n{font-size:var(--fs-lg)}
}

/* ---- THE CALENDAR: the legend as quiet pills, the cells on the card surface, today in brand blue ---- */
.cal-top .btn{min-height:36px}
.cal-title{font-size:var(--fs-lg)}
.cal-legend{gap:var(--sp-2)}
.cal-legend-item{padding:4px 10px;border:1px solid var(--s-line-2);border-radius:var(--rad-pill);background:var(--s-sunk);color:var(--ink-2);font-weight:600}
.cal-key{border-radius:50%;width:9px;height:9px}
.cal-cell{border-color:var(--s-line-2);border-radius:var(--rad-sm);background:var(--s-panel)}
.cal-cell.cal-pad{background:transparent}
.cal-cell.cal-today{border-color:var(--b-500);box-shadow:inset 0 0 0 1px var(--b-500);background:var(--s-accent-soft)}
.cal-cell.cal-today .cal-dnum{color:var(--blue-ink)}
.cal-row-wrap,.cal-row{border-color:var(--s-line-2);border-radius:var(--rad-in);background:var(--s-panel)}
.cal-row-wrap:hover,.cal-row:hover{border-color:var(--b-300)}
@media(max-width:600px){.cal-top .btn{min-height:40px}}

/* ---- THE ACADEMY: catalogue cards, the course, the player, the certificate ---- */
.acad-card{border:1px solid var(--s-line-2);border-radius:var(--rad);background:var(--s-panel);box-shadow:var(--sh-1);padding:var(--sp-3) var(--sp-3) var(--sp-4);transition:box-shadow .15s,border-color .15s}
.acad-card:hover{box-shadow:var(--sh-2);border-color:var(--s-line)}
.acad-card.done{border-color:var(--b-300)}
.acad-tile{border-radius:var(--rad-sm);margin:0 0 var(--sp-1);max-height:160px}
.acad-tile.big{max-height:none}
.acad-top h3{font-size:var(--fs-lg)}
.acad-split{gap:var(--sp-4)}
.acad-side{background:var(--s-sunk);border-radius:var(--rad);padding:var(--sp-2)}
.acad-lesson-head h3{font-size:var(--fs-xl)}
.acad-cert{border-radius:var(--rad);border:1px solid var(--s-line-2);background:var(--s-accent-soft)}
.acad-cert h2,.acad-cert h3{color:var(--blue-ink)}
.acad-filters input[type="search"]{font-size:16px}
@media(max-width:600px){
  .acad-grid{grid-template-columns:1fr}
  .acad-tile{max-height:120px}
  .acad-tile.big{width:100%;max-height:140px}
}

/* ---- THE RESOURCE LIBRARY: document cards on the card surface ---- */
#library-body .acad-card{padding:var(--sp-4)}
.lib-folder{border-radius:var(--rad);border-color:var(--s-line-2);background:var(--s-panel);box-shadow:var(--sh-1)}
.lib-folder:hover{border-color:var(--b-300)}

/* ---- MY ACCOUNT, MY COMPANY DETAILS, MY TEAM: fields a readable width, 16px on a phone ---- */
#account-body .panel .field input,#account-body .panel .field select{max-width:560px}
#account-body .panel,#custdetails-body .panel,#team-body .panel{border-radius:var(--rad)}
#account-body .push-panel,#account-body .panel.sub{border-radius:var(--rad-in)}
@media(max-width:600px){
  #account-body input,#account-body select,#account-body textarea,
  #custdetails-body input,#custdetails-body select,#custdetails-body textarea,
  #team-body input,#team-body select,#team-body textarea,
  #library-body input,#academy-body input,#edge-body input,#edgecal-body input{font-size:16px}
}

/* ---- SIGN IN, SIGN UP, TWO-STEP: the card and its fields on the tokens, 16px everywhere ---- */
.login-card .field input,.login-card .field select,.tf-card input{font-size:16px;border-radius:var(--rad-in)}
.login-card .btn{border-radius:var(--rad-in);min-height:44px}
.login-card .btn.small{min-height:36px}
.tf-card{text-align:left}
.tf-card .login-sub{text-align:center;font-size:var(--fs-lg)}
.tf-card .tf-steps{padding-left:var(--sp-5);line-height:1.6}
.tf-card .tf-qr{border-radius:var(--rad-sm);border:1px solid var(--s-line);background:var(--s-panel);padding:var(--sp-2);margin-top:var(--sp-2)}
.tf-card .tf-lost{margin-top:var(--sp-3);padding:var(--sp-3);border:1px solid var(--s-line-2);border-radius:var(--rad-in);background:var(--s-sunk)}
.tf-card .tf-lost summary{cursor:pointer;font-weight:600;min-height:32px}

/* ---- THE PUBLIC BOOKING PAGE (/b/<token>, public/booking.html): the customer's "pick a time" card ---- */
.qp .card{border-radius:var(--rad);border:1px solid var(--s-line-2);box-shadow:var(--sh-1);background:var(--s-panel)}
.qp .card h2{font-size:var(--fs-xl);letter-spacing:-.01em}
.qp .btn{border-radius:var(--rad-in);min-height:44px}
.qp input,.qp select,.qp textarea{font-size:16px;border-radius:var(--rad-in)}

/* =====================================================================================================
   UI REFRESH v2, STEP 4 — THE STAFF AND OFFICE SCREENS (v3.1121.0, 30 September 2026)
   -----------------------------------------------------------------------------------------------------
   Darren approved the mock (ui-refresh-v2.html, with its office dashboard). Steps 1–3 laid the
   foundation — the tokens, the page header on the brand band, pills, stat tiles, empty states, the one
   search box. This is the look carried onto the screens the team works in all day: the Office
   dashboard, the Communications tray and the Inbox, the Directory (search, customer record, contact
   record), the Enquiries board, Edge members and memberships, the staff Bookings board, Consultant
   KPIs, Edge stats, Users and access and the Set-up hub. NOTHING HERE CHANGES WHAT A SCREEN DOES: no id,
   no handler, no route. It is the drawing only, and it uses TOKENS ONLY — no colour is typed below
   this line (test/v31121ui7 reads everything after this marker), so light and dark follow the tokens
   and a later palette change moves these screens with everything else.
   ===================================================================================================== */

/* ---- THE HEADER'S LIVE SUMMARY AND ITS GLASS STATS (setPageSummary in app.js) ----------------------
   Two or three figures on the band, beside the title, the way the mock drew the office. Glass is the
   hero ink at 14% over the gradient, so it is right on the light and the dark band alike. */
.view-head .ph-stats{display:flex;flex-wrap:wrap;gap:var(--sp-3);align-items:stretch;align-self:center}
.view-head:has(>.ph-stats)>div:first-child{flex:1 1 360px;min-width:0}
.view-head .ph-stat{display:flex;flex-direction:column;justify-content:center;gap:2px;min-width:104px;
  padding:var(--sp-2) var(--sp-4);border-radius:var(--rad);
  background:color-mix(in srgb,var(--s-hero-ink) 14%,transparent);
  border:1px solid color-mix(in srgb,var(--s-hero-ink) 28%,transparent)}
.view-head .ph-stat b{font-size:var(--fs-xl);font-weight:750;line-height:1.1;color:var(--s-hero-ink);
  font-variant-numeric:tabular-nums;letter-spacing:-.01em}
.view-head .ph-stat span{font-size:var(--fs-sm);color:var(--s-hero-muted);white-space:nowrap}
@media(max-width:640px){
  .view-head .ph-stats{width:100%;gap:var(--sp-2)}
  .view-head .ph-stat{flex:1 1 0;min-width:0;padding:var(--sp-2) var(--sp-3)}
  .view-head .ph-stat span{white-space:normal}
}

/* ---- OFFICE DASHBOARD -------------------------------------------------------------------------------
   The ten tiles are stat tiles with an icon square in the corner (the mock's), the two band names are
   sentence-case group headings rather than spaced capitals, and a read that failed is a grey pill. */
#view-officehome .off-stats{padding:var(--sp-4) var(--sp-5) var(--sp-5)}
#view-officehome .off-bandlab{text-transform:none;letter-spacing:0;font-size:var(--fs-md);font-weight:700;
  color:var(--ink);margin:0 0 var(--sp-2)}
#view-officehome .off-band + .off-band{margin-top:var(--sp-5)}
#view-officehome .off-board{gap:var(--sp-3)}
#view-officehome .off-board-tile{position:relative;padding:var(--sp-4);gap:var(--sp-1);min-height:118px;
  border:1px solid var(--s-line-2);border-radius:var(--rad);background:var(--s-raised);box-shadow:var(--sh-1);
  transition:border-color .12s,box-shadow .12s}
#view-officehome .off-board-tile:not([disabled]):hover{border-color:var(--b-300);box-shadow:var(--sh-2)}
#view-officehome .off-board-tile:focus-visible{outline:0;box-shadow:var(--s-focus)}
#view-officehome .off-board-tile .off-lab{font-size:11px;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;color:var(--muted);padding-right:44px}
#view-officehome .off-board-tile .off-n{font-size:var(--fs-2xl);font-weight:750;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums}
#view-officehome .off-board-tile .off-blurb{font-size:var(--fs-sm);line-height:1.4;color:var(--muted)}
#view-officehome .off-board-tile.off-unknown{border-style:dashed;box-shadow:none;background:var(--panel)}
.off-ico{position:absolute;top:var(--sp-3);right:var(--sp-3);width:32px;height:32px;border-radius:var(--rad-in);
  display:grid;place-items:center;background:var(--s-accent-soft);color:var(--blue-ink)}
.off-ico svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.off-bad .off-ico{background:var(--s-bad-soft);color:var(--s-bad)}
.off-warn .off-ico{background:var(--s-warn-soft);color:var(--s-warn)}
.off-unknown .off-ico,.off-notyours .off-ico{background:var(--s-sunk);color:var(--muted)}
.off-blurb .off-unk{margin:var(--sp-1) var(--sp-1) 0 0;vertical-align:middle}
/* v3.1142.0 (5 October 2026) — the reason a tile could not read, one short line under the pill (app.js tileHtml). */
.off-blurb .off-why{display:block;margin-top:var(--sp-1);font-size:12px;line-height:1.35;color:var(--ink);overflow-wrap:anywhere}
#view-officehome .off-money .card,#view-officehome .off-money{border-radius:var(--rad)}
#view-officehome .panel>h2{display:flex;align-items:baseline;gap:var(--sp-2);flex-wrap:wrap}
@media(max-width:640px){
  #view-officehome .off-stats{padding:var(--sp-3)}
  #view-officehome .off-board-tile{min-height:0;padding:var(--sp-3)}
  #view-officehome .off-board-tile .off-n{font-size:var(--fs-xl)}
  .off-ico{width:28px;height:28px;top:var(--sp-2);right:var(--sp-2)}
  #view-officehome .off-board-tile .off-lab{padding-right:34px}
}

/* ---- THE INBOX: ITS HEADER BOX IS THE BRAND BAND ------------------------------------------------------
   0699 put the title, About, search and Compact view in the chip box so the screen spends no line on a
   heading of its own; this box is therefore the page header, and it gets the band every screen opens
   with. The chips sit on the band as glass; the chosen one is the raised card with blue ink. */
.ib-band{background:var(--s-hero);color:var(--s-hero-ink);border:0;border-radius:var(--rad-lg);
  padding:var(--sp-4) var(--sp-6) var(--sp-5);box-shadow:var(--sh-2)}
.ib-band .crumbs{color:var(--s-hero-muted);margin:0 0 var(--sp-1)}
.ib-band .crumbs>b{color:var(--s-hero-ink)}
.ib-band .crumbs .linkbtn{color:var(--s-hero-ink);font-weight:650}
.ib-band .ib-h1{color:var(--s-hero-ink);font-size:26px;font-weight:750;letter-spacing:-.02em}
.ib-band .head-about{background:color-mix(in srgb,var(--s-hero-ink) 14%,transparent);color:var(--s-hero-ink);
  border-color:color-mix(in srgb,var(--s-hero-ink) 28%,transparent)}
.ib-band .head-about:hover,.ib-band .head-about[aria-expanded="true"]{background:color-mix(in srgb,var(--s-hero-ink) 24%,transparent);color:var(--s-hero-ink)}
.ib-band .ib-about,.ib-band .ib-qnote{color:var(--s-hero-muted)}
.ib-band .ib-compactbtn:not(.primary),.ib-band .ib-seg:not(.primary){background:color-mix(in srgb,var(--s-hero-ink) 12%,transparent);
  color:var(--s-hero-ink);border-color:color-mix(in srgb,var(--s-hero-ink) 28%,transparent);box-shadow:none}
.ib-band .ib-compactbtn:not(.primary):hover,.ib-band .ib-seg:not(.primary):hover{background:color-mix(in srgb,var(--s-hero-ink) 20%,transparent);
  color:var(--s-hero-ink);border-color:color-mix(in srgb,var(--s-hero-ink) 40%,transparent)}
.ib-band .ib-compactbtn.primary,.ib-band .ib-seg.primary{background:var(--s-panel);color:var(--blue-ink);
  border-color:var(--s-panel);box-shadow:var(--sh-1)}
.ib-band .ib-seg:not(.primary) .ib-segn{background:color-mix(in srgb,var(--s-hero-ink) 18%,transparent);color:var(--s-hero-ink)}
.ib-band .ib-seg.primary .ib-segn{background:var(--s-accent-soft);color:var(--blue-ink)}
.ib-band .ib-segz{opacity:1}
.ib-band .ib-seg:focus-visible,.ib-band .ib-compactbtn:focus-visible{outline:2px solid var(--s-hero-ink);outline-offset:2px}
@media(max-width:640px){.ib-band{padding:var(--sp-4);border-radius:var(--rad)}.ib-band .ib-h1{font-size:22px}}

/* ---- THE COMMUNICATIONS TRAY: TWO PANES ---------------------------------------------------------------
   The people list on the card, the conversation on the sunk surface beside it, and "nothing here" /
   "nobody chosen" as the foundation's empty state rather than a grey line in the corner. */
.cm-drawer .cm-head{gap:var(--sp-3)}
.cm-drawer .cm-head h2{font-size:var(--fs-lg);font-weight:750;letter-spacing:-.01em}
.cm-empty{margin:var(--sp-3);border-style:dashed;background:var(--s-panel)}
.cm-empty .es-text .hint{display:block;margin:0;color:var(--muted)}
.cm-conv-empty .cm-empty{max-width:420px;text-align:left}
.cm-conv-empty{background:var(--s-sunk)}

/* ---- DIRECTORY, ENQUIRIES: THE COUNT STRIP IS A ROW OF STAT TILES --------------------------------------
   `.dir-count` is the directory's "what is in here" and the enquiry board's "what is waiting": the
   foundation's stat tile — a big ink figure, a quiet label — with the board's own red and amber kept
   for the tiles that carry them. */
.dir-counts{gap:var(--sp-3)}
.dir-count{background:var(--s-raised);border:1px solid var(--s-line-2);border-radius:var(--rad);box-shadow:var(--sh-1);
  padding:var(--sp-3) var(--sp-4)}
.dir-count b{font-size:var(--fs-2xl);font-weight:750;letter-spacing:-.02em;line-height:1.1;color:var(--ink);
  font-variant-numeric:tabular-nums}
.dir-count span{font-size:var(--fs-sm);color:var(--muted)}
.dir-count.enq-urgent b{color:var(--s-bad)}
.dir-count.enq-due b{color:var(--s-warn)}
/* The directory search row: the box, Search, the two ticks and the tag picker on one 40px line. */
#view-directory .row:has(>.search-field){align-items:center;gap:var(--sp-2) var(--sp-3)}
#view-directory .row>.search-field{flex:1 1 320px}
#view-directory .row>.btn{min-height:40px}
#view-directory .dir-exact{display:inline-flex;align-items:center;gap:var(--sp-2);min-height:40px;margin:0}
#view-directory .dir-exact select{min-height:40px;border-radius:var(--rad-in)}
/* The customer record: its at-a-glance tiles are the stat tile, its section chips the segmented bar. */
.dsum-t{border-radius:var(--rad);border-color:var(--s-line-2);background:var(--s-raised);box-shadow:var(--sh-1)}
.dsum-l{font-size:11px;font-weight:700;letter-spacing:.08em}
.dsum-v{font-variant-numeric:tabular-nums}
.dir-modal .sheet-card,.dir-modal{border-radius:var(--rad-lg)}
/* The contact record (Edit a person): labels on top, one field per row, 10px fields. */
.dir-modal .field,.sheet-card .field{margin-bottom:var(--sp-3)}

/* ---- ENQUIRIES BOARD ------------------------------------------------------------------------------------ */
#view-enquiries .enq-filterbar{align-items:center;gap:var(--sp-2) var(--sp-3)}
#view-enquiries .enq-key{display:flex;flex-wrap:wrap;gap:var(--sp-2) var(--sp-5);color:var(--muted);font-size:var(--fs-sm)}
#view-enquiries td .pill{margin:0}

/* ---- EDGE MEMBERS AND THE MEMBERSHIPS LIST ------------------------------------------------------------
   The filter row lines up on one 40px line (the tick used to float half a line below the boxes), the
   tier is a pill in the tier's own words, and the list is the foundation's table. */
#view-edgemembers .edge-form:has(#edge-mf-q){align-items:end;gap:var(--sp-2) var(--sp-3)}
#view-edgemembers .edge-form:has(#edge-mf-q)>label:first-child{flex:1 1 280px}
#view-edgemembers .edge-mf-check{min-height:40px;align-self:end;flex:0 0 auto;display:inline-flex;align-items:center;
  gap:var(--sp-2);grid-template-rows:none;white-space:nowrap}
#view-edgemembers .edge-mf-check>input{width:auto;min-width:0;flex:none;margin:0}
.edge-members .edge-pill{display:inline-flex;align-items:center;min-height:22px;padding:2px 9px;border-radius:var(--rad-pill);
  font-size:12px;font-weight:600;text-transform:capitalize;background:var(--s-sunk);color:var(--ink-2)}
.edge-members .edge-pill.edge-test{background:var(--s-warn-soft);color:var(--s-warn)}
.edge-members .edge-tier-gold,.edge-members .edge-tier-platinum{background:var(--s-accent-soft);color:var(--blue-ink)}
.edge-members td .pill{margin-right:var(--sp-1)}
.edge-members td:first-child{color:var(--muted);font-variant-numeric:tabular-nums}

/* ---- BOOKINGS BOARD (staff) --------------------------------------------------------------------------- */
#view-bookings .bkg-filters{align-items:end;gap:var(--sp-2) var(--sp-3)}
#view-bookings .bkg-empty{margin-top:var(--sp-3)}
#view-bookings .bkg-empty .lead,.edge-mempty .lead,.ia-empty .lead{margin:0;font-size:var(--fs-md);color:var(--muted)}

/* ---- CONSULTANT KPIs, EDGE STATS: TABS AS THE SEGMENTED BAR, FIGURES AS STAT TILES ---------------------- */
#view-kpis .stats-tabs{display:inline-flex;flex-wrap:wrap;gap:var(--sp-1);padding:var(--sp-1);margin:0 0 var(--sp-3);
  background:var(--s-sunk);border-radius:var(--rad-pill)}
#view-kpis .stats-tabs .btn{border-color:transparent;background:transparent;color:var(--ink-2);box-shadow:none;border-radius:var(--rad-pill)}
#view-kpis .stats-tabs .btn:hover{background:var(--s-raised);color:var(--ink)}
#view-kpis .stats-tabs .btn.primary{background:var(--s-raised);color:var(--ink);box-shadow:var(--sh-1)}
#view-kpis .kpi-window{align-items:end}
#view-edgestats .cs-figs{gap:var(--sp-3)}
#view-edgestats .cs-fig{border-radius:var(--rad);border-color:var(--s-line-2);background:var(--s-raised);box-shadow:var(--sh-1)}
#view-edgestats .edge-big{font-variant-numeric:tabular-nums;letter-spacing:-.02em}

/* ---- USERS AND ACCESS ----------------------------------------------------------------------------------
   The capabilities a person holds read as a column of green ticks; the ones they do not are quiet. */
#view-suiteadmin #edge-staff-panel td{vertical-align:top}
#view-suiteadmin .edge-cap.on{color:var(--s-ok)}
#view-suiteadmin .edge-master td{background:var(--s-accent-soft)}

/* ---- THE SET-UP HUB --------------------------------------------------------------------------------------
   The zone strip is the segmented bar (the chosen zone the raised one, not a solid blue button), and
   each tile's icon sits in the same soft square as the office tiles'. */
#view-admin .admin-nav{display:inline-flex;flex-wrap:nowrap;gap:var(--sp-1);padding:var(--sp-1);border:0;max-width:100%;
  background:var(--s-sunk);border-radius:var(--rad-pill);margin:0 0 var(--sp-4)}
#view-admin .admin-navb{border-radius:var(--rad-pill);color:var(--ink-2);font-weight:600;padding:var(--sp-2) var(--sp-4)}
#view-admin .admin-navb:hover{background:var(--s-raised);color:var(--ink)}
#view-admin .admin-navb.on{background:var(--s-raised);color:var(--ink);border-color:transparent;box-shadow:var(--sh-1)}
#view-admin .admin-tiles{gap:var(--sp-3)}
#view-admin .admin-tile{grid-template-columns:40px 1fr;border-radius:var(--rad);border-color:var(--s-line-2);
  background:var(--s-raised);box-shadow:var(--sh-1);transition:border-color .12s,box-shadow .12s}
#view-admin .admin-tile:hover{border-color:var(--b-300);background:var(--s-raised);box-shadow:var(--sh-2)}
#view-admin .admin-tile-i{box-sizing:border-box;width:40px;height:40px;padding:10px;border-radius:var(--rad-in);
  background:var(--s-accent-soft);stroke:var(--blue-ink)}
@media(max-width:640px){#view-admin .admin-nav{overflow-x:auto}}

/* ---- THE CUSTOMER RECORD OPENS ON THE BAND --------------------------------------------------------------
   The record's "at a glance" block is that record's page header (it is a window over whatever screen
   you were on, so it has no .view-head of its own): on the brand band like every header, with its
   seven figures as glass tiles and its buttons and contact chips as glass, the way the mock drew it.
   Every piece of text in it takes the hero ink or the hero's muted ink, so it reads on the light and
   the dark band alike; the good/bad colour of a figure gives way to the ink there, and the same figure
   keeps its colour in the section below. */
#dir-summary{background:var(--s-hero);color:var(--s-hero-ink);border:0;border-radius:var(--rad-lg);
  box-shadow:var(--sh-2);padding:var(--sp-5) var(--sp-5) var(--sp-4)}
#dir-summary :is(h3,.dir-coname,b,.dsum-v,.dsum-addr,.dsum-who,a,.calls,.dir-abn){color:var(--s-hero-ink)}
#dir-summary :is(.hint,.dsum-l,.dsum-sub,p){color:var(--s-hero-muted)}
#dir-summary .dsum-head h3{font-size:var(--fs-xl);font-weight:750;letter-spacing:-.01em}
#dir-summary .dsum-t{background:color-mix(in srgb,var(--s-hero-ink) 12%,transparent);
  border:1px solid color-mix(in srgb,var(--s-hero-ink) 26%,transparent);box-shadow:none;border-radius:var(--rad)}
#dir-summary .dsum-t.dsum-go:hover{border-color:color-mix(in srgb,var(--s-hero-ink) 55%,transparent);
  background:color-mix(in srgb,var(--s-hero-ink) 18%,transparent)}
#dir-summary .dsum-t.dsum-go:focus-visible{outline:2px solid var(--s-hero-ink);outline-offset:2px}
#dir-summary :is(.dir-newenq,.dir-openwin,.calls-num,.calls-call,.calls-msg,a.calls){
  background:color-mix(in srgb,var(--s-hero-ink) 12%,transparent);color:var(--s-hero-ink);
  border:1px solid color-mix(in srgb,var(--s-hero-ink) 28%,transparent);border-radius:var(--rad-pill)}
#dir-summary :is(.dir-newenq,.dir-openwin,.calls-num,.calls-call,.calls-msg,a.calls):hover{
  background:color-mix(in srgb,var(--s-hero-ink) 22%,transparent);color:var(--s-hero-ink)}
#dir-summary .pill,#dir-summary .chip{background:color-mix(in srgb,var(--s-hero-ink) 14%,transparent);color:var(--s-hero-ink)}
@media(max-width:640px){#dir-summary{padding:var(--sp-4);border-radius:var(--rad)}}

/* =====================================================================================================
   UI REFRESH v2, THE FINISHING SWEEP (v3.1123.0, 30 September 2026)
   -----------------------------------------------------------------------------------------------------
   The last typed colours came out of the screens' inline `style=` strings (test/v31123ui9 now refuses a
   hex or rgb() in one). The few that were a FIXED meaning rather than a colour somebody picked become a
   class here, on tokens, so they follow light and dark like everything else:
     .enq-pill.pl-*   a fixed-meaning pill ("Switched on", "Off — no OpenAI key", "this enquiry"). A
                      status's OWN colour (a custom enquiry status) is still `--pill` set from its data.
     .warn-callout    the yellow "read this before you type" band on the lesson editor, on the brand's
                      attention colour instead of a stray Bootstrap yellow.
   Tokens only below this line, like the step-4 block above it.
   ===================================================================================================== */
.enq-pill.pl-ok{--pill:var(--s-ok)}
.enq-pill.pl-bad{--pill:var(--s-bad)}
.enq-pill.pl-blue{--pill:var(--blue)}
.enq-pill.pl-neutral{--pill:var(--grey)}
.enq-pill.pl-orange{--pill:var(--orange)}
.warn-callout{background:var(--o-soft);color:var(--ink);padding:var(--sp-2);border-left:4px solid var(--orange);
  border-radius:var(--rad-sm)}

/* =====================================================================================================================
   v3.1132.0 (2 October 2026) — A PHONE ON ITS SIDE
   Darren, with a screenshot of the Business Health Check on his iPhone turned sideways: "Also landscape on the phone
   is not quite right and also doesn't fill the screen." Grey bars down both edges, a header three lines tall (the
   title wrapped, the search squashed to nothing with "atch" spilling out of it, every control in one tall row), a
   full sidebar column, and the last column of "Previous checks" cut off.
   ===================================================================================================================== */

/* ---- 1. EDGE TO EDGE: the notch's insets pad what is drawn, never the page ------------------------------------------
   The body used to be padded by the side insets, so the page's grey showed down BOTH edges (the notch is on one).
   Each strip that runs edge to edge now takes the inset itself as a TRANSPARENT BORDER — the same trick the top inset
   already used — so the strip's own background runs under the notch and only its contents are kept clear. On a phone
   without a notch, and on every desktop, env() is 0 and nothing moves. */
:root{--sa-l:env(safe-area-inset-left,0px);--sa-r:env(safe-area-inset-right,0px)}
.mainwrap > .apptop,.mainwrap > .support-bar,.mainwrap > .tipbar,#app-shell main{border-right:var(--sa-r) solid transparent}
/* Where the sidebar is a column it is the left edge: it grows by the inset rather than losing its width to it. */
@media (min-width:900.02px){
  .sidebar{border-left:var(--sa-l) solid transparent;width:calc(238px + var(--sa-l))}
  .sidebar.collapsed{width:calc(64px + var(--sa-l))}
}
/* Below 900px the sidebar is a drawer and the header and content are the left edge. */
@media (max-width:900px){
  .mainwrap > .apptop,.mainwrap > .support-bar,.mainwrap > .tipbar,#app-shell main{border-left:var(--sa-l) solid transparent}
  .sidebar{border-left:var(--sa-l) solid transparent}
}

/* ---- 2. THE HEADER NEVER WRAPS ITS TITLE, AND THE SEARCH NEVER SPILLS -------------------------------------------------
   At any width. The title is one line, cut with an ellipsis when the bar is full (it was wrapping to three lines and
   making the bar 82px tall); the search box keeps a usable width or is folded into its icon (below), and a placeholder
   longer than the box is cut with an ellipsis rather than drawn past it. */
.apptop-title{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;flex:0 1 auto}
.find-q{text-overflow:ellipsis}
.find-open-btn{display:none;align-items:center;justify-content:center;width:34px;height:34px;flex:0 0 auto;padding:0;
  border:1px solid var(--line);border-radius:var(--rad-pill,999px);background:var(--panel);color:var(--ink);cursor:pointer}
.find-open-btn svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}
.find-open-btn:hover{border-color:var(--accent);color:var(--accent-ink)}
.find-open-btn:focus-visible{outline:2px solid var(--blue);outline-offset:1px}

/* ---- 3. TABLES SAY THEY SCROLL -------------------------------------------------------------------------------------
   A table wider than its card scrolls sideways inside the card (every list sits in a scroll box), and ui.js marks the
   box `data-xscroll` = more / both / end while columns are hidden. The hidden side fades out, so a cut edge reads as
   "there's more this way" rather than as the last column. */
[data-xscroll="more"]{-webkit-mask-image:linear-gradient(to right,var(--ink) calc(100% - 56px),transparent);
  mask-image:linear-gradient(to right,var(--ink) calc(100% - 56px),transparent)}
[data-xscroll="both"]{-webkit-mask-image:linear-gradient(to right,transparent,var(--ink) 36px,var(--ink) calc(100% - 56px),transparent);
  mask-image:linear-gradient(to right,transparent,var(--ink) 36px,var(--ink) calc(100% - 56px),transparent)}
[data-xscroll="end"]{-webkit-mask-image:linear-gradient(to right,transparent,var(--ink) 36px);
  mask-image:linear-gradient(to right,transparent,var(--ink) 36px)}

/* Our details' merge-field table (the sweep found it at 667px on its side): the field names are long code chips
   that would not wrap, so the table ran past the screen with nothing to scroll it. The box scrolls, and below
   900px a long chip breaks rather than pushing the row wide. */
.odv-g{overflow-x:auto}
@media (max-width:900px){ .odv-name{white-space:normal;overflow-wrap:anywhere;text-align:left} }

/* ---- THE SEARCH FOLDS INTO ITS ICON WHERE THE BAR HAS NO ROOM FOR IT ------------------------------------------------
   Between 900px and 1280px wide, beside the sidebar, the title and the five controls, the box was left about 80px —
   "Search everything…" cut to "Sea…" — and the title was squeezed to "Bu…" (on the phone it wrapped to three lines).
   There, and on a phone on its side (below), the box is the icon; pressing it opens the box across the whole bar,
   and Escape, choosing a result or tapping elsewhere folds it again (app.js wireFind / findFold). At 1280px and up
   the box is there as it always was. */
@media (min-width:900.02px) and (max-width:1279.98px), (max-height:500px) and (orientation:landscape) and (pointer:coarse){
  .apptop-find{order:0;flex:0 0 auto;margin:0 0 0 auto;max-width:none;min-width:0}
  .apptop-find .find-q,.apptop-find .find-exact{display:none}
  .find-open-btn{display:inline-flex}
  .apptop.find-open .apptop-find{position:absolute;inset:0;z-index:6;margin:0;padding:0 14px;gap:8px;background:var(--panel)}
  .apptop.find-open .apptop-find .find-q{display:block;flex:1 1 auto;min-width:0}
  .apptop.find-open .apptop-find .find-exact{display:flex}
  .apptop-title{flex:0 1 auto}
  .apptop-find + .apptop-right,.apptop-right{margin-left:0}
  /* …and the App switcher is its icon (its name is its tooltip), so the screen's title gets the room. */
  .apps-btn .apps-lab{display:none}
}
.apptop-find[hidden] + .apptop-right{margin-left:auto}
/* Where the box is shown it keeps a usable width, and gives way before the title does. */
@media (min-width:1280px){ .apptop-find{flex:1 1 160px;min-width:150px} }

/* ---- 4. THE SHORT SCREEN: a phone on its side --------------------------------------------------------------------
   Only on a touch screen less than 500px high and wider than it is tall — a desktop window dragged short keeps its
   desktop header. The header is one row about 48px high: the title on one line, the search an icon that opens across
   the bar, the App switcher an icon, the workspace name cut short, smaller counts, the tips strip gone. ui.js folds the
   sidebar to its icon rail (» still opens it). */
@media (max-height:500px) and (orientation:landscape) and (pointer:coarse){
  .apptop{flex-wrap:nowrap;gap:6px;padding-top:6px;padding-bottom:6px;padding-left:10px;padding-right:10px}
  .apptop-title{flex:1 1 auto;font-size:14px}
  .apptop-help{margin-left:0}
  .apptop-right{gap:6px;flex:0 1 auto;min-width:0}
  .apptop-right > *{flex:0 0 auto}
  .apptop-right .ws-switch{flex:0 1 auto;min-width:0;max-width:11em;overflow:hidden;margin-left:0}
  .apptop-right .ws-switch .ws-btn{max-width:100%;min-width:0;padding:5px 9px;gap:5px}
  .apptop-right .ws-switch .ws-name{min-width:0;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .apps-btn .apps-lab,.ph-btn .ph-lab{display:none}
  .apptop-right .apps-btn,.apptop-right .ph-btn,.apptop-right .cm-btn{min-height:34px;padding:5px 9px}
  .theme-toggle .tt-ico{width:28px;height:28px}
  .cm-badge{min-width:15px;padding:0 4px;font-size:10px;line-height:15px;height:15px}
  /* The rotating tips are a marketing strip; on a 390px-high screen they were a seventh of the room. The
     "may we come in?" bar (.supask) is a question somebody has to answer, and stays. */
  #tipbar{display:none}
  /* 16px a side, the phone's own gutter (24px was the desktop's, on a screen with a notch to clear as well). */
  #app-shell main{padding:12px 16px}
  .sidebar{height:100dvh}
  /* My Day is a column 760px wide so a long line stays readable on a desktop; on a phone on its side that left
     a hundred pixels of grey down each edge. The phone's width is the column. */
  html.myday-only #app-shell main{max-width:none;padding:12px 16px}
  /* WHAT STAYS PUT WHILE YOU SCROLL is the header, and only the header. The step bar (Next / Back, 104px once its
     buttons stack) and the section chips (70px) were sticky as well, and with the header took a third of a
     390px-high screen; here they sit where they are drawn — the step bar at the end of the page, the chips at
     the top of their list. */
  .stepbar,.sec-bar,.dir-secs{position:static}
}

/* =====================================================================================================================
   v3.1133.0 (2 October 2026) — FOLDER TILES: A PICTURE, OR BIGGER IN THEIR OWN COLOUR
   Darren: "in the resource library, I would like the ability to put a thumbnail image for each folder … If there is no
   image, can you please make the folder bigger and, for now, make each folder a different colour?" The tones are the
   --fdN tokens above; the tile reads its pair through two local variables so one rule draws all nine.
   ===================================================================================================================== */
.lib-folders{grid-template-columns:repeat(auto-fill,minmax(210px,1fr))}
.lib-folder.lib-tone{--fdb:var(--fd0-bg);--fdi:var(--fd0-ink);min-height:132px;padding:16px 16px 14px;gap:4px;
  background:var(--fdb);border-color:transparent;border-top:5px solid var(--fdi);color:var(--ink)}
.lib-folder.lib-tone .lib-folder-i{font-size:38px;margin:0 0 6px}
.lib-folder.lib-tone .lib-folder-n{color:var(--fdi);font-size:var(--fs-lg)}
.lib-folder.lib-tone:hover,.lib-folder.lib-tone:focus-visible{background:var(--fdb);border-color:var(--fdi);border-top-color:var(--fdi);box-shadow:var(--sh-2)}
.lib-folder.lib-tone-1{--fdb:var(--fd1-bg);--fdi:var(--fd1-ink)} .lib-folder.lib-tone-2{--fdb:var(--fd2-bg);--fdi:var(--fd2-ink)}
.lib-folder.lib-tone-3{--fdb:var(--fd3-bg);--fdi:var(--fd3-ink)} .lib-folder.lib-tone-4{--fdb:var(--fd4-bg);--fdi:var(--fd4-ink)}
.lib-folder.lib-tone-5{--fdb:var(--fd5-bg);--fdi:var(--fd5-ink)} .lib-folder.lib-tone-6{--fdb:var(--fd6-bg);--fdi:var(--fd6-ink)}
.lib-folder.lib-tone-7{--fdb:var(--fd7-bg);--fdi:var(--fd7-ink)} .lib-folder.lib-tone-8{--fdb:var(--fd8-bg);--fdi:var(--fd8-ink)}
/* With a picture: the picture fills the top of the tile, 16:9, cropped to fit; the words sit under it. */
.lib-folder.has-pic{padding:0 0 12px;overflow:hidden;gap:2px}
.lib-folder.has-pic>:not(.lib-folder-pic){margin-left:14px;margin-right:14px}
.lib-folder-pic{display:block;width:100%;aspect-ratio:16/9;margin:0 0 8px;background:var(--s-sunk);overflow:hidden}
.lib-folder-pic img{display:block;width:100%;height:100%;object-fit:cover}
.lib-folder-wrap>.lib-folder.lib-tone{padding-right:40px}
.lfp-preview{grid-template-columns:minmax(0,260px);margin:0 0 12px}
@media (max-width:640px){
  .lib-folders{grid-template-columns:1fr 1fr}
  .lib-folder.lib-tone{min-height:112px;padding:12px}
  .lib-folder.lib-tone .lib-folder-i{font-size:30px}
}


/* =====================================================================================================================
   v3.1133.0 (2 October 2026) — JUMP CHIPS ON THE REPORT SCREENS
   Darren: "for each of the stats and graphs, I would really like to have chips at the top so we can view the next one
   without having to scroll down and down." One row that scrolls sideways (a report has more charts than a screen is
   wide), sticky like every .sec-bar — and static on a phone on its side with the rest (the v3.1132.0 rule names .sec-bar).
   A target lands just under the row rather than behind it.
   ===================================================================================================================== */
/* v3.1138.0 (3 October 2026) — Darren: "This menu should be a sticky menu at the top, and it needs to be easier to manage
   than a scroll bar left to right." No sideways scroll: the chips that fit on one line show (it wraps until the script has
   measured), the rest go under "More ▾" (app.js jumpFit), and a phone gets one "Jump to" select. */
.jump-bar{display:flex;flex-wrap:wrap;overflow:visible;
  max-width:100%;width:auto;margin:0 0 12px}
.jump-bar .chip{flex:0 0 auto;white-space:nowrap}
.jump-bar .chip.jump-hid{display:none}
.jump-more{position:relative;margin-left:auto;display:flex;align-items:center}
.jump-more-b{border:1px solid var(--line);border-radius:999px;padding:5px 12px;font:inherit;font-size:13px;font-weight:600;
  background:var(--panel);color:var(--ink);cursor:pointer;white-space:nowrap;max-width:220px;overflow:hidden;text-overflow:ellipsis}
.jump-more-b:hover,.jump-more-b:focus-visible,.jump-more-b[aria-expanded="true"]{border-color:var(--blue);color:var(--blue-ink)}
.jump-more-b.on{background:var(--s-accent-soft);border-color:var(--blue);color:var(--blue-ink)}
.jump-menu{position:absolute;right:0;top:calc(100% + 6px);z-index:30;min-width:220px;max-height:min(70vh,480px);overflow-y:auto;
  display:grid;gap:2px;padding:6px;background:var(--panel);border:1px solid var(--line);border-radius:10px;box-shadow:var(--sh-2,var(--shadow))}
.jump-menu[hidden]{display:none}
.jump-menu button{text-align:left;border:0;background:transparent;border-radius:6px;padding:7px 10px;font:inherit;font-size:13.5px;
  font-weight:600;color:var(--ink);cursor:pointer}
.jump-menu button:hover,.jump-menu button:focus-visible,.jump-menu button.on{background:var(--s-accent-soft);color:var(--blue-ink);outline:none}
.jump-sel{display:none;align-items:center;gap:8px;flex:1 1 auto;font-size:13px;font-weight:700;color:var(--muted)}
.jump-sel select{flex:1 1 auto;min-width:0;font:inherit;font-size:15px;font-weight:600;padding:7px 10px;border:1px solid var(--line);
  border-radius:8px;background:var(--panel);color:var(--ink)}
.jump-target{scroll-margin-top:72px}
@media print{ .jump-bar{display:none} }
/* v3.1137.0 (3 October 2026) — Darren: "can it be sticky so that when you click it, it changes? It doesn't scroll you
   down". The row now SWITCHES sections (app.js jumpChoose): the ones not chosen wear .jump-off. On screen only — on paper
   every section prints. One row per screen, so the old rule that stacked a screen's own chips under it is gone. */
@media screen{ .jump-off{display:none !important} }
/* v3.1137.0 — STICKY MEANS VISIBLE. The header (.apptop) is sticky at the top too, and above the chip bars (z-index 15
   to 3), so a bar "stuck" at top:0 was stuck BEHIND the header — scrolled down, the chips had gone. Every chip bar now
   sticks just under the header (app.js measures it into --apptop-h) and under the waiting-chat line when that is up. */
.sec-bar,.dir-secs{top:calc(var(--cm-bar-h,0px) + var(--apptop-h,0px))}
/* A phone: the row is compact (smaller chips, tighter track) and still scrolls sideways inside itself, never the page.
   On its side it is static with every other chip bar (the v3.1132.0 short-screen rule names .sec-bar). */
@media (max-width:640px){
  .jump-bar{gap:2px;padding:3px;margin:0 0 8px;border-radius:12px}
  .jump-bar .chip{padding:5px 10px;font-size:12.5px}
  /* v3.1138.0 — a phone: one "Jump to" select in place of the chips (the chips stay in the page for the keyboard and tests). */
  .jump-bar:has(.jump-more){padding:6px}
  .jump-bar:has(.jump-more) .chip{display:none}
  .jump-bar .jump-more{flex:1 1 auto;margin-left:0}
  .jump-bar .jump-more-b{display:none}
  .jump-bar .jump-sel{display:flex}
}


/* =====================================================================================================================
   v3.1137.0 (3 October 2026) — THE APP FACE: every customer app opens like the Platinum Edge member screen
   Darren: "Kind of like what you did with the Platinum Edge screen: you made it great. We need to do the same with …
   all the other apps to make sure that they look the part: professional, modern, cutting-edge."
   app.js APP_FACES / appFaceMount draw it: the brand-band header dressed as a hero (the app's icon on a glass tile, its
   one line, the Simpro company and safety chips, the one action), "How it works" in three cards, the readiness rows,
   and the shared empty / success / error states. Tokens only — both themes come from the token blocks above; the glass
   on the band is white at an alpha, exactly like the Edge hero's chips.
   ===================================================================================================================== */
.view-head.ax-hero{overflow:hidden;isolation:isolate;padding-top:22px;padding-bottom:24px}
.view-head.ax-hero::after{content:'';position:absolute;z-index:-1;right:-80px;top:-120px;width:360px;height:360px;border-radius:50%;
  background:radial-gradient(closest-side,var(--ax-glow),var(--ax-glow-0));pointer-events:none}
.view-head.ax-hero::before{content:'';position:absolute;z-index:-1;left:0;right:0;bottom:0;height:3px;background:var(--o-500);opacity:.9}
.ax-ico{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:48px;height:48px;border-radius:var(--rad);
  background:var(--ax-glass);border:1px solid var(--ax-glass-line);color:var(--s-hero-ink);align-self:center;
  box-shadow:var(--ax-tile-sh)}
.ax-ico .ax-svg{width:26px;height:26px}
.view-head.ax-hero .head-compact{align-items:center}
.view-head.ax-hero>div:first-child{flex:1 1 440px;min-width:0}
.view-head.ax-hero .head-compact>span.ax-ico.ax-ico{flex-basis:auto;margin-right:2px}
.view-head.ax-hero .head-compact>.ph-sum,.view-head.ax-hero .head-compact>.ax-chips{flex-basis:100%}
/* flex-basis 100% is clamped by the summary's 72ch measure, so on a wide header it sat BESIDE the title; the line
   always starts its own row and keeps a readable measure with padding instead. */
.view-head.ax-hero .head-compact>p.ph-sum{max-width:none;padding-right:min(22%,260px)}
.view-head.ax-hero .ph-sum{font-size:var(--fs-md);color:var(--s-hero-muted);max-width:72ch}
.ax-chips{display:flex;flex-wrap:wrap;gap:8px;margin:12px 0 0;flex-basis:100%}
.ax-chip{display:inline-flex;align-items:center;gap:6px;background:var(--ax-glass);border:1px solid var(--ax-glass-line);
  color:var(--s-hero-ink);border-radius:var(--rad-pill);padding:4px 12px;font:inherit;font-size:12.5px;font-weight:600;line-height:1.5}
.ax-chip .ax-svg{width:14px;height:14px;flex:0 0 auto}
.ax-chip.muted{font-weight:500;font-style:italic;background:var(--ax-glass-soft)}
.ax-chip-btn{cursor:pointer}
.ax-chip-btn:hover,.ax-chip-btn:focus-visible{background:var(--ax-glass-2);border-color:var(--ax-glass-line-2)}
.view-head.ax-hero .view-head-actions{align-self:center}

.ax-intro{background:var(--s-panel);border:1px solid var(--s-line);border-radius:var(--rad);box-shadow:var(--sh-1);
  padding:18px 20px 20px;margin:0 0 var(--sp-4)}
.ax-intro-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
.ax-h{margin:0;font-size:var(--fs-lg)}
.ax-fold{font-size:var(--fs-sm);font-weight:600;color:var(--accent-ink)}
.ax-steps{list-style:none;padding:0;margin:14px 0 0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;counter-reset:none}
.ax-step{display:flex;gap:12px;align-items:flex-start;background:var(--s-raised);border:1px solid var(--s-line-2);border-radius:var(--rad-in);
  padding:14px 16px;transition:transform .15s ease,box-shadow .15s ease,border-color .15s ease}
.ax-step:hover{transform:translateY(-2px);box-shadow:var(--sh-2);border-color:var(--s-line)}
.ax-step b{display:block;font-size:var(--fs-md);color:var(--ink)}
.ax-step p{margin:4px 0 0;color:var(--muted);font-size:var(--fs-sm);line-height:1.5}
.ax-n{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:30px;height:30px;border-radius:50%;
  background:var(--s-hero);color:var(--s-hero-ink);font-weight:750;font-size:var(--fs-sm);font-variant-numeric:tabular-nums}
.ax-ready{list-style:none;padding:0;margin:14px 0 0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
.ax-ready-row{display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid var(--s-line-2);border-radius:var(--rad-in);
  background:var(--s-panel);min-width:0}
.ax-ready-ico{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:32px;height:32px;border-radius:var(--rad-sm);
  background:var(--s-accent-soft);color:var(--s-link)}
.ax-ready-ico .ax-svg{width:18px;height:18px}
.ax-ready-row.ax-ok .ax-ready-ico{background:var(--s-ok-soft);color:var(--s-ok)}
.ax-ready-row.ax-warn .ax-ready-ico{background:var(--s-warn-soft);color:var(--s-warn)}
.ax-ready-row.ax-warn{border-color:var(--s-warn)}
.ax-ready-txt{flex:1 1 auto;min-width:0;font-size:var(--fs-sm);color:var(--s-ink-2);line-height:1.45;overflow-wrap:anywhere}
.ax-ready-txt b{color:var(--ink)}
.ax-ready-row .btn{flex:0 0 auto;margin-left:auto}
.ax-intro.ax-folded{padding:10px 16px}
.ax-intro-sum{display:flex;flex-wrap:wrap;gap:6px 16px;align-items:center}
.ax-mini{display:inline-flex;align-items:center;gap:7px;color:var(--s-ink-2);font-size:var(--fs-sm);font-weight:600}
.ax-mini .ax-n{width:22px;height:22px;font-size:11.5px}

.ax-state{display:flex;align-items:center;gap:var(--sp-4);padding:18px 20px;margin:8px 0;border-radius:var(--rad);
  border:1px solid var(--s-line);background:var(--s-panel)}
.ax-state b{display:block;font-size:var(--fs-md);color:var(--ink)}
.ax-state p{margin:3px 0 0;color:var(--s-ink-2);font-size:var(--fs-sm)}
.ax-state>.btn,.ax-state>a.btn{margin-left:auto;flex:0 0 auto}
.ax-state-ico{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:42px;height:42px;border-radius:50%;
  background:var(--s-accent-soft);color:var(--s-link)}
.ax-state-ico .ax-svg{width:22px;height:22px}
.ax-empty{border-style:dashed}
.ax-success{background:var(--s-ok-soft);border-color:var(--s-ok)}
.ax-success .ax-state-ico{background:var(--s-panel);color:var(--s-ok)}
.ax-error{background:var(--s-bad-soft);border-color:var(--s-bad)}
.ax-error .ax-state-ico{background:var(--s-panel);color:var(--s-bad)}
.ax-flash{box-shadow:var(--s-focus) !important;transition:box-shadow .3s ease}

/* THE RESULTS, POLISHED IN PLACE. Only inside an app screen (.ax-app), so no other screen moves: the summary cards get
   the brand edge the Edge tiles have, the table wrappers a rounded card, and a row under the pointer a soft wash. */
.ax-app .cards .card{position:relative;overflow:hidden;padding-left:18px}
.ax-app .cards .card::before{content:'';position:absolute;left:0;top:0;bottom:0;width:4px;background:var(--s-hero)}
.ax-app .table-wrap{border-radius:var(--rad-in);border:1px solid var(--s-line-2)}
.ax-app .table-wrap table{margin:0}
.ax-app tbody tr[data-rowopen]:hover>td,.ax-app tbody tr:hover>td{background:var(--s-accent-soft)}
.ax-app .panel>h2:first-child{letter-spacing:-.01em}

@media (max-width:900px){ .ax-steps{grid-template-columns:1fr} .ax-ready{grid-template-columns:1fr} }
@media (max-width:640px){
  .ax-ico{width:40px;height:40px;border-radius:var(--rad-in)} .ax-ico .ax-svg{width:22px;height:22px}
  .view-head.ax-hero::after{width:220px;height:220px;right:-70px;top:-90px}
  .view-head.ax-hero .view-head-actions{width:100%} .view-head.ax-hero .view-head-actions .btn{width:100%;justify-content:center}
  .ax-intro{padding:14px 14px 16px}
  .view-head.ax-hero .head-compact>p.ph-sum{padding-right:0}
  .ax-step{padding:12px 14px}
  .ax-ready-row{display:grid;grid-template-columns:32px minmax(0,1fr);align-items:start;column-gap:12px;row-gap:8px}
  .ax-ready-row .btn{grid-column:2;justify-self:start;margin-left:0}
  .ax-steps{gap:8px} .ax-step p{font-size:12.5px}
  .ax-state{flex-wrap:wrap} .ax-state>.btn{margin-left:0}
}
@media (prefers-reduced-motion:reduce){ .ax-step,.ax-flash{transition:none} .ax-step:hover{transform:none} }
@media print{ .ax-intro,.ax-chips,[data-ax-go],.ax-ico{display:none !important} }
/* The apps home and the switcher wear each app's mark on a tile — the same icon its screen's hero carries. */
.apph-card .apph-ico.apph-ico-svg{display:inline-flex;align-items:center;justify-content:center;width:46px;height:46px;border-radius:var(--rad);
  background:var(--s-hero);color:var(--s-hero-ink);box-shadow:var(--sh-2);margin-bottom:4px}
.apph-card .apph-ico.apph-ico-svg .ax-svg{width:24px;height:24px}
.apph-card.locked .apph-ico.apph-ico-svg{background:var(--s-accent-soft);color:var(--s-link);box-shadow:none}
.apph-card{border-radius:var(--rad);box-shadow:var(--sh-1)}
.apph-card:hover{box-shadow:var(--sh-2)}
.apps-menu .ai.ai-svg{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:var(--rad-sm);
  background:var(--s-accent-soft);color:var(--s-link)}
.apps-menu .ai.ai-svg .ax-svg{width:17px;height:17px}
/* The tools' own "✅ Done" panels (archivers, prebuild and takeoff updaters): a success moment in the ok family, the
   warning ones keep their orange edge. The class is added beside the edge the panel already had. */
.ax-app .panel.ax-done-ok{background:var(--s-ok-soft);border-color:var(--s-ok)}
.ax-app .panel.ax-done-ok>h2:first-child,.ax-app .panel.ax-done-ok>h2{color:var(--s-ok)}
.ax-app .panel.ax-done-warn{background:var(--s-warn-soft)}
.rate-hero>.ax-chips{margin-top:-8px}
.home-hero-text>.ax-chips{margin-top:10px}
/* v3.1149.0 (7 October 2026) — the Stripe subscriptions check (public/app.js loadStripeRecon). Tokens only. */
.sr-table{table-layout:fixed;width:100%;min-width:0}
.sr-table td,.sr-table th{vertical-align:top;overflow-wrap:anywhere;white-space:normal}
.sr-table.sr-4 th:nth-child(1){width:19%}.sr-table.sr-4 th:nth-child(2){width:19%}.sr-table.sr-4 th:nth-child(3){width:27%}
.sr-table.sr-2 th:nth-child(1){width:38%}
@media (max-width:760px){.sr-table thead{display:none}.sr-table,.sr-table tbody,.sr-table tr,.sr-table td{display:block;width:100%}
  .sr-table td{padding:6px 10px}.sr-table tr{border-bottom:1px solid var(--line);padding:6px 0}}
.sr-table tr.sr-attn td:first-child{box-shadow:inset 3px 0 0 var(--o-500)}
.sr-sub{margin:0 0 6px}
.sr-sub.sr-canceled,.sr-sub.sr-incomplete_expired{opacity:.7}
.sr-flag{margin:0 0 8px;line-height:1.45}
.sr-chip{display:inline-block;padding:1px 7px;border-radius:999px;font-size:12px;font-weight:600;background:var(--o-soft);color:var(--o-ink)}
.sr-flag.sr-low .sr-chip{background:var(--s-warn-soft);color:var(--ink)}
.sr-ok{color:var(--s-ok);font-weight:600}
.sr-acts{margin-top:4px;display:flex;flex-wrap:wrap;gap:6px;align-items:center}

/* ---- DATA CLEANSE, PHASE 2 (v3.1156.0, 9 October 2026) — the queues, the duplicates board, the stages and costs, and the
   customer's review page (public/cleanse-review.html). Tokens only; every field 16px on a phone so iOS does not zoom. */
#cleanse-body .cln-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:var(--sp-2,8px);margin:var(--sp-2,8px) 0}
#cleanse-body .cln-kpi{border:1px solid var(--line);border-radius:var(--rad-in,8px);background:var(--panel-2);padding:10px 12px;display:flex;flex-direction:column;gap:2px}
#cleanse-body .cln-kpi b{font-size:22px;color:var(--blue-ink)}
#cleanse-body .cln-kpi span{font-size:12.5px;color:var(--muted)}
#cleanse-body .cln-stages{display:flex;flex-wrap:wrap;gap:6px;list-style:none;margin:var(--sp-1,4px) 0 var(--sp-2,8px);padding:0}
#cleanse-body .cln-stage{padding:3px 10px;border-radius:999px;font-size:12.5px;border:1px solid var(--line);color:var(--muted);background:var(--panel)}
#cleanse-body .cln-stage.done{color:var(--s-ok);background:var(--s-ok-soft);border-color:transparent}
#cleanse-body .cln-stage.now{color:var(--blue-ink);background:var(--s-accent-soft);border-color:var(--blue);font-weight:700}
#cleanse-body .cln-stage.skip{text-decoration:line-through;opacity:.85}
#cleanse-body .cln-costs{display:flex;flex-wrap:wrap;gap:var(--sp-1,4px) var(--sp-3,16px);margin:var(--sp-1,4px) 0 var(--sp-2,8px);font-size:13px}
/* v3.1161.0 (11 October 2026) — the Data Cleanse price tiers (Darren's website), the customer's own tier marked */
#cleanse-body .cln-tiers{width:100%;max-width:560px;border-collapse:collapse;margin:var(--sp-1,4px) 0}
#cleanse-body .cln-tiers td{padding:8px 10px;border-bottom:1px solid var(--line);vertical-align:top}
#cleanse-body .cln-tiers tr.cln-tier-on td{background:var(--s-accent-soft);color:var(--blue-ink)}
#cleanse-body .cln-tiers tr.cln-tier-on td:first-child{box-shadow:inset 4px 0 0 var(--blue)}
#cleanse-body .cln-cost meter{width:110px;vertical-align:middle}
/* v3.1161.0 (11 October 2026) — the look-ups as queues, each with its progress and time left (Darren: "there's no indication of how long this would take") */
#cleanse-body .cln-pipe{margin:var(--sp-1,4px) 0 var(--sp-2,8px)}
#cleanse-body .cln-queues{width:100%;border-collapse:collapse;font-size:13px;margin:var(--sp-2,8px) 0}
#cleanse-body .cln-queues caption{text-align:left;color:var(--muted);font-size:12.5px;padding-bottom:4px}
#cleanse-body .cln-queues th,#cleanse-body .cln-queues td{padding:3px 8px;border-bottom:1px solid var(--line);text-align:left;vertical-align:middle}
#cleanse-body .cln-queues progress{width:min(140px,30vw)}
#cleanse-body .cln-recent{margin:2px 0 var(--sp-2,8px);padding-left:18px;font-size:12.5px;color:var(--muted)}
@media (max-width:640px){#cleanse-body .cln-queues th:nth-child(4),#cleanse-body .cln-queues td:nth-child(4),#cleanse-body .cln-queues th:nth-child(5),#cleanse-body .cln-queues td:nth-child(5){display:none}}
#cleanse-body .cln-share{margin:var(--sp-2,8px) 0}
#cleanse-body .cln-link{min-width:min(100%,28em);flex:1}
#cleanse-body .cln-num input{width:7em}
#cleanse-body label.hint{max-width:100%;min-width:0}
#cleanse-body select,#cleanse-body .cln-num input{max-width:100%;min-width:0}
#cleanse-body .cln-rule{border:1px solid var(--line);border-radius:var(--rad-in,8px);padding:6px 10px;margin:6px 0;background:var(--panel)}
#cleanse-body .cln-rule.off{opacity:.7}
#cleanse-body .cln-cardlist{display:flex;flex-direction:column;gap:var(--sp-2,8px)}
#cleanse-body .cln-card{border:1px solid var(--line);border-radius:var(--rad-in,8px);padding:10px 12px;background:var(--panel)}
#cleanse-body .cln-card.on{border-color:var(--blue);box-shadow:0 0 0 2px var(--s-accent-soft)}
#cleanse-body .cln-card.approved{border-left:4px solid var(--s-ok)}
#cleanse-body .cln-card.rejected{border-left:4px solid var(--s-bad);opacity:.8}
#cleanse-body .cln-card-head{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
#cleanse-body .cln-ba{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--sp-2,8px);margin:6px 0;overflow-wrap:anywhere}
#cleanse-body .cln-evidence{margin:4px 0 6px;padding-left:18px;font-size:13px;color:var(--muted)}
#cleanse-body .cln-acts{margin-top:6px}
#cleanse-body kbd{border:1px solid var(--line);border-bottom-width:2px;border-radius:4px;padding:0 4px;font-size:12px;background:var(--panel-2);color:var(--ink)}
#cleanse-body mark,.cr-page mark{background:var(--s-warn-soft);color:var(--ink);border-radius:3px;padding:0 2px}
#cleanse-body .cln-group{border:1px solid var(--line);border-radius:var(--rad-in,8px);padding:10px 12px;margin:8px 0;background:var(--panel)}
#cleanse-body .cln-members{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:var(--sp-2,8px);margin:6px 0}
#cleanse-body .cln-member{border:1px solid var(--line);border-radius:var(--rad-in,8px);padding:8px 10px;background:var(--panel-2);overflow-wrap:anywhere}
#cleanse-body .cln-member.keep{border-color:var(--s-ok)}
#cleanse-body .cln-act{list-style:none;margin:6px 0 0;padding:0;font-size:13px}
#cleanse-body .cln-act li.diff,#cleanse-body .cln-fields dd.diff{font-weight:700}
#cleanse-body .cln-fields{display:grid;grid-template-columns:auto 1fr;gap:2px 8px;margin:6px 0 0;font-size:13px}
#cleanse-body .cln-fields dt{color:var(--muted)}
#cleanse-body .cln-fields dd{margin:0}
@media(max-width:600px){
  #cleanse-body input,#cleanse-body select,#cleanse-body textarea{font-size:16px}
}
.cr-page .cr-item{border:1px solid var(--line);border-radius:var(--rad-in,8px);padding:12px 14px;margin:10px 0;background:var(--panel)}
.cr-page .cr-names{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:8px;margin:6px 0;overflow-wrap:anywhere}
.cr-page .cr-label{font-size:12px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
.cr-page .cr-yn{display:flex;gap:10px;flex-wrap:wrap;margin-top:8px}
.cr-page .cr-yn label{display:inline-flex;align-items:center;gap:6px;min-height:44px;padding:0 14px;border:1px solid var(--line);border-radius:var(--rad-in,8px);cursor:pointer;background:var(--panel-2)}
.cr-page .cr-yn input{width:20px;height:20px}
.cr-page .cr-jobs{display:grid;gap:8px;margin:12px 0}
.cr-page .cr-jobs p{margin:0}
.cr-page .cr-warn{color:var(--warn)}
.cr-page .cr-send{position:sticky;bottom:0;background:var(--bg);padding:10px 0;display:flex;gap:10px;align-items:center;flex-wrap:wrap}
