/* =========================================================================
   G&G Contractors Dashboard — design system
   Palette validated with the dataviz validator against these exact surfaces:
     light card #FFFFFF · dark card #12171F
   Result: full 8 categorical slots PASS adjacent in both modes; all-pairs
   forms cap at 3 series; #1baf7a / #eda100 / #e87ba4 are sub-3:1 on light,
   so every chart ships a table view (the relief rule).

   The look: sharp edges, minimal ornament, real contrast. Windows are square
   because a square edge reads as precise and a rounded one reads as friendly,
   and this is a tool for money. Everything else follows from that.
   ========================================================================= */

/* ── type ────────────────────────────────────────────────────────────────
   Self-hosted, because the rest of the app is vendored so it runs offline
   and a webfont CDN would be the one thing that could not. Two variable
   faces, latin + latin-ext only: 170KB for the whole type system.

   Inter carries the interface and every figure — it has true tabular
   numerals, which is what keeps a column of money aligned. Space Grotesk
   sets headings only; its squared-off terminals are where the edge in
   "sharp and edgy" actually comes from.
   ──────────────────────────────────────────────────────────────────────── */
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 400 800; font-display: swap;
  src: url('fonts/Inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 400 800; font-display: swap;
  src: url('fonts/Inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Space Grotesk'; font-style: normal; font-weight: 500 700; font-display: swap;
  src: url('fonts/SpaceGrotesk-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Space Grotesk'; font-style: normal; font-weight: 500 700; font-display: swap;
  src: url('fonts/SpaceGrotesk-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* The phone app's Workshop type (2026-10-07): Archivo (variable - weight
   400-900, width 62-125%) for everything, Archivo Black for titles and big
   figures. The desk uses Archivo too since 2026-10-08, for the phone's label
   voice in every theme, and both faces in its Workshop theme.
   Google Fonts, SIL Open Font License, fetched 2026-10-07 (archivo v25,
   archivoblack v23). Neither subset has the arrow glyphs, so arrows are drawn
   as icons, as everywhere else in this app. */
@font-face {
  font-family: 'Archivo'; font-style: normal; font-weight: 400 900; font-stretch: 62% 125%; font-display: swap;
  src: url('fonts/Archivo-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Archivo'; font-style: normal; font-weight: 400 900; font-stretch: 62% 125%; font-display: swap;
  src: url('fonts/Archivo-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Archivo Black'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('fonts/ArchivoBlack-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Archivo Black'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('fonts/ArchivoBlack-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* The three desktop themes' type (2026-10-08). Only a desk set to the matching
   theme loads these; the default theme still names only Inter and Space
   Grotesk. Google Fonts, SIL Open Font License, fetched 2026-10-08
   (instrumentsans v4, ibmplexsans v23, ibmplexsanscondensed v15,
   ibmplexmono v20, geist v5, geistmono v6, sora v17).

   Fieldstone: Instrument Sans (variable - weight 400-700, width 75-100%).
   Its latin subset has ↑ ↓ − and tnum. */
@font-face {
  font-family: 'Instrument Sans'; font-style: normal; font-weight: 400 700; font-stretch: 75% 100%; font-display: swap;
  src: url('fonts/InstrumentSans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Instrument Sans'; font-style: normal; font-weight: 400 700; font-stretch: 75% 100%; font-display: swap;
  src: url('fonts/InstrumentSans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Blueprint: IBM Plex Sans, IBM Plex Sans Condensed, IBM Plex Mono. Google
   now serves Plex Sans as one variable file (weight 100-700) whatever weights
   are asked for, so it is one face here, not three. Condensed and Mono are
   static, one file per weight. None of the three has a tnum feature; it does
   not need one, because their default figures are already tabular. All three
   latin subsets have ↑ ↓ −. */
@font-face {
  font-family: 'IBM Plex Sans'; font-style: normal; font-weight: 400 600; font-display: swap;
  src: url('fonts/IBMPlexSans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans'; font-style: normal; font-weight: 400 600; font-display: swap;
  src: url('fonts/IBMPlexSans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans Condensed'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('fonts/IBMPlexSansCondensed-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans Condensed'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('fonts/IBMPlexSansCondensed-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans Condensed'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('fonts/IBMPlexSansCondensed-600-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans Condensed'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('fonts/IBMPlexSansCondensed-600-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans Condensed'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('fonts/IBMPlexSansCondensed-700-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans Condensed'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('fonts/IBMPlexSansCondensed-700-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('fonts/IBMPlexMono-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('fonts/IBMPlexMono-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('fonts/IBMPlexMono-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('fonts/IBMPlexMono-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('fonts/IBMPlexMono-600-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('fonts/IBMPlexMono-600-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Obsidian: Geist (variable - weight 400-600), Geist Mono (variable - weight
   500-600), Sora (variable - weight 300-600). Geist and Geist Mono latin have
   ↑ ↓ −; Geist has tnum, Geist Mono has none and needs none (monospaced).
   Sora's latin subset has − and tnum but NOT ↑ or ↓, although the range
   below lists them (Google's standard latin range, as with Archivo): in a
   Sora run those two fall through to the next face in the stack. */
@font-face {
  font-family: 'Geist'; font-style: normal; font-weight: 400 600; font-display: swap;
  src: url('fonts/Geist-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Geist'; font-style: normal; font-weight: 400 600; font-display: swap;
  src: url('fonts/Geist-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Geist Mono'; font-style: normal; font-weight: 500 600; font-display: swap;
  src: url('fonts/GeistMono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Geist Mono'; font-style: normal; font-weight: 500 600; font-display: swap;
  src: url('fonts/GeistMono-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Sora'; font-style: normal; font-weight: 300 600; font-display: swap;
  src: url('fonts/Sora-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Sora'; font-style: normal; font-weight: 300 600; font-display: swap;
  src: url('fonts/Sora-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Ledger (2026-10-09): Newsreader, variable - weight 400-700 and an optical
   size axis, so a 40px figure gets the display cut. Google Fonts, SIL Open
   Font License, newsreader v26, fetched 2026-10-09. Titles and figures only;
   Ledger's text is Archivo, already here. Its latin subset has − and tnum,
   and lining figures by default, but NOT ↑ or ↓, although the range lists
   them (Google's standard latin range): Ledger's stack puts Archivo next, so
   an arrow in a title is drawn by Archivo. */
@font-face {
  font-family: 'Newsreader'; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url('fonts/Newsreader-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Newsreader'; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url('fonts/Newsreader-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* Type sizes and layers (2026-09-30, assessment V1, plan #53). Every
     font-size and z-index in this file and in assets/*.js is one of these;
     check_js rule 19 fails on a new literal. The values are exactly the ones
     in use when they were introduced, so nothing moved; bringing the
     half-pixel steps together is a later design decision, made here. */
  --fs-1: 9px;
  --fs-2: 10px;
  --fs-3: 10.5px;
  --fs-4: 11px;
  --fs-5: 11.5px;
  --fs-6: 12px;
  --fs-7: 12.5px;
  --fs-8: 13px;
  --fs-9: 13.5px;
  --fs-10: 14px;
  --fs-11: 14.5px;
  --fs-12: 15px;
  --fs-13: 15.5px;
  --fs-14: 16px;
  --fs-15: 16.5px;
  --fs-16: 17px;
  --fs-17: 19px;
  --fs-18: 20px;
  --fs-19: 21px;
  --fs-20: 23px;
  --fs-21: 24px;
  --fs-22: 28px;
  --fs-23: 30px;
  --fs-24: 40px;
  --z-behind: -1;
  --z-raised: 1;
  --z-sticky-col: 1;
  --z-sticky-head: 2;
  --z-sticky-corner: 3;
  --z-sticky-corner-top: 4;
  --z-hovercard-host: 20;
  --z-hovercard: 21;
  --z-topbar: 30;
  --z-scrim: 55;
  --z-sidebar: 60;
  --z-env-bar: 90;
  --z-modal: 100;
  --z-pick: 150;      /* a pick-list's search list, over the dialog its select is in */
  --z-toast: 200;
  --z-env-frame: 1000;
  /* The phone app's titles (Workshop, 2026-10-07): a job's name and the
     Photos sign in Archivo Black at 32, a tab's large title and Home's
     greeting at 40. */
  --fs-ph-title: 32px;
  --fs-ph-large: 40px;
  color-scheme: light;

  /* — brand ramp (from the G&G contract documents' #1F497D, modernised) — */
  --brand-900: #0A1F41;
  --brand-800: #0F2E5C;   /* primary: sidebar, primary buttons, logo */
  --brand-700: #163E7A;
  --brand-600: #1E5099;
  --brand-500: #2A78D6;   /* == series-1, ties charts to the brand */
  --brand-200: #C9DDF7;
  --brand-100: #E8F0FB;
  --brand-50:  #F4F8FD;
  --accent:    #0E8FA8;   /* UI chrome only (focus, active rail) — never a series */

  /* — surfaces —
     The ground is deliberately several steps below the card. The old
     #F4F6F9 sat so close to white that a card had to be found rather than
     seen; at #E9EDF3 the same card reads as a lit panel on a dark table,
     which is where the contrast in "subtle, smart and edgy" comes from.
     It stops there — a darker ground would be glare, not contrast. */
  --plane:     #E9EDF3;
  --surface:   #FFFFFF;
  --surface-2: #F5F7FA;
  --surface-3: #EAEEF4;
  --sidebar:   #0B1220;
  --sidebar-2: #070C16;
  --overlay:   rgba(8, 14, 26, .55);

  /* — ink — */
  --ink:       #0B111F;
  --ink-2:     #414D63;
  /* #667285, not #6E7B90 (2026-09-30, assessment #42): axe-core measured the old
     grey at 4.28:1 on white and 4.07:1 on the table rows' #F8F9FC - under the
     4.5:1 WCAG AA needs for text this size, on every screen (493 places).
     Now 4.87:1 and 4.63:1. Dark mode's #8795A9 already passes (4.9-6.5:1).
     #5E6A7D since 2026-10-06: #667285 read 4.15:1 on the grey ground itself
     (--plane, under the topbar's Search and the page notes) - now 4.6:1 there,
     5.5:1 on white. */
  --ink-muted: #5E6A7D;
  --ink-on-brand: #FFFFFF;
  /* Links and the brand blue used as TEXT (2026-10-06). --brand-600/700 have
     no dark values - they are the brand - so as text they read dark navy on
     the dark ground: a client's phone number at 2.3:1, the schedule's job tags
     at 1.4:1 (the contrast sweep of every page, both themes). Text uses this,
     or --info-text where it was --brand-700 (the same #163E7A in light). */
  --link: #1E5099;

  /* — lines —
     A sharp design leans on its lines, so they are a step stronger than
     before. This is the difference between edges you can see and edges you
     have to assume. */
  --hairline:  #DCE2EB;
  --hairline-strong: #C3CCDA;
  --grid:      #EAEEF4;
  --axis:      #C3CCDA;

  /* — status (fixed tokens, never themed, never reused as a series) — */
  --good:     #0CA30C;
  --warning:  #FAB219;
  --serious:  #EC835A;
  --critical: #D03B3B;
  --good-text: #006300;
  --critical-text: #B02525;
  /* Warning, serious and info were hardcoded at the pill instead of themed
     here, and their dark values existed only under [data-theme="dark"] — so
     anyone on a dark OS who never touched the toggle read #8A5B00 on a dark
     ground at about 1.6:1. Tokens, defined in all three scopes, like the two
     above already were. */
  --warning-text: #8A5B00;
  --serious-text: #A54A22;
  --info-text: #163E7A;
  --good-bg:     #E8F7E8;
  --warning-bg:  #FEF5E0;
  --serious-bg:  #FDEEE7;
  --critical-bg: #FBEAEA;
  --neutral-bg:  #EEF2F7;
  /* The test site's own colours are declared with the rest of it, near the
     end of this file ("the test site"), under :root[data-env="test"] only. */

  /* — categorical series (validated order — never cycle, never generate) — */
  --series-1: #2a78d6;  /* blue    */
  --series-2: #eb6834;  /* orange  */
  --series-3: #1baf7a;  /* aqua    */
  --series-4: #eda100;  /* yellow  */
  --series-5: #e87ba4;  /* magenta */
  --series-6: #008300;  /* green   */
  --series-7: #4a3aa7;  /* violet  */
  --series-8: #e34948;  /* red     */
  --series-muted: #B8C2D0;

  /* — diverging (favourable ↔ unfavourable) —
     Green, not the brand blue. Favourable read as blue on a chart while the
     very same figure read green as a number two inches away on the summary
     page: "Under budget" bars beside a green variance KPI. One meaning, one
     colour. The trade-off is deliberate and known — green/red bars are the
     pair red-green colour blindness collapses, so ANY chart using these must
     also carry a non-colour cue (the variance ranking is sorted and signed,
     and its labels state the direction). Never let colour be the only
     difference between favourable and unfavourable. */
  --diverge-pos: #0CA30C;
  --diverge-neg: #d03b3b;
  --diverge-mid: #EDEFF2;

  /* — geometry —
     Sharp. One value for nearly everything: a 2px edge is square enough to
     read as a hard corner at any size and just soft enough not to alias on a
     low-DPI monitor. The scale is kept so a component can still say which
     kind of corner it wants, but the values now barely differ — that
     sameness is the point. */
  --r-sm: 2px;  --r-md: 2px;   --r-lg: 3px;   --r-xl: 4px;
  --r-pill: 3px;                /* chips and status pills: squared-off tags */
  --sp-1: 4px;  --sp-2: 8px;   --sp-3: 12px;  --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px;  --sp-7: 48px;

  /* Shadows are tight and low. A sharp design gets its depth from the
     hairline and the surface step, not from a soft glow underneath. */
  --shadow-sm: 0 1px 1px rgba(11, 17, 31, .04);
  --shadow-md: 0 1px 2px rgba(11, 17, 31, .06), 0 6px 16px rgba(11, 17, 31, .06);
  --shadow-lg: 0 2px 6px rgba(11, 17, 31, .10), 0 24px 60px rgba(11, 17, 31, .18);

  /* — motion —
     One easing curve and three durations, so everything in the app
     accelerates the same way. --ease-out overshoots nothing; it decelerates
     hard, which is what makes a panel feel like it snapped into place
     rather than floated there. */
  --ease:     cubic-bezier(.4, 0, .2, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --dur-1: .12s;   /* a hover, a colour */
  --dur-2: .2s;    /* a panel opening */
  --dur-3: .34s;   /* a whole screen */

  --sidebar-w: 248px;
  --topbar-h: 60px;

  /* The page gutter, in one place. The topbar and the view both read it, so
     the page title can never drift out of line with the page heading. It
     grows with the viewport and is stepped down explicitly for tablet and
     phone, where 44px of margin would be 12% of the screen. */
  --gutter: clamp(20px, calc(1.6 * var(--vw)), 44px);
  --page-max: 2560px;

  /* — screen scale —
     The whole layout is drawn at --ui-scale, which initScale() in ui.js sets
     from the width of the window: 1 on a 1920px screen, 0.8 on a 1536px
     laptop - and 1, never more, on anything wider (2026-09-29: a big screen
     shows more, not the same thing bigger). See the note there for why and
     for the floors that keep text readable.

     `zoom` scales viewport units along with everything else — 100vh at 0.8
     comes out at 80% of the window, and a dialog capped at 90vh stopped short
     of the bottom while one at 1.33 ran off it. So NOTHING in this app uses
     vh, vw or dvh directly. These three divide the scale back out:
     calc(90 * var(--vh)) is 90% of the real window at any scale. */
  --ui-scale: 1;
  --vh:  calc(1vh  / var(--ui-scale));
  --vw:  calc(1vw  / var(--ui-scale));
  --dvh: calc(1dvh / var(--ui-scale));
  zoom: var(--ui-scale);

  --font: 'Inter', ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --font-display: 'Space Grotesk', 'Inter', ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: ui-monospace, "Cascadia Mono", Consolas, monospace;

  /* ── the desk's themes (2026-10-08) ──────────────────────────────────────
     Sagar picked five desk themes for anyone to choose from, and Ledger made
     six (2026-10-09): Classic (these values), Workshop, Ledger, Fieldstone,
     Blueprint and Obsidian ("The desk's themes", near the end). A theme
     is THESE tokens and a few signature details - never new layout - so every
     rule below draws through them. The status colours above are fixed in
     every theme; nothing here may re-colour good, warning, serious or critical.
     Mockups and the panel's review: docs/DESKTOP-REVIEW-2026-10-08.md. */
  --font-num: var(--font);                      /* table figures */
  /* the phone's label voice - small capitals, 112.5% wide - for section
     headings, the running head and the date chip, in every theme */
  --font-label: 'Archivo', var(--font);
  --label-stretch: 112.5%;
  --label-weight: 700;
  --label-spacing: .08em;
  /* control borders: inputs, selects, secondary buttons. Cards and rows keep
     the faint --hairline; a control has to be found by its edge (3:1). */
  --control-line: #858FA2;
  --sidebar-ink: #AEBED4;
  --sidebar-ink-hover: #FFFFFF;
  --sidebar-muted: #848D9C;
  --sidebar-hover: rgba(255, 255, 255, .06);
  --sidebar-active-bg: #212734;
  --sidebar-active-ink: #FFFFFF;
  --sidebar-line: rgba(255, 255, 255, .08);
  --logo-ink: #FFFFFF;
  /* Argus, the software company the dashboard is built on (2026-10-12). Its
     brand guide allows its logo in two colours only: its navy, and white.
     --argus-ink is the one on the sidebar (white; navy where a theme's
     sidebar is light), --argus-plane-ink the one on the page (navy; white
     in dark mode). */
  --argus-navy: #1F2747;
  --argus-white: #FFFFFF;
  --argus-ink: var(--argus-white);
  --argus-plane-ink: var(--argus-navy);
  --primary: var(--brand-800);
  --primary-hover: var(--brand-700);
  --primary-ink: #FFFFFF;
  --primary-line: var(--primary);
  --btn-line-w: 1px;
  --btn-shadow: none;
  /* "you are here": the phone's tab light, on its side, by the current page */
  --lamp: #FFD60A;
  --lamp-ring: #8A6D00;
  --tab-ink: var(--ink);
  --tab-line: var(--accent);
  --head-bg: var(--surface);                    /* a table's heading row */
  --band-bg: var(--surface-3);                  /* a group band inside a table */
  --card-line: var(--hairline);
  --card-line-w: 1px;
  --card-shadow: var(--shadow-sm);
  --card-shadow-hover: var(--shadow-md);
  --plane-texture: none;
  --topbar-bg: #FBFCFD;
  --topbar-ink: var(--ink);
  --topbar-muted: var(--ink-muted);
  --topbar-line: var(--hairline);
  /* "Waiting on you": the phone's needs-you band - tape, never a second amber */
  --attention-bg: var(--warning-bg);
  --attention-line: var(--warning);
  --attention-ink: var(--ink);
  --count-bg: var(--ink);
  --count-ink: #FFD60A;
  --tape-ink: #0B111F;
  --tape-fill: #FFD60A;
  --chip-bg: var(--ink);                        /* the date chip */
  --chip-ink: #FFFFFF;
  /* the job tag (GG0001 ● ACTIVE): a flat tag in Classic */
  --plate-bg: var(--surface-2);
  --plate-line: var(--control-line);
  --plate-ink: var(--ink);
  --queue-bg: #FFD60A;                          /* a queue's count, as on the phone's tabs */
  --queue-ink: #0B0B0B;
  --lamp-dim: #C3CCDA;                          /* a status lamp that is off */
  --lamp-dim-ring: #858FA2;
}

/* dark values declared under BOTH scopes so the toggle wins either way */
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) { color-scheme: dark;
    /* Each area a visible step apart (Sagar, 2026-10-08: "some of the headers
       and tables start to run together on the dark mode"): page, card, the
       table's heading row and a group band are now ~1.2:1 apart, not ~1.1. */
    --plane:#07090D; --surface:#131922; --surface-2:#1A212C; --surface-3:#232C39;
    --sidebar:#0F1A2E; --sidebar-2:#0A1322; --overlay:rgba(0,0,0,.66);
    --ink:#E6EAF0; --ink-2:#B7C1D0; --ink-muted:#8795A9;
    --hairline:#2A3443; --hairline-strong:#3B4757; --grid:#1E2632; --axis:#3D4857;
    --brand-100:#16283F; --brand-200:#1D3B5C; --brand-50:#121C2A;
    --good-text:#4FC96A; --critical-text:#E66767; --link:#79AEEF;
    --control-line:#5F6D80; --sidebar-active-bg:#1C2940; --primary:#2F6FCB; --primary-hover:#3B7CDA;
    --head-bg:var(--surface-2); --topbar-bg:#10151C; --chip-bg:#E6EAF0; --chip-ink:#07090D;
    --count-bg:#FFD60A; --count-ink:#07090D; --tape-ink:#07090D; --lamp-dim:#3A4250; --lamp-dim-ring:#5F6D80;
    --warning-text:#F5C563; --serious-text:#F0A585; --info-text:#8FBCF0;
    --good-bg:#102412; --warning-bg:#2A210E; --serious-bg:#2B1A13; --critical-bg:#2C1414; --neutral-bg:#1F2732;
    --series-1:#3987e5; --series-2:#d95926; --series-3:#199e70; --series-4:#c98500;
    --series-5:#d55181; --series-6:#008300; --series-7:#9085e9; --series-8:#e66767;
    --series-muted:#4A5768;
    --diverge-pos:#4FC96A; --diverge-neg:#e66767; --diverge-mid:#2A323E;
    --shadow-sm:0 1px 1px rgba(0,0,0,.5);
    --shadow-md:0 1px 2px rgba(0,0,0,.45), 0 6px 16px rgba(0,0,0,.45);
    --shadow-lg:0 2px 6px rgba(0,0,0,.55), 0 24px 60px rgba(0,0,0,.62);
    --argus-plane-ink:var(--argus-white);
  }
}
:root[data-theme="dark"] { color-scheme: dark;
  --plane:#07090D; --surface:#131922; --surface-2:#1A212C; --surface-3:#232C39;
  --sidebar:#0F1A2E; --sidebar-2:#0A1322; --overlay:rgba(0,0,0,.66);
  --ink:#E6EAF0; --ink-2:#B7C1D0; --ink-muted:#8795A9;
  --hairline:#2A3443; --hairline-strong:#3B4757; --grid:#1E2632; --axis:#3D4857;
  --brand-100:#16283F; --brand-200:#1D3B5C; --brand-50:#121C2A;
  --good-text:#4FC96A; --critical-text:#E66767; --link:#79AEEF;
  --control-line:#5F6D80; --sidebar-active-bg:#1C2940; --primary:#2F6FCB; --primary-hover:#3B7CDA;
  --head-bg:var(--surface-2); --topbar-bg:#10151C; --chip-bg:#E6EAF0; --chip-ink:#07090D;
  --count-bg:#FFD60A; --count-ink:#07090D; --tape-ink:#07090D; --lamp-dim:#3A4250; --lamp-dim-ring:#5F6D80;
  --warning-text:#F5C563; --serious-text:#F0A585; --info-text:#8FBCF0;
  --good-bg:#102412; --warning-bg:#2A210E; --serious-bg:#2B1A13; --critical-bg:#2C1414; --neutral-bg:#1F2732;
  --series-1:#3987e5; --series-2:#d95926; --series-3:#199e70; --series-4:#c98500;
  --series-5:#d55181; --series-6:#008300; --series-7:#9085e9; --series-8:#e66767;
  --series-muted:#4A5768;
  --diverge-pos:#4FC96A; --diverge-neg:#e66767; --diverge-mid:#2A323E;
  --shadow-sm:0 1px 1px rgba(0,0,0,.5);
  --shadow-md:0 1px 2px rgba(0,0,0,.45), 0 6px 16px rgba(0,0,0,.45);
  --shadow-lg:0 2px 6px rgba(0,0,0,.55), 0 24px 60px rgba(0,0,0,.62);
  --argus-plane-ink:var(--argus-white);
}

/* ── reset ───────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; font-family: var(--font); font-size: var(--fs-10); line-height: 1.5;
  color: var(--ink); background: var(--plane);
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
  /* Inter ships an alternate single-storey a and a disambiguated 1/l — both
     read cleaner at interface sizes. cv11 also fixes the l/I confusion that
     matters in a project code like GG0011. */
  font-feature-settings: 'cv11', 'ss01';
  text-rendering: optimizeLegibility;
}
h1,h2,h3,h4,p,figure { margin: 0; }
/* Headings carry the display face. This is the only place Space Grotesk is
   used — it sets the tone at the top of a screen and then gets out of the way,
   because body copy set in a display face is a headache by the third table. */
h1, h2, h3, .view__title, .kpi__value, .modal__title, .topbar__title {
  font-family: var(--font-display); font-feature-settings: normal;
}
button, input, select, textarea { font: inherit; color: inherit; }
a { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; }
/* ONE focus ring (2026-09-30, assessment Part 6 §5): --accent, 2px, on
   anything that takes focus. The per-control rules below keep only their own
   offset; they had drifted to three different blues. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
/* The same short hover everywhere a hover shows. :where() weighs nothing, so
   any control with its own transition keeps it; this only fills the gaps. */
:where(.btn, .tab, .chip, .seg__btn, .seg button, .alert, .tbl tbody tr) {
  transition: background-color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease);
}
[hidden] { display: none !important; }

/* ── motion ──────────────────────────────────────────────────────────────
   Two gestures, used everywhere: things fade up as they arrive, and fade
   down as they leave. Nothing spins, nothing bounces. The point is that a
   screen change should read as a change rather than as a flicker.
   ──────────────────────────────────────────────────────────────────────── */
@keyframes gg-fade      { from { opacity: 0 } }
@keyframes gg-rise      { from { opacity: 0; transform: translateY(8px) } }
@keyframes gg-rise-sm   { from { opacity: 0; transform: translateY(4px) } }
/* Deliberately a slide and no scale. A dialog that scales in looks marginally
   nicer and makes its WIDTH depend on the animation having finished — which
   on a phone, where the dialog is full-bleed, showed up as a 20px-narrow
   sheet any time the compositor was paused. Vertical movement cannot get a
   layout wrong. */
@keyframes gg-open      { from { opacity: 0; transform: translateY(12px) } }
@keyframes gg-sweep     { from { transform: scaleX(0) } }

/* One switch for the whole system. Anyone who has asked their OS to stop
   animating things gets a dashboard that changes instantly — which for a
   tool full of tables is arguably the better experience anyway.

   `[data-motion="off"]` is the same switch thrown deliberately, by
   `index.html?motion=off`. The capture tool and the check harnesses use it,
   because a headless browser on virtual time never advances the animation
   clock: an entrance sits at frame zero, and a page whose entrance begins at
   `opacity: 0` photographs blank. See initMotion() in ui.js. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important; scroll-behavior: auto !important;
  }
}
:root[data-motion="off"] *,
:root[data-motion="off"] *::before,
:root[data-motion="off"] *::after {
  animation-duration: .001ms !important; animation-iteration-count: 1 !important;
  transition-duration: .001ms !important; scroll-behavior: auto !important;
}

/* ── app shell ───────────────────────────────────────────────────────── */
/* The gradient paints the sidebar's column for the full document height, so a
   long page never shows a white gutter below the sticky 100vh sidebar.
   The starfield that used to live behind the app is gone (2026-10-08: the
   panel found it dated and off-trade; Sagar approved Classic without it). A
   theme may still lay a texture on the page's gutters - Blueprint's drafting
   grid - through --plane-texture. It sits behind every opaque card, so no
   figure is ever read against it.
   The shell is a layer of its own (isolation) so that the texture's z-index
   of -1 means "just above the shell's own background". Without it the grid
   sat behind that background, under the whole app, and never showed
   (Sagar, 2026-10-09: "the blueprint grid isn't coming through"). Dialogs,
   toasts and the test banner hang off <body>, outside it, so nothing that
   has to cover the app moves. */
.app {
  display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: calc(100 * var(--vh));
  background: linear-gradient(to right, var(--sidebar) 0 var(--sidebar-w), var(--plane) var(--sidebar-w));
  position: relative; isolation: isolate;
}
.app::before {
  content: ""; position: fixed; inset: 0; z-index: var(--z-behind); pointer-events: none;
  background: var(--plane-texture);
}

.sidebar {
  background-color: var(--sidebar);
  color: var(--sidebar-ink); display: flex; flex-direction: column;
  position: sticky; top: 0; height: calc(100 * var(--vh)); overflow-y: auto;
  border-right: 1px solid var(--sidebar-line);
  /* the thin scrollbar keeps the chrome from growing a fat grey bar */
  scrollbar-width: thin; scrollbar-color: var(--sidebar-hover) transparent;
}
.sidebar::-webkit-scrollbar { width: 8px; }
.sidebar::-webkit-scrollbar-thumb { background: var(--sidebar-hover); }
.sidebar::-webkit-scrollbar-track { background: transparent; }
/* the logo and the line under it are centred in the corner as one lockup
   (Sagar, 2026-10-09: it "looks a little left aligned") */
.sidebar__brand {
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
  border-bottom: 1px solid var(--sidebar-line);
  text-align: center;
}
.sidebar__brand .logo { margin-inline: auto; }
/* The official logo, in its own colours (2026-10-09, Sagar: "this is the
   official company logo"). It replaces the white cut-out every theme used to
   tint. The same in every theme and mode: its greys and orange are mid-tones,
   so it reads on a dark sidebar and on Ledger's and Blueprint's paper alike.
   tools/make_logo.py makes it from "G&G Logo.png". */
.logo {
  display: block; width: 100%; max-width: 148px; aspect-ratio: 2997 / 1928;
  background: url("logo.png") no-repeat center / contain;
}
/* The phone's label voice: small capitals in Archivo at 112.5% width. It sets
   the four sidebar headings - the same four words the phone's More screen
   uses - the top bar's running head and the date chip, in every theme. */
.label-voice, .sidebar__sub, .nav__label, .nav__label--toggle, .crumbs, .date-chip, .date-cal {
  font-family: var(--font-label); font-stretch: var(--label-stretch);
  font-weight: var(--label-weight); letter-spacing: var(--label-spacing); text-transform: uppercase;
}
.sidebar__sub { margin-top: var(--sp-3); font-size: var(--fs-4); color: var(--sidebar-muted); font-stretch: 100%; white-space: nowrap; }
.nav { padding: var(--sp-4) var(--sp-3) var(--sp-4) var(--sp-4); display: flex; flex-direction: column; gap: 2px; flex: 1; }
.nav__label { padding: var(--sp-4) var(--sp-3) var(--sp-2); font-size: var(--fs-4); color: var(--sidebar-muted); }
/* A section heading you can fold away. It is a <button>, so it is reachable by
   keyboard and announces its own state — a <div> with a click handler would
   be neither. */
.nav__label--toggle {
  display: flex; align-items: center; gap: 6px; width: 100%;
  background: none; border: 0; cursor: pointer; text-align: left;
  font-size: var(--fs-4); color: var(--sidebar-muted);
}
.nav__label--toggle:hover { color: var(--sidebar-ink); }
.nav__label--toggle:focus-visible {
  outline: 2px solid var(--sidebar-ink); outline-offset: -2px; border-radius: 4px;
}
.nav__chev {
  display: grid; place-items: center; width: 12px; height: 12px; flex: none;
  transition: transform .15s ease;
}
.nav__chev svg { width: 12px; height: 12px; }
/* points down when open, right when shut */
.nav__label--toggle[aria-expanded="true"] .nav__chev { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .nav__chev { transition: none; } }
.nav__item {
  display: flex; align-items: center; gap: var(--sp-3); padding: 8px var(--sp-3);
  border-radius: var(--r-sm); color: var(--sidebar-ink); font-weight: 500; cursor: pointer;
  border: 0; background: none; width: 100%; text-align: left; position: relative;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.nav__item:hover { background: var(--sidebar-hover); color: var(--sidebar-ink-hover); text-decoration: none; }
.nav__item.is-active { background: var(--sidebar-active-bg); color: var(--sidebar-active-ink); font-weight: 600; }
/* "You are here" is the phone's tab light turned on its side (2026-10-08, the
   panel's tie-in, in every theme): a small lit lamp in the gutter beside the
   current page, and nothing else in the sidebar lit. It replaces the 2px rail.
   Each theme colours it; Classic's is the phone's caution yellow, 13:1 on the
   sidebar. It comes on rather than appearing, the rail's old gesture. */
.nav__lamp { display: none; }
.nav__item.is-active .nav__lamp {
  display: block; position: absolute; left: -10px; top: 50%; width: 8px; height: 8px; margin-top: -4px;
  border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle at 40% 35%, rgba(255,255,255,.95) 0 1px, transparent 2.5px), var(--lamp);
  box-shadow: 0 0 0 1px var(--lamp-ring), 0 0 6px 1px color-mix(in srgb, var(--lamp) 70%, transparent);
  animation: gg-lamp var(--dur-2) var(--ease-out);
}
@keyframes gg-lamp { from { opacity: 0 } }
/* Blueprint's sheet numbers (S-01, O-02 …) ride in every item, hidden elsewhere */
.nav__sheet { display: none; }
.nav__item svg {
  width: 17px; height: 17px; flex: none; opacity: .62;
  transition: opacity var(--dur-1) var(--ease);
}
.nav__item:hover svg, .nav__item.is-active svg { opacity: 1; }
.nav__item > span:not([class]) { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Counts, as on the phone's tab bar (2026-10-08): caution yellow with black
   numbers for a queue, red for what is waiting on YOU. Round, like the phone's. */
.nav__badge {
  margin-left: auto; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px;
  background: var(--queue-bg); color: var(--queue-ink); font-size: var(--fs-5); font-weight: 800;
  display: grid; place-items: center; font-variant-numeric: tabular-nums;
}
/* Workflow's badge is the one that says someone is waiting on you - red, so it
   is seen from across the room (2026-09-28: "the notification isn't loud
   enough"). The row itself is no longer lit (2026-10-08, Sagar: "I like the
   red badge and the waiting for you combo"): a lit row out-shouted the page
   you were actually on. The badge and the Summary's band carry it now. */
.nav__item--waiting .nav__badge { background: var(--critical); color: #fff; box-shadow: 0 0 0 2px var(--sidebar); }
/* ...and the tile itself glows a dim red while anything waits on you, like
   conditional formatting, and is plain again the moment Workflow is clear
   (Sagar, 2026-10-08: "a dim red highlight around the tile would be fine ...
   it's not there when the workflow is clear"). The same in every theme and
   mode - nothing yellow on it. */
.nav__item.nav__item--waiting {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--critical) 55%, transparent);
  background-color: color-mix(in srgb, var(--critical) 12%, transparent);
}
.nav__item.nav__item--waiting:hover { background-color: color-mix(in srgb, var(--critical) 20%, transparent); }
.nav__item.nav__item--waiting.is-active {
  background-color: color-mix(in srgb, var(--critical) 18%, var(--sidebar-active-bg));
}

/* ── "Waiting on you": the band on the Summary and on Workflow ────────────
   The phone's band, in each theme's materials (2026-10-08, the panel's
   tie-in): the count first in a tile, the words, the phone's nouns as chips,
   and the whole band one click to Workflow. A strip of caution tape marks it,
   because on the phone tape means "needs you" - never a second amber, which
   means a warning. */
.band {
  display: flex; flex-direction: column; margin-bottom: var(--sp-4); overflow: hidden;
  background: var(--attention-bg); color: var(--attention-ink);
  border: 1px solid var(--attention-line); border-radius: var(--r-lg);
  text-decoration: none;
}
a.band:hover { text-decoration: none; }
a.band:hover .band__go { transform: translateX(3px); }
.band__tape {
  display: block; flex: none; height: 6px;
  background: repeating-linear-gradient(-45deg, var(--tape-ink) 0 6px, var(--tape-fill) 6px 12px);
}
.band__tape--foot { display: none; }
.band__row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 10px 14px; }
.band__count {
  flex: none; min-width: 36px; height: 36px; padding: 0 9px; display: grid; place-items: center;
  border-radius: var(--r-md); background: var(--count-bg); color: var(--count-ink);
  font-family: var(--font-display); font-size: var(--fs-19); font-weight: 800; font-variant-numeric: lining-nums;
}
.band__title { font-family: var(--font-display); font-size: var(--fs-12); font-weight: 700; margin-right: 4px; }
.band__chip {
  display: inline-flex; align-items: center; height: 26px; padding: 0 10px;
  border: 1px solid var(--control-line); border-radius: var(--r-pill);
  background: var(--surface); color: var(--ink); font-size: var(--fs-7); font-weight: 650;
  text-decoration: none; white-space: nowrap;
}
a.band__chip:hover { border-color: var(--ink); text-decoration: none; }
.band__go { margin-left: auto; flex: none; width: 22px; height: 22px; transition: transform var(--dur-1) var(--ease); }
.band__go svg { width: 100%; height: 100%; display: block; }
/* Nothing waiting: a calm line, no tape */
.band--clear { background: var(--surface); border-color: var(--card-line); }
.band--clear .band__tape { display: none; }
.band--clear .band__count { background: var(--good-bg); color: var(--good-text); }

/* ── the Workflow page (2026-09-28) ──────────────────────────────────────── */
.wf-chip {
  display: inline-flex; align-items: center; gap: 8px; padding: 4px 6px 4px 10px;
  border-radius: var(--r-pill); background: var(--surface); border: 1px solid var(--control-line);
  color: var(--ink); font-size: var(--fs-8); font-weight: 560; text-decoration: none;
}
.wf-chip:hover { border-color: var(--ink); text-decoration: none; }
.wf-chip .nav__badge, .wf-count { margin-left: 0; display: inline-grid; vertical-align: 2px; }
.wf-group { scroll-margin-top: 72px; }
.wf-cat { scroll-margin-top: 72px; margin-bottom: 8px; }
.wf-cat__title {
  display: flex; align-items: center; gap: 8px; margin: 22px 0 10px;
  font-size: var(--fs-6); color: var(--ink-2);
  font-family: var(--font-label); font-stretch: var(--label-stretch); font-weight: var(--label-weight);
  letter-spacing: var(--label-spacing); text-transform: uppercase;
}
.wf-cat__title .nav__badge { margin-left: 0; }
.wf-cat.is-flagged .wf-cat__title { color: var(--ink); }
/* a card waiting on YOU carries the same strip of tape as the band */
.wf-group.is-flagged { border-top: 0; }
.wf-group.is-flagged::before {
  content: ""; display: block; height: 6px; border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: repeating-linear-gradient(-45deg, var(--tape-ink) 0 6px, var(--tape-fill) 6px 12px);
}
/* System health on the Summary (2026-10-07, the Owner only): one quiet line
   when last night was fine, the findings under it when not */
.notice.health { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 4px 10px; margin-bottom: 16px; }
.health__when { color: var(--ink-muted); }
.notice--critical .health__when, .notice--warning .health__when { color: inherit; opacity: .85; }
.health__more { margin-left: auto; }
.health__items { flex-basis: 100%; margin: 2px 0 0; padding-left: 18px; color: var(--ink); }
.sidebar__foot { padding: var(--sp-4); border-top: 1px solid var(--sidebar-line); }
/* the name card opens "My view" (theme, light or dark, text size) */
.whoami {
  display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; padding: 4px; margin: -4px 0 -4px -4px;
  border: 0; border-radius: var(--r-md); background: none; color: var(--sidebar-ink); cursor: pointer; text-align: left;
}
.whoami:hover { background: var(--sidebar-hover); }
.whoami__avatar {
  width: 30px; height: 30px; border-radius: 50%; flex: none; display: grid; place-items: center;
  background: var(--sidebar-hover); color: var(--sidebar-ink-hover); font-weight: 700; font-size: var(--fs-6);
}
.whoami__name { display: block; font-weight: 600; font-size: var(--fs-8); color: var(--sidebar-ink-hover);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.whoami__role { display: block; font-size: var(--fs-5); color: var(--sidebar-muted); }
.sidebar__legal {
  display: flex; gap: 7px; align-items: center; flex-wrap: wrap;
  margin-top: var(--sp-3); font-size: var(--fs-5); color: var(--sidebar-muted);
}
.sidebar__legal a { color: var(--sidebar-ink); text-decoration: none; }
.sidebar__legal a:hover { color: var(--sidebar-ink-hover); text-decoration: underline; }
.sidebar__foot .btn--ghost { color: var(--sidebar-ink); }
.sidebar__foot .btn--ghost:hover { background: var(--sidebar-hover); color: var(--sidebar-ink-hover); }

/* ── "Powered by Argus" (2026-10-12) ──────────────────────────────────────
   Argus is the software company that owns the dashboard; G&G stays the
   hero. Sagar: "subtle so it doesn't bring attention but we want to make it
   clear this is a product belonging to another company". argusTagHtml() in
   ui.js draws it everywhere, so the words and the rules cannot drift.
   The mark is Argus's official logo (assets/argus-logo.svg, unaltered) used
   as a mask, so it takes exactly one of the two colours its brand guide
   allows. Icon and wordmark always together, side by side; the clear space
   around it is the icon's height (--argus-h), the gap after "Powered by"
   included. "Powered by" speaks in the theme's own label voice. */
.argus-tag {
  --argus-h: 10px;
  display: inline-flex; align-items: center; gap: var(--argus-h);
  color: var(--sidebar-muted); line-height: 1; vertical-align: middle;
}
.argus-tag__by {
  font-family: var(--font-label); font-weight: var(--label-weight); letter-spacing: var(--label-spacing);
  text-transform: uppercase; font-size: var(--fs-2); white-space: nowrap;
}
.argus-mark {
  display: block; flex: none; height: var(--argus-h); aspect-ratio: 88.196 / 21;
  background: var(--argus-ink);
  -webkit-mask: url("argus-logo.svg") center / contain no-repeat;
  mask: url("argus-logo.svg") center / contain no-repeat;
}
/* on the page rather than the sidebar */
.argus-tag--plane { color: var(--ink-muted); }
.argus-tag--plane .argus-mark { background: var(--argus-plane-ink); }
/* The sidebar: the last thing in the menu panel, above the line over your
   name, lined up with the menu, and a step bigger than anywhere else (Sagar,
   2026-10-12: "above the line - as the bottom of the sidebar middle panel",
   "slightly more prominent in that location"). */
.sidebar > .argus-tag {
  --argus-h: 14px; display: flex;
  padding: 0 var(--sp-3) var(--sp-4) calc(var(--sp-4) + var(--sp-3));
}
.sidebar > .argus-tag .argus-tag__by { font-size: var(--fs-4); }

.main { min-width: 0; display: flex; flex-direction: column; }
/* the view grows, so the page's foot sits at the bottom of a short page */
.main > #view { flex: 1 0 auto; }
/* The foot of every desk page (2026-10-12, Sagar's pick "B"): the product
   on the left, "Powered by Argus" on the right, on the view's own track. */
.app-foot { flex: none; padding: 0 var(--gutter) var(--sp-5); }
.app-foot__inner {
  max-width: calc(var(--page-max) - 2 * var(--gutter)); margin-inline: auto;
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); flex-wrap: wrap;
  border-top: 1px solid var(--hairline); padding-top: var(--sp-4);
}
.app-foot__name { font-size: var(--fs-6); color: var(--ink-muted); }
/* Solid, not frosted glass (2026-10-08: the panel found the blur dated, and it
   only ever repeated the page title). */
.topbar {
  height: var(--topbar-h); flex: none; display: flex; align-items: center; gap: var(--sp-4);
  position: sticky; top: 0; z-index: var(--z-topbar); padding: 0;
  background: var(--topbar-bg); color: var(--topbar-ink);
  border-bottom: 1px solid var(--topbar-line);
}
/* The bar's inner padding tracks the view's, so the running head sits directly
   above the page heading instead of drifting out of line as the window grows. */
.topbar__inner {
  display: flex; align-items: center; gap: var(--sp-4);
  width: 100%; max-width: var(--page-max); margin-inline: auto; height: 100%;
  padding-inline: var(--gutter);
}
/* The running head (2026-10-08): where the bar used to repeat the page's own
   title - "Projects" above "Projects" - it now says where you are, in the
   phone's label voice: OPERATIONS › PROJECTS › GG0001 · USAA ICE DAM.
   The last part is the page itself; the parts before it are links. */
.crumbs {
  display: flex; align-items: center; gap: 8px; min-width: 0; font-size: var(--fs-6);
  color: var(--topbar-muted); white-space: nowrap; overflow: hidden;
}
.crumbs a { color: inherit; text-decoration: none; }
.crumbs a:hover { color: var(--topbar-ink); text-decoration: underline; }
.crumbs__sep { opacity: .7; }
.crumbs__here { color: var(--topbar-ink); overflow: hidden; text-overflow: ellipsis; }
/* the running head gives way, never the bar's buttons (a 25px-wide menu
   button on a narrow window, mobile_check 2026-10-08) */
.topbar__inner > .btn, .topbar__inner > .search-btn { flex-shrink: 0; }
@media (max-width: 720px) {
  .crumbs > :not(.crumbs__here) { display: none; }
}
/* A theme may set a plate in the bar instead (Workshop's screwed metal, a
   stone tag in Fieldstone) or a drawing's title block (Blueprint). The DOM
   carries both; the theme shows one. */
.topbar__plate, .titleblock { display: none; }
.topbar__spacer { flex: 1; }
/* a page's saved layout (app.js, 2026-11-02), beside where you are - a narrow
   window keeps its room for the page: saving a layout is for a desk */
.layout-slot { flex: none; margin-left: 8px; }
.layout-slot summary { white-space: nowrap; }
@media (max-width: 760px) { .layout-slot { display: none; } }
.topbar .btn--ghost, .topbar .search-btn { color: var(--topbar-muted); }

/* My view: the six themes as cards, each with a strip of its own colours -
   sidebar, page, accent. The colours here are each theme's, written out,
   because the dialog shows all six at once whatever theme is on. */
.view-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: var(--sp-3); }
.view-card {
  display: flex; flex-direction: column; gap: 6px; padding: 10px; text-align: left; cursor: pointer;
  border: 1px solid var(--control-line); border-radius: var(--r-lg); background: var(--surface); color: var(--ink);
}
.view-card:hover { border-color: var(--ink); }
.view-card.is-on { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent); }
.view-card__swatch { display: grid; grid-template-columns: 1fr 2.4fr 1fr; height: 40px; border-radius: var(--r-md); overflow: hidden;
  border: 1px solid var(--hairline); }
.view-card__name { font-weight: 700; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.view-card__blurb { font-size: var(--fs-6); color: var(--ink-muted); line-height: 1.35; }
.view-card__swatch--classic > :nth-child(1) { background: #0B1220; }
.view-card__swatch--classic > :nth-child(2) { background: #E9EDF3; }
.view-card__swatch--classic > :nth-child(3) { background: #0F2E5C; }
.view-card__swatch--workshop > :nth-child(1) { background: #0B0B0B; }
.view-card__swatch--workshop > :nth-child(2) { background: #ECE9E2; }
.view-card__swatch--workshop > :nth-child(3) { background: #FFD60A; }
.view-card__swatch--ledger > :nth-child(1) { background: #FAF8F3; box-shadow: inset -1px 0 0 #E3DFD5; }
.view-card__swatch--ledger > :nth-child(2) { background: repeating-linear-gradient(transparent 0 7px, #E3DFD5 7px 8px), #F2F0EA; }
.view-card__swatch--ledger > :nth-child(3) { background: #1A1813; }
.view-card__swatch--fieldstone > :nth-child(1) { background: #1F2B27; }
.view-card__swatch--fieldstone > :nth-child(2) { background: #E1E4DD; }
.view-card__swatch--fieldstone > :nth-child(3) { background: #B8652F; }
.view-card__swatch--blueprint > :nth-child(1) { background: #FFFFFF; box-shadow: inset -2px 0 0 #0E1A26; }
.view-card__swatch--blueprint > :nth-child(2) { background: linear-gradient(rgba(29,78,137,.12) 1px, transparent 1px) 0 0 / 8px 8px, #F1F4F7; }
.view-card__swatch--blueprint > :nth-child(3) { background: #0A2038; }
.view-card__swatch--obsidian > :nth-child(1) { background: #1D1E22; }
.view-card__swatch--obsidian > :nth-child(2) { background: #0A0B0D; }
.view-card__swatch--obsidian > :nth-child(3) { background: #A9A2FA; }

/* the date on the Summary: the phone Home's ink chip, then the calendar count */
.date-line { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 8px; }
.date-chip {
  display: inline-flex; align-items: center; height: 24px; padding: 0 9px; border-radius: var(--r-md);
  background: var(--chip-bg); color: var(--chip-ink); font-size: var(--fs-5);
}
.date-cal { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-5); color: var(--ink-2); text-decoration: none; }
.date-cal:hover { color: var(--ink); text-decoration: none; }
.date-cal svg { width: 15px; height: 15px; }

/* The job tag (2026-10-08): the phone plate's words and lamp - GG0001 ● ACTIVE -
   above a project's name. Flat in Classic; each theme draws it in its own
   material, and Workshop and Fieldstone move it into the top bar. The words are
   read out; screws and rivets are decoration. */
.plate {
  display: flex; width: fit-content; align-items: center; gap: 8px; align-self: flex-start; height: 26px; padding: 0 10px;
  margin: 2px 0 8px;
  border: 1px solid var(--plate-line); border-radius: var(--r-md); background: var(--plate-bg); color: var(--plate-ink);
  font-family: var(--font-label); font-stretch: var(--label-stretch); font-weight: var(--label-weight);
  letter-spacing: var(--label-spacing); text-transform: uppercase; font-size: var(--fs-6); white-space: nowrap;
}
.plate__cell { display: inline-flex; align-items: center; gap: 6px; }
.plate__cell + .plate__cell { padding-left: 8px; border-left: 1px solid var(--plate-line); }
.plate__fix { display: none; }
/* the status lamp: lit for good / warning / critical, dim for the rest - the
   phone's mapping exactly. The fixed status colours, never a theme's. */
.lamp { width: 8px; height: 8px; border-radius: 50%; flex: none; display: inline-block;
  background: var(--lamp-dim, #C3CCDA); box-shadow: 0 0 0 1px var(--lamp-dim-ring, #858FA2); }
.lamp--good { background: var(--good); box-shadow: 0 0 0 1px var(--good-text); }
.lamp--warning { background: var(--warning); box-shadow: 0 0 0 1px var(--warning-text); }
.lamp--critical { background: var(--critical); box-shadow: 0 0 0 1px var(--critical-text); }
.lamp--serious { background: var(--serious); box-shadow: 0 0 0 1px var(--serious-text); }

/* ── how wide the page runs ──────────────────────────────────────────────
   This used to be a flat `max-width: 1680px` with no auto margin, so on
   anything wider than about 1930px the surplus all collected on the right
   and the dashboard looked like it had been pushed into a corner. A 2560px
   monitor was showing 630px of empty plane.

   Now it fills the screen up to 2560px and only then centres, and the
   padding grows with the viewport so a big screen gets a proportionate
   margin rather than the same 32px a laptop gets. The cap is not there to
   save space — it is there so a table row on a 34" ultrawide does not
   become a metre-wide line your eye cannot track across. */
.view {
  width: 100%; max-width: var(--page-max); margin-inline: auto;
  padding: var(--gutter);
}
/* Every route change replaces this element, so the animation runs on arrival
   without a line of JavaScript. It is 200ms and 6px — enough to register
   that the screen changed, not enough to wait for. */
/* No fill mode on purpose: a lingering `transform` — even `none` — makes the
   view a containing block for anything `position: fixed` inside it. */
.view { animation: gg-rise-sm var(--dur-2) var(--ease-out); }
.view__head { display: flex; align-items: flex-start; gap: var(--sp-4); margin-bottom: var(--sp-5); flex-wrap: wrap; }
.view__title { font-size: var(--fs-20); font-weight: 600; letter-spacing: -.02em; }
.view__sub { color: var(--ink-2); margin-top: 3px; }
/* A job's status is the first thing its page answers (assessment H1, 2026-09-30),
   so in a page header the pill matches the job code beside it rather than the
   11px size tables use. Only the job page puts a pill in its header. */
.view__sub .pill { height: 24px; padding: 0 10px; font-size: var(--fs-7); }
.view__sub .pill__dot { width: 7px; height: 7px; }
.view__actions { margin-left: auto; display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* ── buttons ─────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: 34px; padding: 0 14px; border-radius: var(--r-sm); border: var(--btn-line-w) solid var(--control-line);
  background: var(--surface); color: var(--ink); font-weight: 550; font-size: var(--fs-9);
  letter-spacing: -.005em; cursor: pointer; white-space: nowrap;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
              box-shadow var(--dur-1) var(--ease), transform .06s var(--ease);
}
.btn:hover { background: var(--surface-2); border-color: var(--ink); text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn[disabled] { opacity: .45; cursor: not-allowed; }
.btn[disabled]:active { transform: none; }
.btn svg { width: 15px; height: 15px; }
.btn--primary { background: var(--primary); border-color: var(--primary-line); color: var(--primary-ink); box-shadow: var(--btn-shadow); }
.btn--primary:hover {
  background: var(--primary-hover); border-color: var(--primary-line);
  box-shadow: var(--btn-shadow), 0 2px 10px color-mix(in srgb, var(--primary) 32%, transparent);
}
.btn--danger { background: var(--critical); border-color: var(--critical); color: #fff; }
.btn--danger:hover {
  background: #B92F2F; border-color: #B92F2F;
  box-shadow: 0 2px 10px color-mix(in srgb, var(--critical) 34%, transparent);
}
.btn--ghost { background: transparent; border-color: transparent; color: var(--ink-2); }
.btn--ghost:hover { background: var(--surface-3); border-color: transparent; color: var(--ink); }
.btn--sm { height: 28px; padding: 0 10px; font-size: var(--fs-7); }
.btn--icon { width: 34px; padding: 0; }
.btn--icon.btn--sm { width: 28px; }

/* ── cards ───────────────────────────────────────────────────────────── */
.card {
  background: var(--surface); border: var(--card-line-w) solid var(--card-line);
  border-radius: var(--r-lg); box-shadow: var(--card-shadow);
  transition: border-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.card__head {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--hairline);
}
.card__title { font-family: var(--font-display); font-size: var(--fs-12); font-weight: 600; letter-spacing: -.01em; }
.card__sub { font-size: var(--fs-7); color: var(--ink-muted); margin-top: 1px; }
.card__tools { margin-left: auto; display: flex; align-items: center; gap: var(--sp-2); }
.card__body { padding: var(--sp-5); }
.card__body--flush { padding: 0; }

/* ── KPI tiles ───────────────────────────────────────────────────────── */
.kpi-row {
  display: grid; gap: var(--sp-4); margin-bottom: var(--sp-5);
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  align-items: stretch;
}
/* ── the headline row ─────────────────────────────────────────────────────
   Three tiles, always three across, set larger than the rest. The summary
   used to be eleven identical boxes, which told you every figure on it
   mattered equally — and left an orphan on a third row at most widths. These
   three are the ones an owner opens the page for; everything under them is
   the working that produced them. */
.kpi-row--lead {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-bottom: var(--sp-3);
}
.kpi-row--lead .kpi { padding: var(--sp-5) var(--sp-5) var(--sp-4); }
.kpi-row--lead .kpi__value { font-size: var(--fs-24); letter-spacing: -.035em; margin-top: 10px; }
/* Label and foot are the SAME style on every tile, whatever its size
   (2026-09-30, assessment V2): the lead row's 11px/13px against 10.5px/12.5px
   below read as a mistake, not a hierarchy. Size lives in the number. */
/* The supporting tiles fill their row exactly, rather than whatever auto-fit
   lands on. Auto-fit gave five then three, so the row ended in a gap two tiles
   wide and the layout looked like it had lost something.

   The column count follows the tile count — eight go 4 + 4, five go across in
   one — so the same rule serves the company summary and a job, which have
   different numbers of them. `:has()` is already load-bearing elsewhere in
   this file. */
.kpi-row--lead + .kpi-row { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.kpi-row--lead + .kpi-row:has(> :nth-child(8):last-child) {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
.kpi-row--lead + .kpi-row:has(> :nth-child(5):last-child) {
  grid-template-columns: repeat(5, minmax(0, 1fr));
}
@media (max-width: 1180px) {
  .kpi-row--lead + .kpi-row,
  .kpi-row--lead + .kpi-row:has(> :nth-child(8):last-child),
  .kpi-row--lead + .kpi-row:has(> :nth-child(5):last-child) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 900px) {
  /* below this a third of the width is too narrow for a 40px figure */
  .kpi-row--lead { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
  .kpi-row--lead .kpi__value { font-size: var(--fs-23); }
}
@media (max-width: 720px) {
  .kpi-row--lead { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-2); }
  .kpi-row--lead .kpi { padding: var(--sp-3); }
  .kpi-row--lead .kpi__value { font-size: var(--fs-21); }
  /* the third tile spans the pair rather than sitting half-width and alone */
  .kpi-row--lead .kpi:last-child:nth-child(odd) { grid-column: 1 / -1; }
}
.kpi {
  background: var(--surface); border: var(--card-line-w) solid var(--card-line); border-radius: var(--r-lg);
  padding: var(--sp-4) var(--sp-5); box-shadow: var(--card-shadow); position: relative; overflow: hidden;
  transition: border-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
/* A hairline of brand colour along the top edge, drawn in only on hover.
   At rest the row of tiles is perfectly quiet; the one under the cursor
   picks up a single sharp line. */
/* Inside the box, not `inset: -1px`: a top-5 tile lifts its own `overflow:
   hidden` while its panel is open, and a bar hung outside the border would
   then poke out at three edges. */
/* No accent bar on hover any more (2026-10-08, the panel: an admin-template
   flourish); the tile lifts by its theme's own shadow. */
.kpi:hover { box-shadow: var(--card-shadow-hover); }
/* ── a tile you can interrogate ──────────────────────────────────────────
   Hover or focus a money tile and it shows what the number is made of. The
   base .kpi clips its contents so the accent bar stays inside its corners,
   so the clipping is lifted only while the panel is actually open. */
/* ═══════════════════════════════════════════════════════════════════════════
   The filter strip.

   One row, inside the card, resting on a hairline. It should read as part of
   the table rather than as a control panel bolted above it — so no borders,
   no boxes, nothing raised. The controls stay invisible until the cursor is
   near them, and only a filter that is actually DOING something gets colour.
   That way a page at rest is calm, and the one thing you have changed is the
   one thing that catches the eye.
   ═══════════════════════════════════════════════════════════════════════════ */
.toolbar {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 9px var(--sp-4); min-height: 46px; flex-wrap: wrap;
  border-bottom: 1px solid var(--hairline);
  background: linear-gradient(var(--surface-2), var(--surface));
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}
.toolbar__count {
  font-size: var(--fs-6); color: var(--ink-muted); font-variant-numeric: tabular-nums;
  white-space: nowrap; padding-right: 2px;
}
/* A grid cell says whether its save went in (plan #52): a green "Saved" that
   fades once the database has it; red, with the reason on hover, if refused.
   Words, not a tick: ✓ is not in the webfont's subset. */
td[data-cell] { position: relative; }
td[data-cell="saved"]::after {
  content: "Saved"; position: absolute; top: -1px; right: 2px; font-size: var(--fs-1); font-weight: 700;
  line-height: 1; padding: 1px 3px; border-radius: 3px; color: var(--good-text); background: var(--good-bg);
  pointer-events: none; animation: cell-saved 2.6s ease forwards;
}
@keyframes cell-saved { 0%, 70% { opacity: 1; } 100% { opacity: 0; } }
td[data-cell="refused"] { box-shadow: inset 0 0 0 2px var(--critical); }
td[data-cell="refused"]::after {
  content: "Not saved"; position: absolute; top: -1px; right: 2px; font-size: var(--fs-1); font-weight: 700;
  line-height: 1; padding: 1px 3px; border-radius: 3px; color: #fff; background: var(--critical); pointer-events: none;
}
@media (prefers-reduced-motion: reduce) { td[data-cell="saved"]::after { animation: none; } }

/* Search, Ctrl+K (plan #54): the top-bar button and the dialog's list */
.search-btn { gap: 8px; color: var(--ink-muted); border: 1px solid var(--control-line); border-radius: var(--r-pill);
              height: 32px; padding: 0 8px 0 12px; font-size: var(--fs-6); }
.search-btn kbd { font: inherit; font-size: var(--fs-3); color: var(--ink-muted); background: var(--surface-3);
                  border-radius: 5px; padding: 1px 6px; }
@media (max-width: 760px) { .search-btn__label, .search-btn kbd { display: none; } .search-btn { padding: 0 9px; } }
/* two classes: .toolbar__search comes later in this file, and its flex basis
   would otherwise make the box 200px TALL in the dialog's column */
.toolbar__search.search__box { flex: none; width: 100%; height: 38px; }
.search__results { max-height: min(60vh, 520px); overflow-y: auto; display: flex; flex-direction: column; gap: 2px; }
.search__group { font-size: var(--fs-3); font-weight: 650; letter-spacing: .04em; text-transform: uppercase;
                 color: var(--ink-muted); padding: 10px 10px 4px; }
.search__hit { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 2px 10px;
               text-align: left; border: 0; background: none; font: inherit; color: var(--ink);
               padding: 7px 10px; border-radius: var(--r-sm); cursor: pointer; }
.search__hit:hover, .search__hit.is-active { background: var(--brand-50); }
.search__title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.search__sub { grid-column: 1 / -1; font-size: var(--fs-5); color: var(--ink-muted);
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Active-filter chips beside the count (plan #51): the same "set" colours as a
   select with a filter chosen, so on is one look everywhere. */
.toolbar__chips { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.fchip {
  display: inline-flex; align-items: center; gap: 4px; height: 26px; padding: 0 3px 0 10px;
  border-radius: var(--r-pill); font-size: var(--fs-5); font-weight: 620; white-space: nowrap;
  background: var(--brand-50); color: var(--info-text);
  border: 1px solid color-mix(in srgb, var(--brand-500) 34%, transparent);
}
.fchip__label { font-weight: 500; }
.fchip__x {
  display: inline-grid; place-items: center; width: 20px; height: 20px; padding: 0; border: 0;
  border-radius: 50%; background: none; color: inherit; cursor: pointer;
}
.fchip__x svg { width: 12px; height: 12px; }
.fchip__x:hover { background: color-mix(in srgb, var(--brand-500) 18%, transparent); }

/* Search: a quiet well that lifts and takes the brand colour on focus. */
.toolbar__search {
  display: flex; align-items: center; gap: 7px; flex: 1 1 200px; min-width: 150px;
  height: 30px; padding: 0 10px; border-radius: var(--r-pill);
  background: var(--surface); border: 1px solid var(--hairline);
  transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
}
.toolbar__search:hover { border-color: var(--hairline-strong); }
.toolbar__search:focus-within {
  border-color: var(--brand-500); background: var(--surface);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-500) 16%, transparent);
}
.toolbar__search svg { width: 14px; height: 14px; color: var(--ink-muted); flex: none; }
.toolbar__search:focus-within svg { color: var(--link); }
.toolbar__search input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  font: inherit; font-size: var(--fs-8); color: var(--ink); padding: 0;
}
.toolbar__search input::placeholder { color: var(--ink-muted); }

/* Selects read as chips. Borderless at rest; tinted once they are filtering. */
.toolbar__select {
  height: 30px; padding: 0 26px 0 11px; border-radius: var(--r-pill);
  border: 1px solid transparent; background: var(--surface);
  font: inherit; font-size: var(--fs-7); font-weight: 550; color: var(--ink-2);
  cursor: pointer; appearance: none; max-width: 190px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23788' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 8px center; background-size: 12px;
  transition: border-color .15s ease, background-color .15s ease, color .15s ease;
}
.toolbar__select:hover { border-color: var(--hairline-strong); }
.toolbar__select:focus-visible {
  outline: none; border-color: var(--brand-500);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-500) 16%, transparent);
}
/* --info-text, not --brand-700: the brand ramp's 700 has no dark value, so an
   active filter read dark navy on the dark ground ("Grouped by status", "Open"
   - found on a phone in dark mode, 2026-10-06). The same #163E7A in light. */
.toolbar__select.is-set {
  background-color: var(--brand-50); border-color: color-mix(in srgb, var(--brand-500) 34%, transparent);
  color: var(--info-text); font-weight: 620;
}

/* Date inputs sit in the strip as chips, matching the selects. */
.toolbar__date {
  height: 30px; padding: 0 9px; border-radius: var(--r-pill);
  border: 1px solid transparent; background: var(--surface);
  font: inherit; font-size: var(--fs-7); color: var(--ink-2); cursor: pointer;
  transition: border-color .15s ease, color .15s ease;
}
.toolbar__date:hover { border-color: var(--hairline-strong); }
.toolbar__date:focus-visible {
  outline: none; border-color: var(--brand-500);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-500) 16%, transparent);
}

/* A count beside a tab label, so a queue's size is visible without opening it. */
.tab__count {
  display: inline-block; min-width: 18px; padding: 0 5px; margin-left: 6px;
  border-radius: var(--r-pill); background: var(--neutral-bg); color: var(--ink-2);
  font-size: var(--fs-4); font-weight: 700; line-height: 17px; text-align: center;
  font-variant-numeric: tabular-nums; vertical-align: 1px;
}
.tab.is-active .tab__count { background: var(--surface-3); color: var(--ink); }
/* a tab with something inside that needs attention (Settings, 2026-09-28) */
.tab__count--warn, .tab.is-active .tab__count--warn { background: var(--warning); color: #3A2A00; }
.tab__count--critical, .tab.is-active .tab__count--critical { background: var(--critical); color: #fff; }

.toolbar__clear {
  height: 30px; padding: 0 12px; border-radius: var(--r-pill); cursor: pointer;
  border: 1px solid transparent; background: none;
  font: inherit; font-size: var(--fs-7); font-weight: 550; color: var(--ink-muted);
  transition: color .15s ease, background .15s ease;
}
.toolbar__clear:hover { color: var(--critical); background: var(--critical-bg); }
.toolbar__clear:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* A card whose first child is the strip should not double up on padding. */
.card > .toolbar:first-child { border-radius: var(--r-lg) var(--r-lg) 0 0; }

@media (max-width: 640px) {
  .toolbar { gap: 6px; padding: 8px var(--sp-3); }
  .toolbar__count { width: 100%; order: -1; }
  .toolbar__search { flex: 1 1 100%; }
  .toolbar__select { flex: 1 1 auto; max-width: none; }
}
@media (prefers-reduced-motion: reduce) {
  .toolbar__search, .toolbar__select, .toolbar__clear { transition: none; }
}

/* ── sortable column headings ───────────────────────────────────────────── */
.tbl__sort {
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  display: inline-flex; align-items: center; gap: 5px; width: 100%;
}
.tbl th.num .tbl__sort { justify-content: flex-end; }
.tbl__sort:hover { color: var(--ink); }
.tbl__sort:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The arrow is always in the DOM so a heading does not jump sideways the
   moment you sort by it. */
.tbl__arrow { font-size: var(--fs-1); opacity: .3; flex: none; line-height: 1; }
.tbl__sort:hover .tbl__arrow { opacity: .6; }
.tbl__sort.is-on { color: var(--info-text); }
.tbl__sort.is-on .tbl__arrow { opacity: 1; color: var(--link); }
@media (max-width: 720px) {
  /* the card layout hides the header row entirely, so nothing to sort there */
  .tbl--cards .tbl__sort { pointer-events: none; }
}

.kpi--top { cursor: default; }
.kpi--top:hover, .kpi--top:focus-within, .kpi--top:focus {
  overflow: visible; z-index: var(--z-hovercard-host);
}
.kpi--top:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.kpi__top {
  position: absolute; left: 0; right: 0; top: calc(100% - 4px); z-index: var(--z-hovercard);
  background: var(--surface); border: 1px solid var(--hairline-strong);
  border-radius: var(--r-md); box-shadow: var(--shadow-lg, 0 10px 30px rgba(0,0,0,.16));
  padding: var(--sp-3) var(--sp-4); min-width: 240px;
  opacity: 0; visibility: hidden; transform: translateY(-4px);
  transition: opacity .12s ease, transform .12s ease, visibility .12s;
}
.kpi--top:hover .kpi__top,
.kpi--top:focus-within .kpi__top,
.kpi--top:focus .kpi__top { opacity: 1; visibility: visible; transform: translateY(0); }
.kpi__top-head {
  font-size: var(--fs-5); font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-muted); margin-bottom: 8px;
}
.kpi__top-row {
  display: flex; justify-content: space-between; gap: var(--sp-4);
  font-size: var(--fs-8); padding: 3px 0; align-items: baseline;
}
.kpi__top-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kpi__top-name a { color: var(--ink); text-decoration: none; }
.kpi__top-name a:hover { color: var(--info-text); text-decoration: underline; }
.kpi__top-val { font-variant-numeric: tabular-nums; color: var(--ink-2); flex: none; }
/* The kpi row must not clip its own children, or the panel is cut off by the
   grid rather than by the tile. */
.kpi-row:has(.kpi--top:hover), .kpi-row:has(.kpi--top:focus-within) { overflow: visible; }
@media (hover: none) {
  /* No hover on a phone, and a tap should not open a panel that covers the
     next tile. The tile stays a plain figure. */
  .kpi__top { display: none; }
}

.kpi__label {
  font-size: var(--fs-5); font-weight: 650; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-muted); display: flex; align-items: center; gap: 6px;
  /* one fixed line box, help icon or not, so the numbers under a row of
     labels start at the same height (V2) */
  min-height: 16px; line-height: 16px;
}
/* The foot sits at the bottom of the tile, so in a row of equal-height tiles
   the feet line up too, whatever length the number above them is (V2). */
.kpi { display: flex; flex-direction: column; }
.kpi > .kpi__foot { margin-top: auto; padding-top: 7px; }
.kpi-row--lead .kpi > .kpi__foot { padding-top: 9px; }
/* proportional figures on display numbers — never tabular-nums here */
.kpi__value {
  font-size: var(--fs-22); font-weight: 600; letter-spacing: -.03em; margin-top: 7px; line-height: 1.1;
}
.kpi__foot { margin-top: 7px; font-size: var(--fs-7); color: var(--ink-2); display: flex; align-items: center; gap: 6px; }

/* ── the overhead burden strip ────────────────────────────────────────────
   One line between a job's figures and its tabs. Deliberately a strip and not
   a card: gross margin is the headline and this is the second reading, so it
   should sit quietly under the tiles rather than compete with them. */
.burden {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  padding: 10px var(--sp-4); margin-bottom: var(--sp-4);
  background: var(--surface); border: var(--card-line-w) solid var(--card-line);
  border-radius: var(--r-lg); font-size: var(--fs-9);
}
.burden__label {
  font-size: var(--fs-5); font-weight: 650; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-muted); flex: none;
}
.burden__figures { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.burden__sep { color: var(--hairline-strong); }
.burden__spacer { flex: 1 1 auto; }
.burden__note { color: var(--ink-muted); font-size: var(--fs-7); }
@media (max-width: 720px) {
  .burden { gap: var(--sp-2); padding: var(--sp-3); }
  .burden__spacer { display: none; }
  .burden__figures, .burden__note { flex: 1 1 100%; }
}

/* ── pills / badges ──────────────────────────────────────────────────────
   Squared-off tags rather than lozenges. A rounded pill reads as a label
   someone stuck on; a square one reads as a field of the record. */
.pill {
  display: inline-flex; align-items: center; gap: 5px; height: 21px; padding: 0 8px;
  border-radius: var(--r-pill); font-size: var(--fs-4); font-weight: 650; white-space: nowrap;
  letter-spacing: .015em; background: var(--neutral-bg); color: var(--ink-2);
}
/* The dot is the phone's status lamp (2026-10-08): lit in the fixed status
   colour for good, warning, serious and critical, a dim lamp for everything
   else - so Signed, Lead and Complete no longer look like a colour-coded alarm. */
.pill__dot { width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--lamp-dim); box-shadow: 0 0 0 1px var(--lamp-dim-ring); }
.pill--good .pill__dot     { background: var(--good);     box-shadow: 0 0 0 1px var(--good-text); }
.pill--warning .pill__dot  { background: var(--warning);  box-shadow: 0 0 0 1px var(--warning-text); }
.pill--serious .pill__dot  { background: var(--serious);  box-shadow: 0 0 0 1px var(--serious-text); }
.pill--critical .pill__dot { background: var(--critical); box-shadow: 0 0 0 1px var(--critical-text); }
.pill--good     { background: var(--good-bg);     color: var(--good-text); }
.pill--warning  { background: var(--warning-bg);  color: var(--warning-text); }
.pill--serious  { background: var(--serious-bg);  color: var(--serious-text); }
.pill--critical { background: var(--critical-bg); color: var(--critical-text); }
/* --brand-100 is themed but --brand-700 is not, so the old pairing gave about
   1.4:1 in dark mode. --info-text is themed alongside its background. */
.pill--info     { background: var(--brand-100);   color: var(--info-text); }

/* ═══════════════════════════════════════════════════════════════════════════
   Tables.

   These are the product — most of the app is a table — so they get the
   attention. What makes one look designed rather than defaulted:

     · a hairline under the header and nothing else heavy anywhere
     · row separators at half strength, so the eye tracks across a row
       instead of being chopped into boxes
     · every figure in tabular numerals, right-aligned, so a column of
       money is a straight edge you can scan for the odd one out
     · the row under the cursor lit by a tint AND a 2px brand edge, which
       is what tells you where you are in a 40-row table
     · a first column that carries a little more weight, because it is the
       thing the row is *about*
   ═══════════════════════════════════════════════════════════════════════════ */
.table-wrap { overflow-x: auto; }
/* FREEZE PANE. Sagar, 2026-09-24: "we need something like a freeze pane where
   the navigation is better."

   The headings were ALREADY position: sticky — and never stuck. overflow-x on
   .table-wrap makes the wrapper a scroll container, so a sticky heading pins
   to the top of a box that never scrolls vertically, and it scrolled away
   with the page like everything else. The fix is to let the wrapper be the
   thing that scrolls: a table taller than the screen scrolls inside its own
   card, the headings stay put at the top of it, and the totals row
   (tfoot, already sticky at the bottom) stays put at the foot. Exactly an
   Excel freeze pane.

   Desktop and tablet only. Below 720px most tables become stacked cards with
   no heading row at all, and a scroll box inside a phone page is a trap for
   a thumb. A table that sets its own max-height inline (the cost entries, a
   dialog's list) keeps it — an inline style wins, and those were sized for
   where they sit. */
@media (min-width: 721px) {
  .table-wrap {
    overflow: auto;
    /* No overscroll-behavior: contain. Once the table reaches its end the
       wheel has to carry on into the page, or a table filling the screen
       would be a wall you could not scroll past. The browser alone does not
       carry it on mid-swipe; ui.js wheelPastTable does (2026-10-14). */
    max-height: calc(100 * var(--vh) - var(--topbar-h) - 40px);
  }
}
/* Tabular figures on number cells only (2026-10-08, the panel): set on the
   whole table, Inter's tabular feature also widened every hyphen to a digit's
   width - "REQ - 1002", "Sherwin - Williams". */
table.tbl { width: 100%; border-collapse: collapse; font-size: var(--fs-9); }
.tbl th {
  text-align: left; font-size: var(--fs-5); font-weight: 650; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-2); padding: 9px var(--sp-4); vertical-align: bottom;
  border-bottom: 1px solid var(--hairline-strong); background: var(--head-bg);
  position: sticky; top: 0; z-index: var(--z-sticky-head); white-space: nowrap;
}
.tbl td {
  padding: 10px var(--sp-4); vertical-align: middle;
  border-bottom: 1px solid color-mix(in srgb, var(--hairline) 62%, transparent);
}
/* ── nothing wraps that should not ────────────────────────────────────────
   A heading NEVER wraps (set above), and neither does anything short and
   identifying: a code, a date, a number, a status. Left alone, "AIRM-AXIAL"
   broke across two lines and a row of buttons across three, so a table of
   62 items was several screens tall and the columns no longer lined up with
   their headings.

   ONLY while it is a real table. Below 720px these same cells become
   full-width rows in a card, where wrapping is not just allowed but required
   — forcing nowrap there pushed the card wider than the phone and put a
   horizontal scrollbar on the whole page. */
@media (min-width: 721px) {
  .tbl td.num,
  .tbl td:has(> .pill),
  .tbl td:has(> .row),
  .tbl td[data-label="Date"],
  .tbl td[data-label="Updated"],
  .tbl td[data-label="Requested"],
  .tbl td[data-label="Start"],
  .tbl td[data-label="Target end"],
  .tbl td[data-label="Actual end"],
  .tbl td[data-label="Kind"],
  .tbl td[data-label="UoM"],
  .tbl td[data-label="Unit cost"] { white-space: nowrap; }
  /* the first column is what the row is ABOUT — a code, an order number, a SKU */
  .tbl td.tbl__lead { white-space: nowrap; }
  /* an action cell keeps its buttons on one line */
  .tbl td.tbl__act > .row { flex-wrap: nowrap; }
  /* …and the description column absorbs the slack instead */
  .tbl th:nth-child(2), .tbl td:nth-child(2) { min-width: 0; }
}
.tbl td.tbl__lead { font-weight: 550; }
.tbl tbody tr {
  position: relative;
  transition: background var(--dur-1) var(--ease);
}
.tbl tbody tr:hover { background: var(--surface-2); }
/* A line to buy (not in the warehouse) is tinted, the whole line - Sagar,
   2026-10-14: "a faint highlight of the items themselves". The request's
   lines, and every row of the request form's Buy table. */
.tbl tbody tr.tbl__row--buy,
.tbl--buy tbody tr { background: var(--warning-bg); }
/* Transactions: money in and out are said by the AMOUNT's colour alone (.pos /
   .neg, quickbooks.js amountCell) - the whole line was tinted until 2026-10-20
   (Sagar: "just color the numbers either red or green and leave the rest alone"). */
/* A withdrawn request in the history sits back from the decided ones. */
.tbl tbody tr.tbl__row--withdrawn > td { opacity: .62; }
/* The edge marker rides on the first cell so it does not need a pseudo-element
   on the <tr>, which cannot be positioned reliably across browsers. */
.tbl tbody td:first-child {
  box-shadow: inset 2px 0 0 transparent;
  transition: box-shadow var(--dur-1) var(--ease);
}
.tbl tbody tr:hover td:first-child { box-shadow: inset 2px 0 0 var(--accent); }
.tbl tbody tr.is-clickable { cursor: pointer; }
/* A row's name as a button (the inventory SKU, 2026-10-07), so what a click on
   the row opens also opens from the keyboard. It reads as the cell's text. */
.tbl__open { padding: 0; border: 0; background: none; font: inherit; color: inherit; text-align: left; cursor: pointer; }
.tbl__open:hover { color: var(--link); text-decoration: underline; }
/* a quieter second line inside a cell - the model under an item's description */
.tbl__sub { display: block; overflow: hidden; text-overflow: ellipsis; }
.tbl .num { text-align: right; font-family: var(--font-num); font-variant-numeric: tabular-nums; white-space: nowrap; }
.tbl tfoot td { font-family: var(--font-num); font-variant-numeric: tabular-nums; }
/* dates keep even-width digits too, so a column of them lines up */
.tbl td:is([data-label="Date"], [data-label="Updated"], [data-label="Requested"], [data-label="Start"],
  [data-label="Target end"], [data-label="Actual end"], [data-label="Due"], [data-label="Waiting"]) { font-variant-numeric: tabular-nums; }
.tbl tfoot td {
  font-weight: 650; background: var(--surface-2); border-top: 1px solid var(--hairline-strong);
  border-bottom: 0; position: sticky; bottom: 0;
}
.tbl__empty { padding: var(--sp-7); text-align: center; color: var(--ink-muted); }
/* ── one way to say "this opens" (2026-10-08, the panel) ─────────────────
   htmlTable `open`: the row opens, its first cell is a real link, and the
   phone's chevron sits in a narrow last column. */
.tbl tr[data-open-href] { cursor: pointer; }
.tbl__openlink { color: inherit; text-decoration: none; }
.tbl__openlink:hover { color: var(--link); text-decoration: underline; }
.tbl td.tbl__chev { width: 28px; padding-left: 0; padding-right: var(--sp-3); text-align: right; }
/* many photos at once (2026-10-22): a tick box per photo, and the bar over the
   table that acts on the ticked ones - shown only while one is ticked */
.tbl td.tbl__pick { width: 34px; padding-right: 0; }
.tbl__pick input, .tbl th input[data-pick-all] { width: 18px; height: 18px; cursor: pointer; vertical-align: middle; }
.bulkbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
  margin-top: var(--sp-2); padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--hairline-strong); border-radius: var(--r-sm); background: var(--surface-2);
}
.bulkbar[hidden] { display: none; }
.bulkbar__count { margin-right: auto; }
/* a paper on a bank line: a small paperclip at the line's end (2026-10-14) */
.tbl td.tbl__papercol { width: 22px; padding-left: 0; padding-right: 0; text-align: center; }
.tbl__paper { display: inline-flex; width: 14px; height: 14px; color: var(--ink-muted); vertical-align: middle; }
/* the + or − before a Transactions amount (2026-10-14): the money's direction,
   muted and unweighted so the figure is still what is read */
.tbl__sign { color: var(--ink-muted); font-weight: 400; margin-right: 1px; }
.tbl__paper svg { width: 14px; height: 14px; }
.tbl__chevron { display: inline-flex; width: 16px; height: 16px; color: var(--ink-muted); vertical-align: middle; }
.tbl__chevron svg { width: 16px; height: 16px; }
.tbl tr[data-open-href]:hover .tbl__chevron { color: var(--accent); }
/* ── "⋯": a row's other actions (2026-10-08) ─────────────────────────────── */
.rowmenu { position: relative; display: inline-block; }
.rowmenu > summary { list-style: none; }
.rowmenu > summary::-webkit-details-marker { display: none; }
.rowmenu__list {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: var(--z-hovercard); min-width: 180px;
  display: flex; flex-direction: column; padding: 4px; gap: 2px;
  background: var(--surface); border: 1px solid var(--control-line); border-radius: var(--r-md); box-shadow: var(--shadow-lg);
}
.rowmenu__list > .btn { justify-content: flex-start; width: 100%; border-color: transparent; background: none; box-shadow: none; }
.rowmenu__list > .btn:hover { background: var(--surface-2); }
/* the last rows of a scrolling table open their menu upward, not into the
   bottom of the scroll box where it would be cut off */
.tbl tbody tr:nth-last-child(-n+3):not(:nth-child(-n+3)) .rowmenu__list { top: auto; bottom: calc(100% + 4px); }
/* ── a table lines up with its own card (2026-10-08, the panel): the first and
   last cells take the card's padding, so the card's title, its filter strip
   and the table's first column share one left edge ─────────────────────── */
.card .tbl th:first-child, .card .tbl td:first-child { padding-left: var(--sp-5); }
.card .tbl th:last-child, .card .tbl td:last-child { padding-right: var(--sp-5); }
.card > .toolbar { padding-left: var(--sp-5); padding-right: var(--sp-5); }
.card .tbl .group-row__btn { padding-left: var(--sp-5); }
/* a band that is only a label (the budget's trade groups) was flush to the
   card's edge, its text outside the table's margin */
.tbl tr.group-row td:only-child:not(:has(.group-row__btn)) { padding: 6px var(--sp-5); }
/* headings: the sort arrow BEFORE the label on a number column, so the label
   ends where the figures do; the heading sits on its rule */
.tbl th.num .tbl__sort { flex-direction: row-reverse; justify-content: flex-start; }
/* one row height: a control inside a row is the compact one, never stretching it */
.tbl td .select, .tbl td .input { height: 26px; padding-top: 0; padding-bottom: 0; font-size: var(--fs-7); }
.tbl td .select { padding-right: 26px; background-position: right 8px center; }
/* a long table's held-back rows (plan #63): one quiet line under what is showing */
.tbl__window td { background: var(--surface-2, var(--surface)); }
.tbl__window-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
  justify-content: center; color: var(--ink-muted); font-size: var(--fs-7); }
.tbl--compact td, .tbl--compact th { padding: 7px var(--sp-3); }
/* ── a column's width, dragged like a spreadsheet's (2026-10-06) ───────────
   htmlTable's "Column widths": a handle on each heading's right edge, a line
   on hover and while dragging. A table someone has sized is laid out fixed,
   at exactly the widths chosen - what does not fit is cut, as in Excel. A
   phone shows cards, so there is nothing to drag there. */
.tbl__resize {
  position: absolute; top: 0; right: 0; width: 8px; height: 100%;
  cursor: col-resize; touch-action: none; z-index: var(--z-raised);
}
.tbl__resize::after {
  content: ""; position: absolute; top: 20%; bottom: 20%; right: 3px; width: 2px; border-radius: 1px;
  background: transparent; transition: background var(--dur-1) var(--ease);
}
.tbl th:hover > .tbl__resize::after,
.matrix th:hover > .tbl__resize::after { background: var(--hairline-strong); }
.tbl__resize:hover::after, .tbl__resize.is-dragging::after { background: var(--accent); }
.tbl--sized { table-layout: fixed; }
.tbl--sized th, .tbl--sized td { overflow: hidden; text-overflow: ellipsis; }
.tbl--sized .tbl__clip { max-width: 100%; }
/* Once sized, the widths chosen are the widths (2026-10-09): a cell's own
   minimum (the cash grid's, the pick dialog's inline min-width) and the access
   matrix's 720px floor no longer push a column wider than it was dragged or
   spring the table back out. */
.tbl--sized th, .tbl--sized td, .matrix.tbl--sized th, .matrix.tbl--sized td { min-width: 0 !important; }
.matrix.tbl--sized { min-width: 0; }
/* A double-click measures each column on one line for a moment (ui.js
   fitWidths) - laid out at its natural width, nothing wrapped or clipped. */
.tbl--fit { width: max-content !important; min-width: 0 !important; table-layout: auto !important; }
.tbl--fit th, .tbl--fit td { white-space: nowrap !important; min-width: 0 !important; }
.tbl--fit .tbl__clip { max-width: none !important; }
@media (pointer: coarse) { .tbl__resize { width: 16px; } }
@media (max-width: 720px) {
  .tbl--cards .tbl__resize { display: none; }
  .tbl--cards.tbl--sized { width: 100% !important; table-layout: auto; }
}
/* the phone app (data-ui="phone") restacks tables as cards at any width - no
   handles there, and no remembered desk widths pulling a card out of shape */
:root[data-ui="phone"] .tbl__resize { display: none; }
:root[data-ui="phone"] .tbl--sized { width: 100% !important; table-layout: auto; }
/* ── TIGHT ROWS ON A DESKTOP (Sagar, 2026-09-29) ─────────────────────────
   "I personally work in a lot of excel, so more visible rows and stuff are
   preferred but only if it's organized and easy to navigate" - "tighten up
   like excel rows" - "I'm sure this an issue across the board".

   With a mouse, every table runs tight: 4px above and below the text, and
   24px buttons in a row - the 28px ones were what set every row at 49px.
   Every other row carries a faint band so an eye can follow one row across
   fourteen columns, as in Excel. The headings already freeze at the top of
   the table's own scroll box (FREEZE PANE, above).

   A column marked `clip` (htmlTable) stays on one line, cut with … at its
   --clip width and shown whole on hover. Wrapped, one long description made
   its row three lines tall.

   Touch screens and phones keep the roomier rows and full-size buttons: a
   finger needs the target, and phones restack tables as cards anyway. */
@media (min-width: 721px) and (pointer: fine) {
  .tbl td, .tbl--compact td { padding: 4px var(--sp-2); }
  .tbl th, .tbl--compact th { padding: 7px var(--sp-2); }
  .tbl tr.group-row td { padding: 4px var(--sp-2); }
  .tbl td .btn--sm { height: 24px; padding: 0 8px; font-size: var(--fs-6); }
  .tbl td .btn--icon.btn--sm { width: 24px; padding: 0; }
  /* One row height (2026-10-08 review, #39): rows ran from 24px to 38px by
     what they held - text, a pill, a field, a full-size button. Now every
     row is 34px: a button in a row is the 24px one and a field the 26px one,
     so each fits the row a line of text sets. */
  .tbl tbody tr:not(.group-row) > td { height: 34px; }
  .tbl td .btn { height: 24px; padding: 0 8px; font-size: var(--fs-6); }
  .tbl td .btn--icon { width: 24px; padding: 0; }
  .tbl td .rowmenu__list > .btn { height: 30px; padding: 0 10px; font-size: var(--fs-7); }
  /* Opaque bands, set on the ROW: the frozen first cell below takes its
     row's colour by inheriting it, and a see-through band would let the
     columns scrolling beneath it show through. */
  .tbl tbody tr { background: var(--surface); }
  .tbl tbody tr:nth-child(even of :not(.group-row)) {
    background: color-mix(in srgb, var(--surface-2) 70%, var(--surface));
  }
  /* ...and more specific than a tinted line, which lost its tint on every
     other row (2026-10-14) - so the tints are restated too, ahead of the
     hover: the row under the cursor still goes to the hover grey */
  .tbl tbody tr.tbl__row--buy:not(.group-row),
  .tbl.tbl--buy tbody tr:not(.group-row) { background: var(--warning-bg); }
  /* the band is more specific than the plain hover, so hover is restated */
  .tbl tbody tr:not(.group-row):hover { background: var(--surface-2); }
  /* FREEZE THE FIRST COLUMN as well as the headings: when a table is wider
     than the screen and scrolls sideways, the SKU / job / name the row is
     about stays in view, as in Excel. It changes nothing on a table that fits. */
  .tbl th:first-child, .tbl td:first-child { position: sticky; left: 0; }
  .tbl thead th:first-child { z-index: var(--z-sticky-corner); }
  .tbl tbody td:first-child { z-index: var(--z-sticky-col); background: inherit; }
  .tbl tfoot td:first-child { z-index: var(--z-sticky-corner); }
  /* --clip is a share of the window's width (htmlTable writes the number),
     so a bigger screen shows more of the text before it is cut: 12 is 230px
     on a 1920 screen and 307px on a 2560 one. A fixed width cut 46 inventory
     descriptions on a 30-inch monitor with room to spare. */
  .tbl__clip {
    display: block; max-width: calc(var(--clip, 14) * var(--vw));
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* A heading may wrap between words. Unwrapped, "SIGNED OUT TO" held a
     column of dashes at 125px and "REORDER PT" one of zeros at 105 - the
     headings, not the data, pushed the inventory past a 1920 screen. */
  .tbl th { white-space: normal; }
}
/* Group banner: the trade groups in the budget table, and the status bands in
   the project list. */
.tbl tr.group-row td {
  background: var(--band-bg); font-size: var(--fs-6); padding: 6px var(--sp-4);
  border-bottom: 1px solid var(--hairline-strong);
}
.tbl tbody tr.group-row:hover td { background: var(--band-bg); }
/* A banner that is ONLY a label pins to the left, so you can still see which
   trade you are in after scrolling the wide budget columns.

   A banner carrying its group's subtotals must not: it is an opaque cell that
   would slide straight over its own numbers. `:only-child` tells the two
   apart exactly — the budget table's banner is a single colspan cell, and a
   band with subtotals has one cell per money column beside it. */
.tbl tr.group-row td:only-child { position: sticky; left: 0; }
.tbl tr.group-row td + td { text-align: right; font-weight: 650; }
.tbl tr.group-row td:first-child { padding: 0; }
/* the band's hover marker would fight the button's own */
.tbl tbody tr.group-row:hover td:first-child { box-shadow: none; }

/* The band is a button across the full label cell, so the hit target is the
   whole width of it rather than a 14px chevron. */
.group-row__btn {
  display: flex; align-items: center; gap: var(--sp-2); width: 100%;
  padding: 7px var(--sp-4); border: 0; background: none; cursor: pointer;
  font: inherit; color: var(--ink); text-align: left;
  transition: background var(--dur-1) var(--ease);
}
.group-row__btn:hover { background: color-mix(in srgb, var(--ink) 6%, transparent); }
.group-row__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.group-row__label {
  font-family: var(--font-display); font-size: var(--fs-8); font-weight: 600;
  letter-spacing: .005em;
}

/* ── forms ───────────────────────────────────────────────────────────── */
.field { display: flex; flex-direction: column; gap: 5px; }
.field__label { font-size: var(--fs-7); font-weight: 600; color: var(--ink-2); display: flex; align-items: center; gap: 5px; }
.field__hint { font-size: var(--fs-6); color: var(--ink-muted); }

/* A field the database refused. Loud on purpose: a failed save must not be
   missable, and the person is pointed at the one box to fix rather than left
   to re-check twenty of them. */
.field--error > .input,
.field--error > .select,
.field--error > .textarea,
.field--error > .grid-input { border-color: var(--critical);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--critical) 22%, transparent); }
.field--error > .field__label { color: var(--critical-text); }
.field__error { font-size: var(--fs-6); font-weight: 600; color: var(--critical-text);
  display: flex; align-items: flex-start; gap: 5px; }
.field__error::before { content: "!"; display: inline-flex; align-items: center;
  justify-content: center; width: 14px; height: 14px; border-radius: 50%;
  background: var(--critical); color: #fff; font-size: var(--fs-2); line-height: 1; flex: none; }

/* The "is this right?" step, shown in place of the form rather than over it so
   nothing typed is ever at risk while it is up. */
.confirm-panel { border: 1px solid var(--hairline-strong); border-radius: var(--r-sm);
  padding: 14px; background: var(--surface-2); }

/* Why a banner and not a dialog: a dialog opens by closing the one already
   open, which would delete the form and everything typed into it. This sits
   inside the same dialog, above the form, and the form stays exactly as it was. */
/* the star marking a field that is required at the job's current status */
.req { color: var(--critical-text); font-weight: 700; margin-left: 2px; }

.form-banner { border: 1px solid var(--critical); border-left-width: 4px;
  border-radius: var(--r-sm); padding: 10px 12px; margin-bottom: 12px;
  background: color-mix(in srgb, var(--critical) 8%, transparent);
  color: var(--critical-text); display: flex; flex-direction: column; gap: 3px; }

/* The same shape as .form-banner, for a message that is NOT beside a form —
   inside a dialog, at the top of a panel. `alertDialog` is documented as "a
   dead stop… for failures" and rendered as an unstyled paragraph with a blue
   OK button, so a save that did not happen looked exactly like a tip. A
   message that carries a consequence has to carry the colour of it.
   Tone is on the modifier, never on the element that uses it. */
.notice { border: 1px solid var(--hairline-strong); border-left-width: 4px;
  border-radius: var(--r-sm); padding: 10px 12px;
  background: var(--neutral-bg); color: var(--ink);
  display: flex; flex-direction: column; gap: 3px; }
.notice--critical { border-color: var(--critical); color: var(--critical-text);
  background: color-mix(in srgb, var(--critical) 8%, transparent); }
.notice--warning  { border-color: var(--warning);  color: var(--warning-text);
  background: color-mix(in srgb, var(--warning) 14%, transparent); }
.notice--good     { border-color: var(--good);     color: var(--good-text);
  background: color-mix(in srgb, var(--good) 10%, transparent); }
.input, .select, .textarea {
  height: 34px; padding: 0 10px; border: 1px solid var(--control-line); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink); width: 100%; transition: border-color .12s, box-shadow .12s;
}
.textarea { height: auto; padding: 8px 10px; resize: vertical; min-height: 72px; line-height: 1.5; }
.input:focus, .select:focus, .textarea:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent);
}
/* The browser's own Windows controls ignored every theme (2026-10-08, the
   panel): a select is drawn by us - the arrow, the edge, the padding - and a
   date field's calendar icon and a checkbox take the theme's colours. */
.select {
  appearance: none; -webkit-appearance: none; padding-right: 30px; cursor: pointer;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23788' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center; background-size: 12px;
}
.select:hover, .input:hover, .textarea:hover { border-color: var(--ink-2); }
input[type="checkbox"], input[type="radio"] { accent-color: var(--primary); }
/* The date field's calendar mark is ours, drawn in the theme's ink through a
   mask, instead of Windows' black glyph (inverted by filter in dark). The
   separators go quiet so the date reads first. */
input[type="date"]::-webkit-calendar-picker-indicator {
  width: 15px; height: 15px; padding: 0; margin-left: 6px; cursor: pointer; opacity: 1;
  background: var(--ink-muted);
  -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.5' y='5' width='17' height='15.5' rx='1.5'/%3E%3Cpath d='M3.5 10h17M8 3v4M16 3v4'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.5' y='5' width='17' height='15.5' rx='1.5'/%3E%3Cpath d='M3.5 10h17M8 3v4M16 3v4'/%3E%3C/svg%3E") center / contain no-repeat;
}
input[type="date"]:hover::-webkit-calendar-picker-indicator,
input[type="date"]:focus::-webkit-calendar-picker-indicator { background: var(--ink); }
input[type="date"]::-webkit-datetime-edit-text { color: var(--ink-muted); }
input[type="date"]::-webkit-datetime-edit-fields-wrapper { padding: 0; }
.input[readonly] { background: var(--surface-3); color: var(--ink-2); border-color: var(--hairline-strong); }
.input.num, .input--num { text-align: right; font-variant-numeric: tabular-nums; }
/* inventory text is saved in capitals (2026-10-22), so it is typed in them; the hint stays as written */
.input--caps { text-transform: uppercase; }
.input--caps::placeholder { text-transform: none; }

/* compact variants for dense filter bars and the split grid */
.input--sm, .select--sm { height: 30px; padding: 0 8px; font-size: var(--fs-7); }

/* Type to search on a pick-list (ui.js initPickSearch, 2026-10-16). The
   select stays the box; pressing it opens this list under it, hung off
   <body> so a dialog's scrolling body cannot cut it off, and over the dialog.
   Theme colours only, so every theme and dark mode draw it in their own. */
select[data-pick] { text-overflow: ellipsis; }
.pick {
  position: fixed; z-index: var(--z-pick); display: flex; flex-direction: column; min-width: 0;
  background: var(--surface); color: var(--ink); border: 1px solid var(--hairline-strong);
  border-radius: var(--r-md); box-shadow: var(--shadow-lg); overflow: hidden;
}
/* the ring is on the list as a whole: the cursor is in its search box */
.pick:focus-within { box-shadow: var(--shadow-lg), 0 0 0 2px var(--accent); }
.pick__search {
  display: flex; align-items: center; gap: 8px; flex: none; padding: 4px 10px;
  border-bottom: 1px solid var(--hairline); color: var(--ink-muted);
}
.pick__search svg { width: 15px; height: 15px; flex: none; }
.pick__q {
  flex: 1; min-width: 0; height: 30px; border: 0; padding: 0; outline: none;
  background: transparent; color: var(--ink); font-size: var(--fs-8);
}
.pick__q::placeholder { color: var(--ink-muted); }
.pick__list { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 4px; position: relative; overscroll-behavior: contain; }
.pick__group {
  font-size: var(--fs-3); font-weight: 650; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-muted); padding: 8px 8px 3px;
}
.pick__opt {
  position: relative; display: flex; flex-direction: column; justify-content: center; min-width: 0;
  min-height: 30px; padding: 4px 8px 4px 26px; border-radius: var(--r-sm); cursor: pointer; font-size: var(--fs-8);
}
.pick__label, .pick__sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick__sub { font-size: var(--fs-5); color: var(--ink-muted); }
.pick__opt.is-active { background: var(--brand-50); box-shadow: inset 2px 0 0 var(--accent); }
.pick__opt.is-current { font-weight: 600; }
.pick__tick { position: absolute; left: 7px; top: 50%; width: 13px; height: 13px; margin-top: -6.5px; color: var(--accent); }
.pick__tick svg { width: 13px; height: 13px; display: block; }
.pick__opt.is-off { color: var(--ink-muted); cursor: default; opacity: .6; }
.pick__hit { background: color-mix(in srgb, var(--accent) 26%, transparent); color: inherit; border-radius: 2px; padding: 0; }
.pick__none { padding: 10px 12px; color: var(--ink-muted); font-size: var(--fs-7); }
/* a tablet on the desk: every row and the box take a 44px press, and 16px
   type in the box so the iPad does not zoom when it takes the cursor */
@media (pointer: coarse) {
  .pick__opt { min-height: 44px; }
  .pick__q { height: 44px; font-size: var(--fs-14); }
}

/* =============================================================================
   Transaction split grid — job / trade / expense type / amount / two actions.
   One bank charge often covers several jobs, so splitting is the normal case
   rather than an exception, and the arithmetic has to stay visible while typing.
   ============================================================================= */
.alloc-head, .alloc-line {
  display: grid; gap: var(--sp-2); align-items: center;
  grid-template-columns:
    minmax(0, 1.8fr)   /* job */
    92px               /* posts to: job cost or revenue */
    minmax(0, 1.3fr)   /* trade */
    minmax(0, 1.2fr)   /* expense type */
    0                  /* settles - collapsed until a line is revenue */
    104px 28px 28px;   /* amount, put-the-rest, remove */
}
/* Only when something on the panel actually posts to revenue does the
   milestone column take space. A dead column on every ordinary purchase would
   squeeze the three that matter. */
.alloc-head.alloc-line--rev, .alloc-line.alloc-line--rev {
  grid-template-columns:
    minmax(0, 1.6fr) 92px minmax(0, 1.1fr) minmax(0, 1.1fr)
    minmax(0, 1.5fr) 104px 28px 28px;
}
/* A hidden select must keep its GRID CELL, or every control after it slides
   one column left and lands under the wrong heading. `visibility` reserves
   the space; `display:none` removes the item from auto-placement entirely,
   which is what happened on a mixed cost-and-revenue split. */
.alloc-line [data-k="milestoneId"][hidden] {
  display: block !important; visibility: hidden; pointer-events: none;
}
/* …except on a phone, where the row is a two-column stack rather than a
   single grid line and an invisible control just leaves a hole. */
@media (max-width: 720px) {
  .alloc-line [data-k="milestoneId"][hidden] { display: none !important; }
}
.alloc-head { padding: 0 2px; }
.alloc-line + .alloc-line { margin-top: 6px; }
/* A coded piece's memo (Home Depot import, 2026-10-27): the project tag and
   every item on it - about 1,000 characters on a long receipt - under its row,
   the row's full width, as tall as its text (quickbooks.js sizes it as it is
   typed): never a box to scroll inside. Read-only once its piece is approved. */
.alloc-line > .alloc-memo {
  grid-column: 1 / -1; min-height: 32px; padding: 5px 8px; resize: none; overflow-y: hidden;
  font-size: var(--fs-7); line-height: 1.45;
}
.alloc-line > .alloc-memo[readonly] { background: var(--surface-2); color: var(--ink-2); }
/* an other-revenue line's own For (2026-11-02), the row's full width above its memo */
.alloc-line > .alloc-for { grid-column: 1 / -1; }
/* a piece's memo where it is only read: the approval window, Workflow */
.piece-memo { white-space: pre-wrap; overflow-wrap: anywhere; color: var(--ink-2); }
/* A bank line's window, decided whole (2026-10-27): each piece a block - its
   project or overhead category and amount on one line, its trade and expense
   type, its memo - never a table to scroll sideways on a phone. */
.line-pieces { border-top: 1px solid var(--hairline); }
.line-piece { display: grid; gap: 6px; padding: 10px 0; border-bottom: 1px solid var(--hairline); }
.line-piece__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.line-piece__fill { flex: 1; }
.line-piece--overhead .line-piece__head > strong { color: var(--ink-2); }

@media (max-width: 720px) {
  .alloc-head { display: none; }
  .alloc-line {
    grid-template-columns: 1fr 1fr; padding: 10px; margin-top: 10px;
    border: 1px solid var(--hairline); border-radius: var(--r-md);
  }
  .alloc-line > [data-k="projectId"]   { grid-column: 1 / -1; }
  .alloc-line > [data-k="milestoneId"] { grid-column: 1 / -1; }
  .alloc-line > [data-k="amount"]      { grid-column: 1 / -1; }
  /* the two-column phone layout ignores the desktop track sizes entirely */
  .alloc-line.alloc-line--rev { grid-template-columns: 1fr 1fr; }
}

/* =============================================================================
   Access matrix — people down the side, capabilities across the top.
   Wide on purpose: it scrolls horizontally rather than shrinking to illegible.
   ============================================================================= */
.matrix { border-collapse: separate; border-spacing: 0; font-size: var(--fs-8); min-width: 720px; }
.matrix th, .matrix td { padding: 8px 10px; border-bottom: 1px solid var(--hairline); }
.matrix thead th {
  position: sticky; top: 0; z-index: var(--z-sticky-head); background: var(--surface-2);
  font-size: var(--fs-5); font-weight: 600; text-align: center; vertical-align: bottom;
  color: var(--ink-2); line-height: 1.25;
}
.matrix tbody th {
  position: sticky; left: 0; z-index: var(--z-sticky-col); background: var(--surface);
  text-align: left; font-weight: 600; white-space: nowrap;
  border-right: 1px solid var(--hairline);
}
.matrix td { text-align: center; }

/* =============================================================================
   The cash year (2026-10-14) - Sagar's weekly cash workbook: cash lines down
   the side, weeks across. Wide on purpose, like the access matrix: it scrolls
   across and the line names stay put.
   ============================================================================= */
.cy-scroll { max-height: calc(70 * var(--vh)); }
.cashyear { border-collapse: separate; border-spacing: 0; font-size: var(--fs-7); }
.cashyear th, .cashyear td { padding: 5px 10px; border-bottom: 1px solid var(--hairline); white-space: nowrap; }
.cashyear thead th {
  position: sticky; top: 0; z-index: var(--z-sticky-head); background: var(--surface-2);
  font-weight: 600; color: var(--ink-2); text-align: right; vertical-align: bottom; line-height: 1.25;
}
.cashyear thead th:first-child { left: 0; z-index: calc(var(--z-sticky-head) + 1); text-align: left; }
.cashyear tbody th {
  position: sticky; left: 0; z-index: var(--z-sticky-col); background: var(--surface);
  text-align: left; font-weight: 500; min-width: 260px; border-right: 1px solid var(--hairline);
}
.cashyear td { text-align: right; font-variant-numeric: tabular-nums; }
.cashyear td:last-child, .cashyear th:last-child { border-left: 1px solid var(--axis); font-weight: 600; }
.cashyear tr.cy-head th, .cashyear tr.cy-head td {
  background: var(--surface-2); font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  font-size: var(--fs-5); color: var(--ink-2); padding-top: 12px;
}
.cashyear tr.cy-section th, .cashyear tr.cy-section td { background: var(--band-bg); font-weight: 650; color: var(--ink-2); }
.cashyear tr.cy-total th, .cashyear tr.cy-total td { font-weight: 650; border-top: 1px solid var(--axis); }
.cashyear tr.cy-balance th, .cashyear tr.cy-balance td { font-weight: 700; background: var(--surface-2); }
.cashyear tr.cy-check th, .cashyear tr.cy-check td { color: var(--ink-2); font-style: italic; }
.cashyear tr.cy-warn th { color: var(--critical-text); }
.cashyear tbody tr:not(.cy-head):not(.cy-section) th { padding-left: 22px; }
.cashyear .cy-zero { color: var(--ink-muted); }
/* this week: a rule down its column */
.cashyear .cy-now { box-shadow: inset 2px 0 0 var(--accent); }
/* checking ended under the floor */
.cashyear td.cy-under { background: color-mix(in srgb, var(--critical-text) 11%, transparent); }
/* a figure that opens the bank lines behind it */
.cy-cell {
  all: unset; cursor: pointer; display: block; text-align: right; border-radius: var(--r-sm);
}
.cy-cell:hover { text-decoration: underline; text-underline-offset: 2px; }
.cy-cell:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* =============================================================================
   The cash analyst (2026-10-20, phase 4): This week's briefing and Ask, above
   the cash forecast's tabs. Its words are plain paragraphs and lists; a
   question sits apart from an answer by a rule down its side.
   ============================================================================= */
.ca-text p { margin: 0 0 6px; }
.ca-text .ca-head { margin: 10px 0 4px; font-weight: 650; }
.ca-text .ca-head:first-child { margin-top: 0; }
.ca-list { margin: 0 0 8px; padding-left: 20px; }
.ca-list li { margin: 2px 0; }
.ca-talk { display: flex; flex-direction: column; gap: 10px; max-height: calc(50 * var(--vh)); overflow-y: auto; margin-bottom: 10px; }
.ca-turn { padding: 6px 10px; border-left: 3px solid var(--hairline-strong); overflow-wrap: anywhere; }
.ca-turn p { margin: 2px 0 0; }
.ca-turn--you { border-left-color: var(--accent); background: var(--surface-2); }
.ca-turn--error { border-left-color: var(--critical); color: var(--critical-text); }
/* the forecast (2026-10-20, phase 2): the weeks ahead shaded apart from the actual ones, a rule where they start */
.cashforecast .cf-fc { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.cashforecast thead th.cf-fc { background: color-mix(in srgb, var(--accent) 12%, var(--surface-2)); }
.cashforecast .cf-first { border-left: 2px solid var(--accent); }
.cashforecast td.cf-fc.cy-under { background: color-mix(in srgb, var(--critical-text) 14%, transparent); }
.cashforecast td.cf-open { text-decoration: underline dotted; text-underline-offset: 3px; cursor: help; }

/* Four states, and they must be tellable apart at a glance: what someone has
   because of their role should not look like something granted to them. */
.cap {
  width: 30px; height: 30px; border-radius: var(--r-sm); border: 1px solid transparent;
  display: inline-grid; place-items: center; cursor: pointer; font-size: var(--fs-8);
  background: transparent; color: var(--ink-muted); transition: transform .08s ease;
}
.cap:hover  { transform: scale(1.12); }
.cap:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.cap[data-state="inherit-yes"] { color: var(--good-text); background: var(--good-bg); opacity: .55; }
.cap[data-state="inherit-no"]  { color: var(--ink-muted); background: var(--surface-3); opacity: .5; }
.cap[data-state="granted"] {
  color: var(--good-text); background: var(--good-bg); opacity: 1;
  border-color: var(--good-text); font-weight: 700;
}
.cap[data-state="denied"] {
  color: var(--critical-text); background: var(--critical-bg); opacity: 1;
  border-color: var(--critical-text); font-weight: 700;
}
.cap[disabled] { cursor: not-allowed; opacity: .3; }

/* =============================================================================
   Schedule — the shared team calendar.

   The week grid is seven day-columns, optionally with a swimlane per person or
   per job down the left. On a phone the columns stack into a day-by-day agenda
   and empty days drop out entirely, because scrolling past four blank Saturdays
   to find Monday is not a schedule.
   ============================================================================= */
/* .seg is defined once, with the chart toggles' .seg further down (assessment
   O23: it was two rules that merged in the cascade). The merged values live
   there, so both the Schedule and the charts look exactly as before. */
.seg__btn {
  border: 0; background: var(--surface); color: var(--ink-2); cursor: pointer;
  font: inherit; font-size: var(--fs-7); font-weight: 600; padding: 0 12px; height: 30px;
}
.seg__btn + .seg__btn { border-left: 1px solid var(--hairline); }
.seg__btn:hover { background: var(--surface-3); }
.seg__btn.is-on { background: var(--primary); color: var(--primary-ink); }

.dot { width: 8px; height: 8px; border-radius: 50%; flex: none; display: inline-block; }

/* Tick-on/tick-off calendar filters. Multi-select rather than a dropdown so a
   custom mix — say the company calendar plus two people — is one glance and two
   clicks, and stays that way for that person next time. */
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: var(--sp-4); }
.chip {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  border: 1px solid var(--hairline); background: var(--surface); color: var(--ink-2);
  border-radius: var(--r-pill); padding: 0 12px; height: 30px; font: inherit;
  font-size: var(--fs-7); font-weight: 600;
}
.chip:hover { background: var(--surface-3); }
.chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.chip.is-on { background: var(--primary); color: var(--primary-ink); border-color: var(--primary-line); }
.chip.is-on .dot { box-shadow: 0 0 0 2px rgba(255,255,255,.55); }

/* "+2" on an event that also sits on other calendars */
.ev__also {
  flex: none; font-size: var(--fs-3); font-weight: 700; color: var(--ink-2);
  background: var(--surface-3); border-radius: var(--r-pill); padding: 1px 5px;
}

.sched__controls { margin-bottom: var(--sp-4); flex-wrap: wrap; }
.sched__legend { margin-top: var(--sp-4); gap: var(--sp-4); flex-wrap: wrap; }
.sched__today { margin-bottom: var(--sp-4); }
.sched__todaylist { display: flex; flex-direction: column; gap: 6px; }

/* one event */
.ev {
  --ev: var(--brand-800);
  display: flex; align-items: baseline; gap: 7px; width: 100%; text-align: left;
  border: 1px solid var(--hairline); border-left: 3px solid var(--ev);
  background: var(--surface); border-radius: var(--r-sm);
  padding: 5px 8px; cursor: pointer; font: inherit; font-size: var(--fs-7);
  color: var(--ink); min-width: 0;
}
/* A grid item defaults to min-width:auto and will NOT shrink below its content,
   so without this a long event title pushes the whole week grid wider than a
   375px phone. The ellipsis on .ev__title only works once this is set. */
.ev { max-width: 100%; }
.ev:hover { background: var(--surface-3); }
.ev:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ev + .ev { margin-top: 4px; }
.ev__time { color: var(--ink-2); font-variant-numeric: tabular-nums; flex: none; font-size: var(--fs-5); }
.ev__title { font-weight: 550; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }
.ev__job {
  flex: none; font-size: var(--fs-4); font-weight: 600; color: var(--info-text);
  background: var(--brand-100); border-radius: var(--r-pill); padding: 1px 5px;
}
/* Who is at a meeting (2026-10-14): each person's dot in their calendar's
   colour (--who, from the calendar - data, not a literal), with their
   initials in ink beside it, so the colour is never the only cue. */
.ev__who { flex: none; display: inline-flex; align-items: center; gap: 5px; }
.who {
  --who: var(--ink-2);                     /* overridden per person, inline */
  display: inline-flex; align-items: center; gap: 3px; flex: none;
  font-size: var(--fs-3); font-weight: 700; letter-spacing: .02em; color: var(--ink-2); line-height: 1;
}
.who::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--who); flex: none; }
.who--dot { gap: 0; }
/* a private event is deliberately plain — it should read as withheld, not broken */
.ev--private { border-style: dashed; background: var(--surface-3); }
.ev--private .ev__title { font-weight: 500; font-style: italic; color: var(--ink-2); }

/* week grid */
.sched { display: flex; flex-direction: column; }
.sched__row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.sched--person .sched__row, .sched--project .sched__row {
  grid-template-columns: 148px repeat(7, minmax(0, 1fr));
}
.sched__row + .sched__row { border-top: 1px solid var(--hairline); }
.sched__row--head { background: var(--surface-2); }
.sched__col { padding: 8px 10px; text-align: center; border-left: 1px solid var(--hairline); }
.sched__col:first-child { border-left: 0; }
.sched__day { display: block; font-size: var(--fs-4); font-weight: 600; letter-spacing: .04em;
              text-transform: uppercase; color: var(--ink-muted); }
.sched__date { display: block; font-size: var(--fs-14); font-weight: 650; margin-top: 2px; }
.sched__col.is-today .sched__date { color: var(--info-text); }
.sched__col.is-today { background: var(--brand-50); }
.sched__lane {
  padding: 10px; font-size: var(--fs-7); font-weight: 600; display: flex;
  align-items: center; gap: 6px; flex-wrap: wrap;
  background: var(--surface-2); border-right: 1px solid var(--hairline);
}
.sched__cell {
  padding: 8px; border-left: 1px solid var(--hairline);
  min-height: 62px; min-width: 0;             /* see the note on .ev above */
}
.sched__col, .sched__lane { min-width: 0; }
.sched__cell.is-today { background: var(--brand-50); }

/* month grid */
.mon { min-width: 660px; }
.mon__head, .mon__grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.mon__head { background: var(--surface-2); }
.mon__head > div {
  padding: 8px; text-align: center; font-size: var(--fs-4); font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink-muted);
  border-left: 1px solid var(--hairline);
}
.mon__head > div:first-child { border-left: 0; }
.mon__cell {
  min-height: 96px; padding: 6px; border-top: 1px solid var(--hairline);
  border-left: 1px solid var(--hairline); min-width: 0;
}
.mon__cell:nth-child(7n + 1) { border-left: 0; }
.mon__cell.is-outside { background: var(--surface-2); }
.mon__cell.is-outside .mon__date { color: var(--ink-muted); }
.mon__cell.is-today { background: var(--brand-50); }
.mon__date { font-size: var(--fs-7); font-weight: 650; margin-bottom: 4px; }
.mon__cell.is-today .mon__date { color: var(--info-text); }
.mon__more { margin-top: 3px; }

@media (max-width: 720px) {
  .sched__row--head { display: none; }
  .sched__row, .sched--person .sched__row, .sched--project .sched__row {
    grid-template-columns: 1fr;
  }
  .sched__cell { border-left: 0; border-top: 1px solid var(--hairline); }
  .sched__cell.is-empty { display: none; }
  .sched__cell::before {
    content: attr(data-day);
    display: block; font-size: var(--fs-4); font-weight: 700; letter-spacing: .04em;
    text-transform: uppercase; color: var(--ink-muted); margin-bottom: 6px;
  }
  .sched__cell.is-today::before { color: var(--info-text); }
  .sched__lane { border-right: 0; border-top: 2px solid var(--brand-800); }
  .seg__btn { height: 34px; }
}
.form-grid { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
.form-grid .span-2 { grid-column: span 2; }
.fieldset { border: 0; padding: 0; margin: 0 0 var(--sp-5); }
.fieldset__legend {
  font-size: var(--fs-4); font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-muted); padding: 0 0 var(--sp-3); display: flex; align-items: center; gap: var(--sp-2);
}
.search {
  position: relative; display: flex; align-items: center;
}
.search .input { padding-left: 32px; }
.search svg { position: absolute; left: 10px; width: 15px; height: 15px; color: var(--ink-muted); pointer-events: none; }

/* ── tabs ────────────────────────────────────────────────────────────── */
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--hairline); margin-bottom: var(--sp-5); overflow-x: auto; }
.tab {
  padding: 9px 14px; border: 0; background: none; cursor: pointer; font-weight: 560; font-size: var(--fs-9);
  color: var(--ink-2); border-bottom: 2px solid transparent; margin-bottom: -1px; white-space: nowrap;
}
.tab:hover { color: var(--ink); }
.tab.is-active { color: var(--tab-ink); border-bottom-color: var(--tab-line); font-weight: 640; }

/* ── charts ──────────────────────────────────────────────────────────── */
.chart-grid { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); }
/* container sized to include the x-axis band — never a nested scrollbar */
.chart-box { position: relative; height: 288px; }
.chart-box--tall { height: 340px; }
.chart-box--short { height: 190px; }
.chart-legend { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); padding: 0 var(--sp-5) var(--sp-4); }
.chart-legend__item { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-7); color: var(--ink-2); }
.chart-legend__swatch { width: 10px; height: 10px; border-radius: 1px; flex: none; }
.chart-legend__swatch--line { height: 3px; border-radius: 2px; width: 14px; }
/* The one .seg rule: the Schedule's view switcher (.seg__btn) and the charts'
   Chart/Table toggles (.seg button). It used to be two rules whose values the
   cascade merged; these are the values that won, so nothing looks different. */
.seg {
  display: inline-flex; padding: 2px; background: var(--surface-3); border-radius: var(--r-sm); gap: 2px;
  border: 1px solid var(--hairline); overflow: hidden;
}
.seg button {
  border: 0; background: none; padding: 3px 9px; border-radius: 1px; font-size: var(--fs-6);
  font-weight: 600; color: var(--ink-2); cursor: pointer;
}
.seg button.is-active { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-sm); }

/* ═══════════════════════════════════════════════════════════════════════════
   Collapsible groups — the project list folded by status.

   The band is a row inside the table (see `groups` in htmlTable), so every
   group shares one set of column widths and one horizontal scrollbar. Folded
   groups keep their band on screen with their count and their subtotals, so
   the shape of the book is never hidden: you can see there are eleven Closed
   jobs worth $412k without opening them.
   ═══════════════════════════════════════════════════════════════════════════ */
.group__chev {
  display: grid; place-items: center; width: 14px; height: 14px; flex: none;
  color: var(--ink-muted); transition: transform var(--dur-2) var(--ease-out);
}
.group__chev svg { width: 14px; height: 14px; }
/* Keyed off aria-expanded wherever it sits rather than off a specific class,
   so the chevron turns for any band without each one needing its own rule. */
[aria-expanded="true"] > .group__chev { transform: rotate(90deg); }
.group__count {
  min-width: 20px; padding: 0 6px; border-radius: var(--r-pill);
  background: var(--neutral-bg); color: var(--ink-2);
  font-size: var(--fs-4); font-weight: 700; line-height: 18px; text-align: center;
  font-variant-numeric: tabular-nums; flex: none;
}

/* ── alerts panel ────────────────────────────────────────────────────── */
.alert-list { display: flex; flex-direction: column; }
.alert {
  display: flex; gap: var(--sp-3); padding: 11px var(--sp-5); align-items: flex-start;
  border-bottom: 1px solid var(--hairline); cursor: pointer; background: none; border-left: 0;
  border-right: 0; border-top: 0; width: 100%; text-align: left; font: inherit; color: inherit;
}
.alert:hover { background: var(--surface-2); }
.alert:last-child { border-bottom: 0; }
.alert__icon { width: 26px; height: 26px; border-radius: var(--r-sm); display: grid; place-items: center; flex: none; margin-top: 1px; }
.alert__icon svg { width: 14px; height: 14px; }
.alert__icon--critical { background: var(--critical-bg); color: var(--critical-text); }
.alert__icon--warning  { background: var(--warning-bg);  color: var(--warning-text); }
.alert__icon--serious  { background: var(--serious-bg);  color: var(--serious-text); }
.alert__icon--info     { background: var(--brand-100);   color: var(--info-text); }
.alert__body { min-width: 0; flex: 1; }
.alert__title { display: block; font-weight: 600; font-size: var(--fs-9); }
.alert__meta { display: block; font-size: var(--fs-7); color: var(--ink-muted); margin-top: 1px; }
/* the money at stake on the line, right-aligned like the Approvals badges (H2) */
.alert__amount { flex: none; font-size: var(--fs-9); color: var(--ink); margin-top: 1px; white-space: nowrap; }

/* ── modal ────────────────────────────────────────────────────────────────
   Opening: the ground dims while the dialog rises a few pixels and settles
   out of a barely-perceptible 1.5% scale. Closing plays the same thing
   backwards, slightly faster — putting something away should not take as
   long as getting it out. `closeModal()` in ui.js waits for the animation
   before removing the node.
   ──────────────────────────────────────────────────────────────────────── */
/* NO fill mode on the entrances. `both` holds the `from` keyframe whenever the
   animation has not actually run — a background tab, a screenshot tool, a
   paused compositor — and `from` here is `scale(.985) translateY(10px)`. That
   left the phone's full-bleed dialog sitting 20px narrow with a permanent
   transform, which is how the mobile check caught it. An element's resting
   geometry must not depend on an animation having played. */
.modal-backdrop {
  position: fixed; inset: 0; background: var(--overlay); z-index: var(--z-modal);
  display: grid; place-items: center; padding: var(--sp-5); overflow-y: auto;
  animation: gg-fade var(--dur-2) var(--ease);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
}
.modal {
  background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--shadow-lg);
  width: 100%; max-width: 560px; max-height: calc(90 * var(--vh)); display: flex; flex-direction: column;
  border: 1px solid var(--hairline-strong);
  animation: gg-open var(--dur-2) var(--ease-out);
  /* A grid item's default min-width is its content's, so a wide table pushed
     the whole dialog past a phone's edge instead of scrolling inside its own
     .table-wrap - the Issue dialog opened 369px off the left of a 390px
     screen, its Picking boxes out of reach. Measured in Edge 2026-09-28:
     with this, every dialog tried fits the phone and desktop is unchanged. */
  min-width: 0;
}
/* The exits DO hold their end state — the node is removed a frame later, and
   without `forwards` it would flash back to full opacity on the way out. */
.modal-backdrop.is-closing { animation: gg-fade var(--dur-1) var(--ease) reverse forwards; }
.is-closing .modal { animation: gg-open var(--dur-1) var(--ease) reverse forwards; }
/* A backdrop on its way out is invisible but still a full-screen fixed layer at
   z-index 100, and an invisible layer still takes clicks. Without this, the
   moment after dismissing a dialog — up to half a second if the animation
   never fires and the fallback timer has to do the removing — every click on
   the page underneath went nowhere. */
.modal-backdrop.is-closing { pointer-events: none; }
/* A message opened ON TOP of a dialog (modal({ over: true }) — a refused save,
   a sync failure). The dialog beneath stays in view so it is clear what the
   message is about, and a second full-strength overlay would dim the page
   twice over, so this one only shades it a little. */
.modal-backdrop[data-over] {
  background: color-mix(in srgb, var(--overlay) 45%, transparent);
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
/* A click outside the dialog, or Escape, no longer closes it (ui.js, "A DIALOG
   CLOSES ONLY WHEN SOMEONE CLOSES IT"). Instead the X lights up, so the way
   out is obvious. Colour and a ring only — nothing moves. */
.modal__head [data-close].is-nudged {
  background: var(--brand-100); color: var(--ink);
  box-shadow: 0 0 0 2px var(--accent);
}
.modal--wide { max-width: 900px; }
.modal--xwide { max-width: 1120px; }
.modal__head { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--hairline); }
.modal__title { font-size: var(--fs-15); font-weight: 600; letter-spacing: -.015em; }
.modal__body { padding: var(--sp-5); overflow-y: auto; flex: 1; }
.modal__foot { display: flex; gap: var(--sp-2); justify-content: flex-end; padding: var(--sp-4) var(--sp-5); border-top: 1px solid var(--hairline); background: var(--surface-2); border-radius: 0 0 var(--r-lg) var(--r-lg); }
/* "& next" (walk.js, 2026-10-14): where a step sits in the list it was
   opened from - ‹ 3 of 12 › - at the start of its window's footer. On a
   narrow screen, or the phone, it takes a row of its own above the buttons. */
.walk-bar { display: inline-flex; align-items: center; gap: 2px; flex: none; margin-right: auto; }
.walk-bar__at { min-width: 6ch; text-align: center; white-space: nowrap; }
.modal__foot .walk-bar .btn { flex: none; }
/* a list's window (walk.js, 2026-11-02): ‹ n of N › in its head, beside the X */
.walk-bar--head { margin-right: 6px; }
/* "& next" on a button: one word-space from the word before it, not the
   button's 7px gap (walk.js dress) */
.btn > [data-walk-next] { margin-left: calc(.28em - 7px); }
/* the next row after a decision made in a row (walk.js, 2026-11-03) */
tr.is-next-row > td, .is-next-row:not(tr) { background: var(--brand-50); }
tr.is-next-row > td:first-child, .is-next-row:not(tr) { box-shadow: inset 3px 0 0 var(--accent); }
/* ‹ n of N › at the top of a screen opened from a list (phone.js, 2026-11-03) */
.ph-walk { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 4px var(--ph-pad) 0; }
.ph-walk__at { color: var(--ph-ink-2); font-size: var(--fs-13); font-weight: 700; }
.ph-walk__go {
  display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; padding: 0;
  border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r); background: var(--ph-card); color: var(--ph-ink);
}
.ph-walk__go svg { width: 20px; height: 20px; }
.ph-walk__go:disabled { opacity: .35; }
/* a phone list's Sort picker and filter box (ui.js makeRowList, 2026-11-03) */
.ph-listtools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 8px; }
.ph-listtools__search { flex: 1 1 180px; margin: 0; }
.ph-listtools__count { color: var(--ph-ink-2); font-size: var(--fs-13); font-weight: 600; }
.ph-listtools__count:empty { display: none; }
.ph-sortpick { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; max-width: 100%; min-width: 0; }
.ph-sortpick__word { color: var(--ph-ink-2); font-size: var(--fs-13); font-weight: 700; }
.ph-sortpick__select {
  min-width: 0; max-width: 100%; height: 40px; padding: 0 10px; border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r);
  background: var(--ph-card); color: var(--ph-ink); font: inherit; font-size: var(--fs-16); font-weight: 600; /* 16px+: an iPhone never zooms */
}
.ph-sortpick__select:focus { outline: none; box-shadow: 0 0 0 3px var(--ph-accent); }
@media (max-width: 720px) {
  .modal__foot:has(.walk-bar) { flex-wrap: wrap; }
  .modal__foot .walk-bar { flex: 1 0 100%; justify-content: center; margin-right: 0; }
}
:root[data-ui="phone"] .modal__foot:has(.walk-bar) { flex-wrap: wrap; }
:root[data-ui="phone"] .modal__foot .walk-bar { flex: 1 0 100%; justify-content: center; margin: 0; }
:root[data-ui="phone"] .modal__foot .walk-bar .btn { flex: none; width: 44px; height: 44px; }

/* ── the photo viewer (projects.js photoViewer) ──────────────────────────
   A dark stage the picture is fitted into, whatever its shape; the name and
   Full size under it; Previous · 3 of 89 · Next along the bottom. On a phone
   the dialog is already the whole screen (the 720px block below), so the
   stage takes whatever height is left and the buttons sit at thumb height.
   The stage is dark in both themes: a photo reads truest against near-black. */
.modal--viewer { max-width: 1120px; }
.modal--viewer .modal__body { padding: 0; display: flex; flex-direction: column; overflow: hidden; }
.modal--viewer .modal__foot { justify-content: space-between; align-items: center; }
.viewer { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.viewer__stage {
  position: relative; height: calc(64 * var(--vh)); min-height: 240px;
  display: grid; place-items: center; overflow: hidden;
  background: #111418; color: #E8ECF2;
  /* the browser keeps vertical scrolling and pinch-zoom; a sideways drag is
     left to the swipe handler */
  touch-action: pan-y pinch-zoom; user-select: none; -webkit-user-select: none;
}
.viewer__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
/* the list's thumbnail, standing in while the photo loads */
.viewer__img--thumb { filter: blur(1.5px); opacity: .85; }
.viewer__loading {
  position: relative; z-index: var(--z-raised); font-size: var(--fs-10); color: #E8ECF2;
  background: rgba(17, 20, 24, .7); padding: 4px 12px; border-radius: 999px;
}

/* ── thumbnails in the file list (projects.js thumbTile) ───────────────── */
.file-name { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
/* a camera's file name has no spaces to break at - let it wrap anywhere rather
   than run off a phone */
.file-name > div { overflow-wrap: anywhere; }
.file-thumb {
  flex: none; width: 40px; height: 40px; padding: 0; overflow: hidden;
  display: grid; place-items: center;
  border: 1px solid var(--hairline); border-radius: var(--r-sm); background: var(--surface-2);
}
button.file-thumb { cursor: pointer; }
button.file-thumb:hover { border-color: var(--accent); }
.file-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.file-thumb__label {
  font-size: var(--fs-1); font-weight: 650; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-muted);
}
.file-thumb.is-missing { border-style: dashed; }
@media (max-width: 720px) { .file-thumb { width: 48px; height: 48px; } }
/* look-alike photos, side by side for their uploader (2026-10-14) */
.lookalike { border-top: 1px solid var(--hairline); padding-top: var(--sp-3); }
.lookalike__row { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-2); }
.lookalike__item { display: grid; gap: 4px; justify-items: start; width: 132px; min-width: 0; }
.lookalike__img { width: 132px; height: 99px; }
.lookalike__img img { width: 100%; height: 100%; object-fit: cover; }

/* ── a photo on each inventory item (2026-10-27, item-photos.js) ──────────
   The kind's drawing is always under it, so an item with no photo - or one
   that has not loaded, or failed - shows its kind, never a broken picture.
   The photo is laid over the drawing; a failed one is taken away again. */
.inv-desc { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.inv-desc__text { min-width: 0; }
.item-thumb {
  position: relative; flex: none; width: 36px; height: 36px; overflow: hidden;
  display: grid; place-items: center;
  border: 1px solid var(--hairline); border-radius: var(--r-sm); background: var(--surface-2); color: var(--ink-muted);
}
.item-thumb svg { width: 18px; height: 18px; }
.item-thumb img, .item-photo__frame img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; background: var(--surface);
}
.item-thumb.is-missing, .item-photo.is-missing .item-photo__frame { border-style: dashed; }
.input--file { height: auto; padding: 8px; }
/* the item's panel: its photo beside what it is */
.item-panel__top { display: flex; align-items: flex-start; gap: var(--sp-4); flex-wrap: wrap; }
.item-panel__top > .facts { flex: 1 1 300px; }
.item-photo { margin: 0; display: grid; gap: var(--sp-2); width: 200px; max-width: 100%; }
.item-photo__frame {
  position: relative; width: 100%; aspect-ratio: 1 / 1; overflow: hidden;
  display: grid; place-items: center; align-content: center; gap: var(--sp-1);
  border: 1px solid var(--hairline); border-radius: var(--r-md); background: var(--surface-2); color: var(--ink-muted);
}
.item-photo__frame svg { width: 44px; height: 44px; }
.item-photo__none { font-size: var(--fs-6); font-weight: 600; }
.item-photo__caption { font-size: var(--fs-6); color: var(--ink-muted); overflow-wrap: anywhere; }
.item-photo__acts, .item-photo__confirm { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.item-photo__acts[hidden], .item-photo__confirm[hidden], .item-photo__busy[hidden] { display: none; }
.item-photo__confirm > span { flex: 1 1 100%; font-size: var(--fs-7); }
.item-photo__busy { margin: 0; font-size: var(--fs-6); color: var(--ink-muted); }
@media (max-width: 720px) { .item-photo { width: 100%; } .item-photo__frame { max-width: 320px; } }
/* on the phone: the Workshop's tile, its tokens only (CONVENTIONS, the phone app) */
.ph-ico.item-thumb { border: 0; border-radius: 8px; color: var(--ph-hue-ink); }
.ph-ico.item-thumb svg { width: 19px; height: 19px; stroke-width: 2.2; }
.item-photo--phone { width: auto; margin: 0 calc(var(--ph-pad) + 4px) 24px var(--ph-pad); }
.item-photo--phone .item-photo__frame {
  max-width: none; aspect-ratio: 4 / 3; border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r);
  background: var(--ph-fill); color: var(--ph-ink-2); box-shadow: var(--ph-lift);
}
.item-photo--phone .item-photo__frame svg { width: 56px; height: 56px; }
.item-photo--phone .item-photo__none { font-size: var(--fs-10); font-weight: 800; color: var(--ph-ink-2); }
.item-photo--phone .item-photo__caption { font-size: var(--fs-8); color: var(--ph-ink-2); }
.item-photo--phone .item-photo__confirm > span { font-size: var(--fs-10); font-weight: 700; color: var(--ph-ink); }
.item-photo--phone .item-photo__busy { font-size: var(--fs-8); color: var(--ph-ink-2); }
.item-photo--phone .item-photo__acts .ph-btn, .item-photo--phone .item-photo__confirm .ph-btn { flex: 1 1 auto; }
.lookalike__name { max-width: 132px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.viewer__tile { max-width: 380px; padding: var(--sp-5); text-align: center; display: grid; gap: var(--sp-2); }
.viewer__tile p { color: #B7C1D0; }
/* Under the picture: everything the file's row says (name, record, added,
   by, size), then its buttons - the row's own. */
.viewer__meta {
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); align-items: center; justify-content: space-between;
  padding: var(--sp-3) var(--sp-5); border-top: 1px solid var(--hairline);
}
.viewer__info { flex: 1 1 260px; min-width: 0; }
.viewer__acts { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; justify-content: flex-end; }
.viewer__acts > .row { flex-wrap: wrap; }
.viewer__name { font-weight: 600; overflow-wrap: anywhere; }
.viewer__count { font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--ink-2); }
.viewer__nav { min-height: 44px; min-width: 128px; justify-content: center; }
@media (max-width: 720px) {
  .viewer__stage { height: auto; flex: 1; min-height: 0; }
  .viewer__meta { padding: var(--sp-2) var(--sp-4); }
  /* the buttons start at the left edge under the details, finger-sized */
  .viewer__acts { flex: 1 1 100%; justify-content: flex-start; }
  .viewer__acts .btn { height: 40px; }
  .viewer__acts .btn--icon { width: 40px; }
  .viewer__nav { min-height: 52px; min-width: 0; }
  .modal--viewer .modal__foot .viewer__count { flex: 0 0 auto; padding: 0 var(--sp-1); }
}

/* ── the phone's camera roll (projects.js photoGrid) ─────────────────────
   720px and under, where tables become cards: Photos & files is square
   thumbnails edge to edge, three across on a phone, and the table beside it
   is hidden. Wider than that, the grid is hidden and the table is as it was. */
.photo-grid { display: none; }
@media (max-width: 720px) {
  .files-table--gridded { display: none; }
  .photo-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 2px;
    margin-top: var(--sp-2);
  }
}
.photo-grid__day {
  grid-column: 1 / -1; padding: var(--sp-3) 0 var(--sp-1);
  font-size: var(--fs-7); font-weight: 620; color: var(--ink-2);
}
.photo-grid__day:first-child { padding-top: 0; }
.photo-tile {
  position: relative; aspect-ratio: 1 / 1; min-width: 0; padding: 0; margin: 0;
  border: 0; border-radius: var(--r-sm); overflow: hidden; cursor: pointer;
  background: var(--surface-2);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
}
.photo-tile img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.photo-tile:focus-visible { outline-offset: -2px; }
/* no picture: what it is, and its name */
.photo-tile--plain { border: 1px solid var(--hairline); padding: 6px; }
.photo-tile .file-thumb__label { font-size: var(--fs-6); }
.photo-tile__name {
  max-width: 100%; font-size: var(--fs-3); line-height: 1.25; color: var(--ink-2); text-align: center;
  overflow-wrap: anywhere; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.photo-tile--video { background: #1B2027; border-color: #1B2027; color: #E8ECF2; }
.photo-tile--video .photo-tile__name { color: #B7C1D0; }
.photo-tile__play { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%; background: rgba(255, 255, 255, .16); }
.photo-tile__play svg { width: 18px; height: 18px; margin-left: 2px; }
.photo-tile.is-missing { border: 1px dashed var(--hairline-strong); }
/* a record: a small lock in the corner, readable over any photo */
.photo-tile__rec {
  position: absolute; right: 4px; bottom: 4px; width: 22px; height: 22px;
  display: grid; place-items: center; border-radius: 50%;
  background: rgba(17, 20, 24, .72); color: #fff;
}
.photo-tile__rec svg { width: 14px; height: 14px; }

/* ── toast ───────────────────────────────────────────────────────────── */
.toasts { position: fixed; bottom: var(--sp-5); right: var(--sp-5); z-index: var(--z-toast); display: flex; flex-direction: column; gap: var(--sp-2); }
.toast {
  background: var(--ink); color: var(--plane); padding: 10px var(--sp-4); border-radius: var(--r-md);
  box-shadow: var(--shadow-lg); font-size: var(--fs-9); display: flex; align-items: center; gap: var(--sp-3);
  animation: gg-rise var(--dur-2) var(--ease-out); max-width: 420px;
}
.toast--good { background: var(--good-text); color: #fff; }
.toast--bad  { background: var(--critical); color: #fff; }
/* The third tier. Without it every "nothing broke, but look at this" message —
   a partial import, a queue still needing a decision, an action with nothing to
   do — had to pick between claiming success it had not earned and crying error
   it had not hit. Most picked the default dark toast, which is the one thing
   worse than either: indistinguishable from a message nobody styled at all.
   Ink on amber, not white: #FAB219 cannot carry white text. */
.toast--warn { background: var(--warning); color: #3A2A00; font-weight: 600; }

/* ── misc ────────────────────────────────────────────────────────────── */
.stack { display: flex; flex-direction: column; gap: var(--sp-4); }
/* Label-and-value pairs in a panel (an inventory item, 2026-10-07): two
   columns on a desk, one on a phone. */
.facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3) var(--sp-5); margin: 0; }
.facts > div { min-width: 0; }
.facts dt { font-size: var(--fs-6); font-weight: 600; color: var(--ink-muted); }
.facts dd { margin: 2px 0 0; font-size: var(--fs-9); color: var(--ink); overflow-wrap: anywhere; }
@media (max-width: 560px) { .facts { grid-template-columns: minmax(0, 1fr); } }
.row { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.row--tight { gap: var(--sp-2); }
/* Fields side by side line up at the top: one with a note under it made the
   row taller, and centring pushed its neighbour's box down (Trade beside
   Expense type in the approval window - Sagar, TEST, 2026-11-02) */
.row > .field { align-self: flex-start; }
.spacer { flex: 1; }
.muted { color: var(--ink-muted); }
.small { font-size: var(--fs-7); }
.strong { font-weight: 650; }
.nowrap { white-space: nowrap; }
.tnum { font-variant-numeric: tabular-nums; }
/* Three tiers, and a figure must sit in the right one. `.neg` is a bad
   outcome — over budget, a shortfall, money not recovered. `.warn` is a figure
   that wants attention but has not gone wrong: stock at its reorder point, a
   request waiting on approval, a queue to work through. Painting those red was
   the old behaviour and it cost the red its meaning — a stock room that is
   simply due a reorder lit up exactly like a job running over. */
.neg  { color: var(--critical-text); }
.pos  { color: var(--good-text); }
.warn { color: var(--warning-text); }
.hr { height: 1px; background: var(--hairline); border: 0; margin: var(--sp-5) 0; }
.meter { height: 4px; border-radius: 0; background: var(--surface-3); overflow: hidden; min-width: 60px; }
.meter__fill { height: 100%; border-radius: 0; background: var(--brand-500); }
.meter__fill--over { background: var(--critical); }
.meter__fill--warn { background: var(--warning); }
.help-btn { color: var(--ink-muted); }
.help-btn:hover { color: var(--link); }
/* ── empty states ────────────────────────────────────────────────────────
   A fresh job and a cleared queue are the first thing a new person sees, and
   two lines of grey text in a wide expanse of nothing reads as a page that
   failed rather than one that is simply empty. */
.empty {
  padding: var(--sp-7) var(--sp-5); text-align: center; color: var(--ink-muted);
  display: flex; flex-direction: column; align-items: center;
}
.empty__mark {
  width: 46px; height: 46px; display: grid; place-items: center;
  margin-bottom: var(--sp-4); border: 1px solid var(--hairline);
  border-radius: var(--r-md); background: var(--surface-2); color: var(--ink-muted);
}
.empty__mark svg { width: 22px; height: 22px; }
.empty__title {
  font-family: var(--font-display); font-size: var(--fs-12); font-weight: 600;
  color: var(--ink); margin-bottom: 5px;
}
.empty__msg { max-width: 46ch; line-height: 1.55; }
/* the same pattern, one row high, where a table would be (plan #55) */
.empty-row { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 10px; }
.empty-row__mark { display: inline-grid; place-items: center; width: 28px; height: 28px; flex: none;
                   border: 1px solid var(--hairline); border-radius: var(--r-sm); background: var(--surface-2); }
.empty-row__mark svg { width: 15px; height: 15px; }
.empty-row__act { margin-left: 6px; }

/* a tile's 13-week trend and its change (plan #55) */
.kpi__trend { margin-top: 6px; height: 30px; }
.spark { display: block; width: 100%; height: 30px; overflow: visible; }
.spark__line { stroke: var(--ink-muted); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; opacity: .7; }
/* a zero-length round-capped line, not a circle: the svg stretches to the tile,
   and a circle stretched with it became an oval. The stroke does not scale. */
.spark__now { stroke: var(--accent); stroke-width: 8; stroke-linecap: round; }
.spark__hit { fill: transparent; }
.spark__hit:hover { fill: color-mix(in srgb, var(--ink-muted) 10%, transparent); }
.delta { font-family: var(--font); font-size: var(--fs-4); font-weight: 600; letter-spacing: 0;
         color: var(--ink-muted); white-space: nowrap; vertical-align: middle; }
.delta__vs { font-weight: 500; color: var(--ink-muted); }
.delta.pos { color: var(--good-text); }        /* .delta comes after .pos/.neg would otherwise... */
.delta.neg { color: var(--critical-text); }    /* ...lose to its muted ink; only the direction is coloured */

/* timeline */
.timeline { position: relative; padding-left: 22px; }
.timeline::before { content: ""; position: absolute; left: 6px; top: 6px; bottom: 6px; width: 1px; background: var(--hairline); }
.timeline__item { position: relative; padding: 0 0 var(--sp-4); }
.timeline__item::before {
  content: ""; position: absolute; left: -19px; top: 5px; width: 9px; height: 9px;
  border-radius: 50%; background: var(--surface); border: 2px solid var(--hairline-strong);
}
.timeline__item--brand::before { border-color: var(--brand-500); }
.timeline__meta { font-size: var(--fs-6); color: var(--ink-muted); }

/* weekly cash grid */
.grid-scroll { overflow: auto; max-height: calc(62 * var(--vh)); }
.grid-input {
  width: 100%; border: 1px solid transparent; background: transparent; padding: 5px 8px;
  text-align: right; font-variant-numeric: tabular-nums; border-radius: var(--r-sm); font-size: var(--fs-8);
}
.grid-input:hover { border-color: var(--hairline-strong); }
.grid-input:focus { outline: none; border-color: var(--brand-500); background: var(--surface); box-shadow: 0 0 0 3px var(--brand-100); }
.grid-input::placeholder { color: var(--ink-muted); opacity: .5; }
/* A box that has to be FILLED IN, not a cell that can be edited. The pick
   dialog's quantities start empty on purpose (a pre-filled number is how the
   full request got charged), and an empty transparent box is invisible. */
.grid-input--entry { border-color: var(--hairline-strong); background: var(--surface); }
td.is-today { background: var(--brand-50); }
:root[data-theme="dark"] td.is-today { background: var(--brand-50); }

/* login */
.login { min-height: calc(100 * var(--vh)); display: grid; grid-template-columns: 1fr 1fr; }
/* One solid panel in the sidebar's colour (2026-10-08 review, #6): the
   gradient dated it, and a sign-in in the same material as the sidebar is
   the first sight of the desk the person is about to open. */
.login__brand {
  background: var(--sidebar); border-right: 1px solid var(--sidebar-line);
  color: var(--logo-ink); padding: var(--sp-7); display: flex; flex-direction: column; justify-content: center; gap: var(--sp-5);
}
.login__brand .logo { max-width: 260px; }
.login__tagline { font-size: var(--fs-16); color: var(--sidebar-ink); max-width: 400px; line-height: 1.55; }
.login__foot { color: var(--sidebar-muted); }
/* "Powered by Argus" at the sign-in (2026-10-12): the brand panel's bottom
   corner on the desk. A phone hides the panel's text and shows the copy after
   the form instead (argus-tag--login-foot, at the end of this file). */
.login__brand { position: relative; }
.login__brand > .argus-tag { --argus-h: 12px; position: absolute; left: var(--sp-7); bottom: var(--sp-6); }
.login__brand > .argus-tag .argus-tag__by { font-size: var(--fs-3); }
.argus-tag--login-foot { display: none; }
.login__form { display: grid; place-items: center; padding: var(--sp-6); background: var(--surface); }
.login__inner { width: 100%; max-width: 340px; }
@media (max-width: 860px) { .login { grid-template-columns: 1fr } .login__brand { display: none } }

/* =========================================================================
   RESPONSIVE — the field crew opens this on a phone and the office opens it
   on a 27" monitor, so both are first-class layouts rather than one being a
   fallback from the other. Four steps:

       wide desktop  →  desktop  →  tablet drawer  →  phone cards

   Everything from 1081px to 1599px is the baseline and is deliberately
   untouched by the wide tier below: most people are on a laptop, and their
   dashboard should not change because someone else bought a bigger screen.
   ========================================================================= */

/* ── wide desktop: spend the extra width on more information ───────────────
   Simply letting the existing layout stretch would give six enormous KPI
   tiles and charts a metre across. Instead the extra room buys more columns
   and a wider reading column, and the chart grid is capped at three across
   so a chart never becomes a letterbox. */
@media (min-width: 1600px) {
  .kpi-row { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--sp-4); }
  .chart-grid { grid-template-columns: repeat(auto-fit, minmax(520px, 1fr)); }
  .form-grid { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
  .chart-box { height: 320px; }
  .chart-box--tall { height: 400px; }
}
@media (min-width: 2100px) {
  /* Past this width an auto-fit chart grid would start a fourth column and
     the charts stop being readable as a pair. Three is the ceiling. */
  .chart-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/* ── compact tiles on a desktop (2026-09-29, with TIGHT ROWS above) ────────
   The tiles are a summary to glance at, not the work: at 1920 nine of them
   took two rows and a quarter of the screen before the first item. Smaller
   padding and figures, and a narrower minimum so they share one row where
   they can. After the wide-desktop tier on purpose - same specificity, so the
   later rule wins. The headline row (.kpi-row--lead) keeps its own sizes. */
@media (min-width: 721px) and (pointer: fine) {
  .kpi-row { gap: var(--sp-3); margin-bottom: var(--sp-4); }
  .kpi { padding: var(--sp-3) var(--sp-4); }
  .kpi__value { font-size: var(--fs-20); margin-top: 4px; }
  .kpi__foot { margin-top: 4px; }
}
@media (min-width: 1600px) and (pointer: fine) {
  .kpi-row { grid-template-columns: repeat(auto-fit, minmax(164px, 1fr)); gap: var(--sp-3); }
}

@media (max-width: 1180px) {
  .summary-split { grid-template-columns: 1fr !important; }
}

/* ── tablet & below: the sidebar becomes a drawer ─────────────────────── */
@media (max-width: 1080px) {
  .app { grid-template-columns: 1fr; background: var(--plane); }
  .sidebar {
    position: fixed; z-index: var(--z-sidebar); transform: translateX(-100%);
    transition: transform .22s cubic-bezier(.4,0,.2,1); width: min(var(--sidebar-w), calc(84 * var(--vw)));
    padding-top: env(safe-area-inset-top);
  }
  .sidebar.is-open { transform: none; box-shadow: var(--shadow-lg); }
  .scrim {
    position: fixed; inset: 0; background: var(--overlay); z-index: var(--z-scrim);
    animation: gg-fade var(--dur-2) var(--ease);
  }
  :root { --gutter: var(--sp-4); }         /* topbar and view both follow */
  .chart-grid { grid-template-columns: 1fr; }
}
@media (min-width: 1081px) { .nav-toggle, .scrim { display: none } }

/* ── phone ────────────────────────────────────────────────────────────── */
@media (max-width: 720px) {
  :root { --topbar-h: 54px; --gutter: var(--sp-3); }

  body { font-size: var(--fs-12); }                      /* 16px inputs below stop iOS zooming */
  .view { padding: var(--gutter) var(--gutter) var(--sp-6); }
  .view__title { font-size: var(--fs-17); }
  .view__head { gap: var(--sp-3); margin-bottom: var(--sp-4); }
  .view__actions { margin-left: 0; width: 100%; }
  /* A basis of 45% rather than `auto` caps the row at two text buttons, and
     min-width:0 lets them shrink. With `auto` the buttons were as wide as
     their labels and the row simply ran off the side of the screen the moment
     the label was a little wider than expected — which it is for the whole
     time a webfont is still loading and the metrically-different fallback is
     on screen. The layout must hold in whatever font is actually rendering. */
  .view__actions .btn { flex: 1 1 45%; justify-content: center; min-width: 0; }
  .view__actions .btn--icon { flex: 0 0 auto; }
  /* The help button joins the title instead of sitting alone on a row of its
     own under it - which it did on every page whose only action it is
     (a foreman's Projects, 2026-10-06). */
  .view__head { position: relative; }
  .view__head > :first-child { padding-right: 40px; }
  .view__actions > .help-btn { position: absolute; top: 0; right: 0; }
  .view__actions:not(:has(> :not(.help-btn))) { width: auto; margin: 0; }
  /* a job's header: code · status · type · address. The dots went to the ends
     of lines as it wrapped; here each item gets its own space, and the
     address its own line. */
  .view__meta { column-gap: 10px; row-gap: 4px; }
  .view__meta .view__sep { display: none; }
  .view__meta > :last-child { flex-basis: 100%; }

  /* clear the notch / status bar */
  .topbar {
    height: calc(var(--topbar-h) + env(safe-area-inset-top));
    padding-top: env(safe-area-inset-top);
  }

  /* two KPI tiles per row reads far better than one tall column */
  .kpi-row { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-2); }
  .kpi { padding: var(--sp-3); border-radius: var(--r-md); }
  .kpi__value { font-size: var(--fs-19); }
  .kpi__label { font-size: var(--fs-3); }
  .kpi__foot { font-size: var(--fs-5); }

  .card { border-radius: var(--r-md); }
  .card__head, .card__body { padding: var(--sp-3) var(--sp-4); }
  .card__tools { flex-wrap: wrap; }

  /* comfortable touch targets everywhere */
  .btn { height: 40px; padding: 0 14px; }
  .btn--sm { height: 34px; }
  /* padding: 0 again - the .btn line above is later in the file, so it put
     14px each side back inside a 34px icon button and crushed every icon on a
     phone to a 4px sliver (measured in Edge, 2026-09-28) */
  .btn--icon { width: 40px; padding: 0; }
  .btn--icon.btn--sm { width: 34px; }
  .input, .select { height: 42px; font-size: var(--fs-14); }
  .textarea { font-size: var(--fs-14); }
  .seg button { padding: 7px 12px; }
  .tab { padding: 11px 14px; }

  .chart-box { height: 240px; }
  .chart-box--tall { height: 280px; }
  .chart-box--short { height: 180px; }
  .chart-legend { padding: 0 var(--sp-4) var(--sp-3); }

  /* a long filter strip scrolls inside itself rather than pushing the page wide */
  .row { min-width: 0; }
  .seg {
    max-width: 100%; overflow-x: auto; overflow-y: hidden;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
  }
  .seg::-webkit-scrollbar { display: none; }
  .seg button { flex: 0 0 auto; white-space: nowrap; }

  .form-grid { grid-template-columns: 1fr; }
  .form-grid .span-2 { grid-column: span 1; }

  /* modals go full-bleed — a centred card wastes a phone screen */
  .modal-backdrop {
    padding: 0; align-items: stretch;
    /* The sheet is already the full height of the screen, so the backdrop has
       nothing to scroll — and letting it try is actively harmful: the 12px
       the entrance slides through counts as overflow, a scrollbar appears,
       and it takes 15px off the width of a dialog that is supposed to be
       edge to edge. The body inside still scrolls. */
    overflow: hidden;
  }
  .modal, .modal--wide, .modal--xwide {
    max-width: 100%; max-height: calc(100 * var(--dvh)); height: calc(100 * var(--dvh));
    border-radius: 0; border: 0;
  }
  .modal__body { padding: var(--sp-4); }
  .modal__foot {
    border-radius: 0; padding: var(--sp-3) var(--sp-4) calc(var(--sp-3) + env(safe-area-inset-bottom));
  }
  .modal__foot .btn { flex: 1; justify-content: center; }

  .toasts { left: var(--sp-3); right: var(--sp-3); bottom: calc(var(--sp-3) + env(safe-area-inset-bottom)); }
  .toast { max-width: none; }

  /* ── tables restack as labelled cards ─────────────────────────────── */
  .tbl--cards { display: block; }
  .tbl--cards thead { display: none; }
  .tbl--cards tbody, .tbl--cards tfoot { display: block; }
  .tbl--cards tr {
    display: block; border: 1px solid var(--hairline); border-radius: var(--r-md);
    padding: var(--sp-3); margin: 0 0 var(--sp-2); background: var(--surface);
  }
  .tbl--cards tbody tr:hover { background: var(--surface); }
  .tbl--cards td {
    display: flex; align-items: baseline; gap: var(--sp-3);
    padding: 3px 0; border: 0; text-align: left !important;
  }
  .tbl--cards td::before {
    content: attr(data-label); flex: 0 0 42%; color: var(--ink-muted);
    font-size: var(--fs-5); font-weight: 620; letter-spacing: .03em; text-transform: uppercase;
  }
  .tbl--cards td.tbl__lead {                  /* first cell = the card heading */
    font-weight: 660; font-size: var(--fs-13); padding-bottom: var(--sp-2);
    margin-bottom: var(--sp-2); border-bottom: 1px solid var(--hairline);
  }
  /* The heading has no label, but its empty label column still took 42-46%
     of the card - every card's heading started halfway across the phone
     (seen on the files list, 2026-09-29; the 09-28 screenshots show it too). */
  .tbl--cards td.tbl__lead::before { content: none; }
  /* an action column has no header, so it gets the card's footer rather than a
     label — otherwise the button sat under an empty 42% gutter. */
  .tbl--cards td.tbl__act {
    justify-content: flex-end; padding-top: var(--sp-2); margin-top: var(--sp-2);
    border-top: 1px solid var(--hairline);
  }
  .tbl--cards td.tbl__act::before { content: none; }
  .tbl--cards td.num { justify-content: flex-start; }
  .tbl--cards td.num > * { margin-left: auto; }
  .tbl--cards td:empty { display: none; }
  .tbl--cards tfoot tr { background: var(--surface-2); border-color: var(--hairline-strong); }
  .tbl--cards tfoot td { font-weight: 700; }
  /* group banners stay full-width dividers rather than becoming cards */
  .tbl--cards tr.group-row {
    background: var(--surface-3); border: 0; border-radius: 0;
    padding: var(--sp-2) var(--sp-3); margin-top: var(--sp-3);
  }
  .tbl--cards tr.group-row td { display: block; padding: 0; position: static; }
  .tbl--cards tr.group-row td::before { content: none; }
  /* A status band's subtotals restack as a column of unlabelled numbers under
     the group name, which reads as nonsense. The band keeps its name and its
     count; the totals are a desktop affordance. */
  .tbl--cards tr.group-row td + td { display: none; }
  /* the meter cells have no useful label on a card */
  .tbl--cards td .meter { min-width: 90px; }

  /* the cash grid stays a real grid — but the week column pins */
  .grid-scroll { max-height: none; }
  .grid-scroll .tbl th:first-child,
  .grid-scroll .tbl td:first-child {
    position: sticky; left: 0; background: var(--surface); z-index: var(--z-sticky-corner);
    box-shadow: 1px 0 0 var(--hairline);
  }
  .grid-scroll .tbl thead th:first-child { z-index: var(--z-sticky-corner-top); }
  .grid-input { min-height: 36px; font-size: var(--fs-14); }

  .timeline { padding-left: 18px; }
  .login__inner { max-width: none; }
  .login__form { padding: var(--sp-4); }
}

/* opened with no signal (app.js, 2026-10-06) */
.no-signal {
  max-width: 520px; margin: 0 auto; padding: calc(16 * var(--vh)) var(--sp-5) var(--sp-5);
  display: flex; flex-direction: column; gap: var(--sp-3); align-items: flex-start;
}

/* very small phones: one KPI per row rather than cramping two */
@media (max-width: 380px) {
  .kpi-row { grid-template-columns: 1fr; }
  .tbl--cards td::before { flex-basis: 46%; }
}

/* landscape phone / small tablet: keep two-up KPIs but relax the rest */
@media (min-width: 721px) and (max-width: 1080px) {
  .kpi-row { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   The pages, after the 2026-10-08 design review. One block per page, so each
   page's rules sit together. Colours are tokens only - every theme repaints
   these from its own set below.
   ═══════════════════════════════════════════════════════════════════════════ */
/* ── Summary ── */
/* "Are we OK, and what needs me?" first (2026-10-08 review, #19): after the
   band, one row of three - where cash runs lowest, the projects over budget,
   and the top of Needs attention. The attention list is the widest; the two
   tiles stretch to its height so their feet line up with its last line. */
.summary-lead {
  display: grid; gap: var(--sp-4); align-items: stretch;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.5fr);
}
.summary-lead .kpi { padding: var(--sp-4) var(--sp-5); }
.summary-lead .kpi__value { font-size: var(--fs-23); letter-spacing: -.03em; margin-top: 8px; }
/* The cash tile is a link card to the Cash forecast page (#21): the whole tile
   opens it, and its "Cash forecast" is the real link for the keyboard. */
.summary-cash { display: flex; min-width: 0; cursor: pointer; }
.summary-cash > .kpi { flex: 1; min-width: 0; }
.summary-cash .kpi__foot { align-items: flex-end; }
.summary-cash__go {
  margin-left: auto; flex: none; display: inline-flex; align-items: center; gap: 4px;
  font-weight: 600; color: var(--link); white-space: nowrap;
}
.summary-cash__go svg { width: 14px; height: 14px; }
/* Needs attention's top five: a line each, the detail on one line */
.summary-attn .alert { padding-top: 9px; padding-bottom: 9px; }
.summary-attn .alert__meta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* What kind of thing a line is, in ink; how serious, as the phone's lamp on
   the icon's corner (#23) - not every line the same amber tile. */
.alert__icon--type { position: relative; background: var(--surface-3); color: var(--ink-2); }
.alert__icon--type .lamp {
  position: absolute; top: -3px; right: -3px; width: 9px; height: 9px;
  outline: 2px solid var(--surface);
}
.alert:hover .alert__icon--type .lamp { outline-color: var(--surface-2); }
/* The figures behind the headline: nine go 5 + 4 with the last one wide, eight
   go 4 + 4, so the rows end square with no hole. */
.kpi-row.summary-tiles { margin-bottom: 0; }
.summary-tiles:has(> :nth-child(9):last-child) { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.summary-tiles:has(> :nth-child(8):last-child) { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.summary-tiles > :nth-child(9):last-child { grid-column: span 2; }
/* Spend, ranked (#22): the name, a thin bar against the largest, the amount in
   tabular figures - in place of a chart that was mostly an empty box. */
.rank-list { list-style: none; margin: 0; padding: var(--sp-3) var(--sp-5); }
.rank-list__row {
  display: grid; grid-template-columns: minmax(0, 10em) minmax(0, 1fr) auto; align-items: center;
  gap: var(--sp-3); padding: 5px 0; font-size: var(--fs-8);
}
.rank-list__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
.rank-list__bar { display: block; height: 6px; border-radius: var(--r-sm); background: var(--surface-3); overflow: hidden; }
.rank-list__fill { display: block; height: 100%; background: var(--series-1); }
.rank-list__amt { min-width: 8ch; text-align: right; font-family: var(--font-num); font-variant-numeric: tabular-nums; color: var(--ink); }
.rank-list__more { padding: 0 var(--sp-5) var(--sp-4); font-size: var(--fs-7); color: var(--ink-muted); }
/* Ctrl+K search (#44): a page's or a category's icon beside its name, as in
   the sidebar */
.search__icon {
  display: inline-grid; place-items: center; width: 16px; height: 16px; margin-right: 8px;
  vertical-align: -3px; color: var(--ink-muted);
}
.search__icon svg { width: 16px; height: 16px; }
@media (max-width: 1180px) {
  .summary-lead { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .summary-lead > .summary-attn { grid-column: 1 / -1; }
  .summary-tiles:has(> :nth-child(9):last-child),
  .summary-tiles:has(> :nth-child(8):last-child) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 720px) {
  /* minmax(0, …), not 1fr: a bare 1fr is at least its content's width, and the
     cash tile's line pushed the page to 753px on a 360px phone (mobile_check) */
  .summary-lead { grid-template-columns: minmax(0, 1fr); gap: var(--sp-2); }
  .summary-lead > .summary-attn { grid-column: auto; }
  .summary-lead .kpi { padding: var(--sp-3); }
  .summary-lead .kpi__value { font-size: var(--fs-19); }
  .rank-list { padding: var(--sp-2) var(--sp-4); }
  .rank-list__more { padding: 0 var(--sp-4) var(--sp-3); }
}
/* ── end Summary ── */
/* ── Project page ── */
/* The Projects list and a project's page (projects.js), after the review.

   #36 - the one notice for projects with no type: its button at the far end
   of the line. */
.proj-unset { margin-bottom: var(--sp-3); }
.proj-unset__go { margin-left: auto; }

/* #24 - the tag line: the phone plate's GG0001 ● ACTIVE, and on every tab but
   Overview the three figures a PM opens a project for, on the same line,
   instead of eight tiles repeated over every tab. */
.proj-tagline { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); margin: 2px 0 8px; }
.proj-tagline > .plate { margin: 0; align-self: center; }
.proj-strip { display: inline-flex; align-items: baseline; flex-wrap: wrap; gap: var(--sp-1) var(--sp-4); font-size: var(--fs-9); }
.proj-strip__fig { display: inline-flex; align-items: baseline; gap: 6px; white-space: nowrap; }
.proj-strip__fig + .proj-strip__fig { padding-left: var(--sp-4); border-left: 1px solid var(--hairline-strong); }
.proj-strip__fig[title] { cursor: help; }
.proj-strip__label {
  font-size: var(--fs-5); font-weight: 650; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-muted);
}
.proj-strip__value { font-weight: 650; font-family: var(--font-num); font-variant-numeric: tabular-nums; }

/* #24 - the tabs stay under the top bar as the page scrolls, on the page's
   own ground so nothing shows through them; on the test site under the
   banner too, the way the top bar itself moves down (the test site, below).
   Above the tables' frozen headings (--z-sticky-corner-top), below the top
   bar. A desk at phone width, and a desk page inside the phone, keep them in
   the flow: neither has this top bar. */
@media (min-width: 721px) {
  .proj-tabs {
    position: sticky; top: var(--topbar-h); z-index: calc(var(--z-sticky-corner-top) + 1);
    background: var(--plane);
  }
  :root[data-env="test"] .proj-tabs { top: calc(var(--topbar-h) + var(--env-offset)); }
  .ph-view--desk .proj-tabs { position: static; }
}

/* #27 - a typed budget or forecast ends on the same line as the total under
   it: the box is pulled out by its own padding and edge (8px + 1px), so its
   figure sits where a printed figure in the cell would. Its resting cue is
   .grid-input--edit, the Cash grid's (Cash and budget, below). */
@media (min-width: 721px) {
  .proj-trades td.num > .grid-input { width: calc(100% + 9px); margin-right: -9px; }
}

/* #35 - one next step per row in a fixed place, the rest under "⋯". The
   menu has a slot of its own whether a row has one or not, so the next step
   lands at the same x on every row. */
.proj-act { display: grid; grid-template-columns: auto 28px; align-items: center; justify-content: end; gap: var(--sp-1); }
.proj-act__next { display: inline-flex; align-items: center; justify-content: flex-end; gap: var(--sp-2); white-space: nowrap; }
.proj-act__more { display: inline-flex; justify-content: flex-end; }

/* #25 - where a figure comes from, in a quiet line by it */
.proj-src { margin: var(--sp-3) 0 0; font-size: var(--fs-7); line-height: 1.45; color: var(--ink-muted); }
.proj-src--lead { margin: 0 0 var(--sp-3); }

/* #43 - the project form: two columns in labelled bands (Project, Client,
   Insurance, Revenue), each ruled off from the one before; the adjuster, the
   carrier's estimator and the public adjuster are sub-heads inside
   Insurance. One column on a phone. */
.proj-form .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.proj-form .fieldset + .fieldset { padding-top: var(--sp-4); border-top: 1px solid var(--hairline); }
.proj-form__sub {
  grid-column: 1 / -1; margin-top: var(--sp-2); font-size: var(--fs-5); font-weight: 650;
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-2);
}
@media (max-width: 720px) {
  .proj-form .form-grid { grid-template-columns: minmax(0, 1fr); }
}
/* "New" (2026-10-09, T9): a photo added since you last opened the project,
   by someone else (store.seen). Ink, not a status colour - nothing is judged
   by it - in the label voice, like the job tag. On a tile it sits in the top
   corner, readable over any photo; in a table it rides after the name. */
.pill--new {
  background: var(--ink); color: var(--surface); font-size: var(--fs-2);
  font-family: var(--font-label); font-stretch: var(--label-stretch);
  letter-spacing: var(--label-spacing); text-transform: uppercase;
}
.photo-tile__new {
  position: absolute; left: 4px; top: 4px; z-index: var(--z-raised); padding: 1px 6px;
  border-radius: var(--r-pill); background: var(--ink); color: var(--surface); line-height: 1.5;
  font-family: var(--font-label); font-stretch: var(--label-stretch); font-weight: var(--label-weight);
  letter-spacing: var(--label-spacing); text-transform: uppercase; font-size: var(--fs-1);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .55);
}
/* From the field (T9): the newest photos first on a project's Overview, in
   one row on a desk, two on a laptop, three across on a phone */
.field-strip__row {
  display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 4px;
  padding: var(--sp-3) var(--sp-5) var(--sp-4);
}
.field-strip__new { font-weight: 700; color: var(--ink); }
@media (max-width: 1280px) { .field-strip__row { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 720px) { .field-strip__row { grid-template-columns: repeat(3, minmax(0, 1fr)); padding: var(--sp-3); } }
/* The walk-through as the phone has it (2026-10-09, T10): its own name in
   the label voice, folded to two lines with See all, above the notes */
.walk { background: var(--surface-2); }
.walk__head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-2); flex-wrap: wrap; }
.walk__tag {
  display: inline-block; font-size: var(--fs-5); color: var(--ink);
  font-family: var(--font-label); font-stretch: var(--label-stretch); font-weight: var(--label-weight);
  letter-spacing: var(--label-spacing); text-transform: uppercase;
}
.walk__body { white-space: pre-wrap; overflow-wrap: anywhere; margin-top: var(--sp-2); }
.walk__body.is-folded { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.walk__more {
  margin-top: var(--sp-1); padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--link); font-weight: 600; font-size: var(--fs-7);
}
.walk__more:hover { text-decoration: underline; }
/* To-dos (2026-10-22) - the desk's page (todos.js): one row a to-do, its tick
   first; a lit row is the one a notification opened */
.todos { margin-top: var(--sp-3); }
.todo { display: flex; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4); border-top: 1px solid var(--hairline); }
.todo:first-child { border-top: 0; }
.todo__tick { margin-top: 3px; width: 16px; height: 16px; flex: none; cursor: pointer; }
.todo__tick:disabled { cursor: default; }
.todo__body { flex: 1; min-width: 0; }
.todo__title { font-weight: 600; overflow-wrap: anywhere; }
.todo--done .todo__title { text-decoration: line-through; color: var(--ink-muted); font-weight: 500; }
.todo__notes { white-space: pre-wrap; overflow-wrap: anywhere; margin-top: 2px; }
.todo__meta { display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--sp-3); margin-top: var(--sp-1); }
.todo__acts { display: flex; gap: 2px; flex: none; }
.todo.is-lit { background: var(--brand-50); box-shadow: inset 3px 0 0 var(--accent); }
.tab__count { margin-left: 4px; color: var(--ink-muted); font-variant-numeric: tabular-nums; }
/* The scope above the project's tabs (2026-10-22, item 4): the walk-through's
   look, folded to three lines */
.scope-box { margin-bottom: var(--sp-4); }
.scope-box .walk__body.is-folded { -webkit-line-clamp: 3; }
.scope-box--none { display: flex; align-items: baseline; gap: var(--sp-2); }
/* The walk-through checklist (2026-10-22, item 10) - checklist.js: a part at a
   time, sections that fold, one row a line - its tick, what it asks with its
   answer under it, then N/A and Remove. A done line is quiet, an N/A one more so. */
.checklist__tabs { padding-bottom: 0; }
.checklist__tabs .tabs { margin-bottom: 0; }
.checklist__section { border-top: 1px solid var(--hairline); }
.checklist__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); width: 100%;
  padding: var(--sp-3) var(--sp-4); border: 0; background: var(--surface-2); color: var(--ink); cursor: pointer; text-align: left;
}
.checklist__head:hover { background: var(--surface-3); }
.checklist__name { font-weight: 650; }
.checklist__rows[hidden] { display: none; }
.checklist__row { display: flex; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-2) var(--sp-4); border-top: 1px solid var(--hairline); }
.checklist__tick { flex: none; padding-top: 3px; }
.checklist__tick input { width: 16px; height: 16px; cursor: pointer; }
.checklist__main { flex: 1; min-width: 0; }
.checklist__label { overflow-wrap: anywhere; }
.checklist__room { font-weight: 600; }
.checklist__row.is-done .checklist__label { color: var(--ink-2); }
.checklist__row.is-na .checklist__label { color: var(--ink-muted); text-decoration: line-through; }
.checklist__answer { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); margin-top: var(--sp-1); }
.checklist__input { text-align: left; max-width: 420px; }
.checklist__seg button[aria-checked="true"] { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-sm); }
.checklist__acts { display: flex; gap: 2px; flex: none; }
.checklist__acts [data-cl-na][aria-pressed="true"] { color: var(--ink); font-weight: 700; background: var(--surface-3); }
.checklist__more { padding: var(--sp-2) var(--sp-4) var(--sp-3) calc(var(--sp-4) + 16px + var(--sp-3)); }
/* Settings › Checklist: the company's list, a line each */
.checklist-set { list-style: none; margin: var(--sp-2) 0 0; padding: 0; }
.checklist-set__item { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); padding: 4px 0; border-top: 1px solid var(--hairline); }
.checklist-set__item.is-hidden .checklist-set__label { color: var(--ink-muted); }
.checklist-set__acts { display: flex; gap: 2px; flex: none; }
/* Printed, like his sheet: nothing on screen; while printing, nothing else */
.cl-print { display: none; }
/* ── end Project page ── */
/* ── Cash and budget ── */
/* An editable grid cell, at rest (2026-10-08 review, #26): .grid-input is
   borderless until hovered, so a column you can type into read like a printed
   report. A rule under the figure, in the control line a control is found by
   (3:1), says "type here" without boxing every cell of a 20-week grid; hover
   draws the whole box, and focus keeps its own ring. Square, or the rounded
   corners curl the rule's ends. */
.grid-input--edit { border-bottom-color: var(--control-line); border-radius: 0; }
.grid-input--edit:hover:not(:focus) { border-color: var(--control-line); }
/* ── end Cash and budget ── */
/* ── Workflow and requests ── */
/* ONE COLUMN GRID FOR EVERY CARD on Workflow (2026-10-08 review, #33): each
   card's table is laid out fixed at the widths approvals.js gives its columns
   (GRID_WIDTHS), so Project, From, Waiting and the next step sit in the same
   place in every card. A table someone has sized by dragging (.tbl--sized)
   keeps its own widths. A desk only - a phone restacks the rows as cards. */
@media (min-width: 721px) {
  :root:not([data-ui="phone"]) .wf-grid > .tbl:not(.tbl--sized) { table-layout: fixed; }
  .wf-grid > .tbl td:not(.tbl__act) { overflow: hidden; text-overflow: ellipsis; }
  /* What: its title and its detail one line each, cut with … - the cell's
     hover shows the whole of both */
  .wf-what > div { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* A card of one to three rows has no room under them for a ⋯ menu, and the
     table's scroll box cut the menu off. Fixed at the card's width it never
     scrolls sideways, and with so few rows it never scrolls down - so it has
     no scroll box, and the menu opens over whatever is below. */
  .table-wrap.wf-grid:not(:has(tbody > tr:nth-child(4))) { overflow: visible; }
}
/* A row's next step, first in its column, and its ⋯ at the column's end - so
   the step lands in the same place on every row (2026-10-08 review, #35) */
.wf-acts { display: flex; align-items: center; gap: var(--sp-1); }
.wf-acts > .rowmenu { margin-left: auto; }
/* ── end Workflow and requests ── */
/* ── Money pages ── */
/* The coding window's "Undo coding" (quickbooks.js, #9): one filled button per
   dialog, so the undo is a plain button that says it is destructive in red
   words, not a second red block beside the blue Save. */
.btn.alloc-undo { color: var(--critical-text); }
.btn.alloc-undo:hover { border-color: var(--critical); color: var(--critical-text); }
/* ── end Money pages ── */
/* ── Operations and settings ── */
/* A calendar's colour from eight fixed swatches (2026-10-08 review, #17): the
   radio is the whole chip, so the keyboard and a screen reader still pick one */
.cal-swatches { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.cal-swatch { position: relative; display: inline-grid; place-items: center; cursor: pointer; }
.cal-swatch input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.cal-swatch__chip { width: 28px; height: 28px; border-radius: 50%; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .18); }
.cal-swatch input:checked + .cal-swatch__chip { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--ink); }
.cal-swatch input:focus-visible + .cal-swatch__chip { outline: 2px solid var(--accent); outline-offset: 4px; }
/* The access grid at full strength (#40): "from their role" was faded to
   2.3:1 in dark. Inherited is a fill with no edge; granted and denied keep
   their edge and gain a corner mark; a locked row says so with its lock. */
.cap[data-state="inherit-yes"], .cap[data-state="inherit-no"], .cap[disabled] { opacity: 1; }
.cap[data-state="granted"], .cap[data-state="denied"] { position: relative; }
.cap[data-state="granted"]::after, .cap[data-state="denied"]::after {
  content: ""; position: absolute; top: 2px; right: 2px; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor;
}
/* Schedule: an event's title on two lines, not cut at ten letters (#52) */
.ev__title {
  white-space: normal; text-overflow: clip; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
/* ── end Operations and settings ── */

/* ═══════════════════════════════════════════════════════════════════════════
   The desk's themes (2026-10-08).

   Sagar picked five from the seven the design panel drew, then added Ledger
   (2026-10-09, "just add ledger"): Classic (the default - the tokens at the
   top of this file), Workshop, Ledger, Fieldstone, Blueprint and Obsidian.
   Everyone picks their own in "My view" (click your name in the sidebar);
   the Owner sets the company's default in Settings.

   A theme is <html data-skin="…">: token values first, then the few signature
   details that make it itself. Never new layout, never a different status
   colour - good, warning, serious and critical mean the same in every theme.
   The phone never takes a skin: it stays Workshop (desktop only for now).

   Each theme declares its dark under BOTH scopes, like the base above, AFTER
   its light block - the media-query copy has the same specificity as the light
   block, so it is the order that lets it win. Every dark mode keeps page,
   card, heading row and group band a visible step apart (Sagar, 2026-10-08:
   "some of the headers and tables start to run together on the dark mode").

   This section sits BEFORE the test site's, so the test site's purple chrome
   still wins over every theme.
   ═══════════════════════════════════════════════════════════════════════════ */

/* shared hooks the themes below switch on */
h1, h2, h3, .view__title, .kpi__value, .modal__title, .band__count, .band__title {
  font-stretch: var(--display-stretch, normal);
}

/* ── Workshop: the phone's look at full strength ─────────────────────────
   Concrete, ink outlines with a hard lift, caution yellow for "you are here"
   and "press this", Archivo Black. The loudest of the six. */
:root[data-skin="workshop"] {
  --font: 'Archivo', ui-sans-serif, system-ui, sans-serif;
  --font-display: 'Archivo Black', 'Archivo', sans-serif;
  --font-num: 'Archivo', ui-sans-serif, system-ui, sans-serif;
  --label-weight: 800;
  --r-sm: 8px; --r-md: 8px; --r-lg: 12px; --r-xl: 12px; --r-pill: 6px;
  --plane: #ECE9E2; --surface: #FFFFFF; --surface-2: #F4F1EA; --surface-3: #EAE6DC;
  --hairline: #E0DCD2; --hairline-strong: #0B0B0B; --control-line: #0B0B0B;
  --ink: #0B0B0B; --ink-2: #4A4740; --ink-muted: #5E5A52;
  --sidebar: #0B0B0B; --sidebar-2: #000000;
  --sidebar-ink: #E4E0D7; --sidebar-ink-hover: #FFFFFF; --sidebar-muted: #A39F95;
  --sidebar-hover: rgba(255, 255, 255, .08); --sidebar-active-bg: #FFD60A; --sidebar-active-ink: #0B0B0B;
  --sidebar-line: rgba(255, 255, 255, .10);
  --accent: #0B0B0B; --link: #0B0B0B;
  --primary: #FFD60A; --primary-hover: #FFE14D; --primary-ink: #0B0B0B; --primary-line: #0B0B0B;
  --btn-line-w: 2px; --btn-shadow: 3px 3px 0 #0B0B0B;
  --lamp: #FFD60A; --lamp-ring: rgba(0, 0, 0, .45);
  --tab-ink: var(--ink); --tab-line: #0B0B0B;
  --head-bg: var(--surface); --band-bg: var(--surface-3);
  --card-line: #0B0B0B; --card-line-w: 2px;
  --card-shadow: 4px 4px 0 #0B0B0B; --card-shadow-hover: 4px 4px 0 #0B0B0B;
  --topbar-bg: #ECE9E2; --topbar-ink: #0B0B0B; --topbar-muted: #4A4740; --topbar-line: #0B0B0B;
  --attention-bg: #FFD60A; --attention-line: #0B0B0B; --attention-ink: #0B0B0B;
  --count-bg: #0B0B0B; --count-ink: #FFD60A; --tape-ink: #0B0B0B; --tape-fill: #FFD60A;
  --chip-bg: #0B0B0B; --chip-ink: #FFFFFF;
  --series-1: #FFFFFF; --series-2: #FFD60A; --series-3: #7A756B;
  --series-1-line: #0B0B0B; --series-2-line: #0B0B0B;
}
@media (prefers-color-scheme: dark) {
  :root[data-skin="workshop"]:where(:not([data-theme="light"])) {
    --plane: #161513; --surface: #24231F; --surface-2: #2F2D29; --surface-3: #3A3833;
    --hairline: #45423C; --hairline-strong: #9C978C; --control-line: #9C978C;
    --ink: #F2EFE8; --ink-2: #C9C4B8; --ink-muted: #A39E93;
    --accent: #FFD60A; --link: #F2EFE8;
    --btn-shadow: 3px 3px 0 #000000;
    --card-line: #9C978C; --card-shadow: 4px 4px 0 #000000; --card-shadow-hover: 4px 4px 0 #000000;
    --head-bg: var(--surface-2); --tab-line: #FFD60A;
    --topbar-bg: #161513; --topbar-ink: #F2EFE8; --topbar-muted: #C9C4B8; --topbar-line: #9C978C;
    --chip-bg: #F2EFE8; --chip-ink: #0B0B0B;
    --series-1: transparent; --series-1-line: #F2EFE8;
  }
}
:root[data-skin="workshop"][data-theme="dark"] {
  --plane: #161513; --surface: #24231F; --surface-2: #2F2D29; --surface-3: #3A3833;
  --hairline: #45423C; --hairline-strong: #9C978C; --control-line: #9C978C;
  --ink: #F2EFE8; --ink-2: #C9C4B8; --ink-muted: #A39E93;
  --accent: #FFD60A; --link: #F2EFE8;
  --btn-shadow: 3px 3px 0 #000000;
  --card-line: #9C978C; --card-shadow: 4px 4px 0 #000000; --card-shadow-hover: 4px 4px 0 #000000;
  --head-bg: var(--surface-2); --tab-line: #FFD60A;
  --topbar-bg: #161513; --topbar-ink: #F2EFE8; --topbar-muted: #C9C4B8; --topbar-line: #9C978C;
  --chip-bg: #F2EFE8; --chip-ink: #0B0B0B;
  --series-1: transparent; --series-1-line: #F2EFE8;
}
/* titles and figures in Archivo Black: one weight, so never bolded again */
[data-skin="workshop"] :is(h1, h2, h3, .view__title, .kpi__value, .modal__title, .card__title, .band__title) { font-weight: 400; }
[data-skin="workshop"] .view__title { font-size: var(--fs-22); letter-spacing: -.02em; }
[data-skin="workshop"] .topbar { border-bottom-width: 2px; }
[data-skin="workshop"] .btn:not(.btn--ghost):not(.btn--primary):not(.btn--sm) { box-shadow: var(--btn-shadow); }
[data-skin="workshop"] .tbl th { border-bottom-width: 2px; }
[data-skin="workshop"] a:not(.btn):not(.nav__item):not(.band):not(.band__chip):not(.wf-chip) { text-decoration: underline; text-underline-offset: 3px; }
/* the phone's tab lights, on their side: a dim lamp by every page, the
   current one lit */
[data-skin="workshop"] .nav__item .nav__lamp {
  display: block; position: absolute; left: -10px; top: 50%; width: 8px; height: 8px; margin-top: -4px;
  border-radius: 50%; background: #4A4741; box-shadow: inset 0 1px 2px rgba(0,0,0,.8), 0 0 0 1px #5E5A52;
}
[data-skin="workshop"] .nav__item.is-active .nav__lamp {
  background: radial-gradient(circle at 40% 35%, rgba(255,255,255,.95) 0 1px, transparent 2.5px), var(--lamp);
  box-shadow: 0 0 3px 1px var(--lamp), 0 0 8px 2px var(--lamp);
}
[data-skin="workshop"] .nav__item { border-radius: 10px; }
/* (Workflow's strip of yellow tape came off 2026-10-08 - Sagar: "it just looks
   out of place", "I don't want it there in any mode". Every theme marks a
   waiting Workflow the one way now: the dim red glow by .nav__badge above.) */
/* the band: the phone's barricade - tape top and bottom, an ink edge */
[data-skin="workshop"] .band { border-width: 2px; }
[data-skin="workshop"] .band__tape { height: 12px; border-bottom: 2px solid var(--tape-ink);
  background: repeating-linear-gradient(-45deg, var(--tape-ink) 0 12px, var(--tape-fill) 12px 24px); }
[data-skin="workshop"] .band__tape--foot { display: block; border-bottom: 0; border-top: 2px solid var(--tape-ink); }
[data-skin="workshop"] .band__count { border-radius: 6px; font-weight: 400; }
[data-skin="workshop"] .band__title { font-size: var(--fs-18); }
[data-skin="workshop"] .band__chip { border: 2px solid #0B0B0B; border-radius: 6px; background: #FFFFFF; color: #0B0B0B; font-weight: 800; }
/* the screwed-on plate, in the bar where the running head would be */
[data-skin="workshop"] .crumbs { display: none; }
[data-skin="workshop"] .topbar__plate {
  display: inline-flex; align-items: center; gap: 8px; height: 32px; padding: 0 10px; flex: none;
  border: 1px solid #A9A59B; border-radius: 6px; color: #2B2A27;
  background: linear-gradient(180deg, #F8F7F3 0%, #DDDAD2 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.9), 0 1px 0 rgba(255,255,255,.7), 0 1px 3px rgba(11,11,11,.2);
  font-family: var(--font-label); font-stretch: 112.5%; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; font-size: var(--fs-6); white-space: nowrap;
}
[data-skin="workshop"] .topbar__plate .plate__cell + .plate__cell { padding-left: 8px; border-left: 1px solid rgba(43,42,39,.3); }
[data-skin="workshop"] .topbar__plate .plate__fix {
  display: block; position: relative; width: 10px; height: 10px; border-radius: 50%; flex: none;
  background: radial-gradient(circle at 35% 30%, #FBFAF7 0%, #CFCCC5 50%, #98958D 100%);
  box-shadow: inset 0 0 0 1px rgba(60,56,48,.4), 0 1px 0 rgba(255,255,255,.8);
}
[data-skin="workshop"] .topbar__plate .plate__fix::after {
  content: ""; position: absolute; left: 2px; right: 2px; top: 4px; height: 2px; border-radius: 1px;
  background: #5F5C55; transform: rotate(-40deg);
}
[data-skin="workshop"] .topbar__plate .plate__fix:last-child::after { transform: rotate(25deg); }
[data-skin="workshop"] .topbar__plate .lamp--good {
  background: radial-gradient(circle at 40% 35%, rgba(255,255,255,.95) 0 1px, transparent 2.5px), var(--good);
  box-shadow: 0 0 3px 1px var(--good), 0 0 7px 2px var(--good);
}
/* the tag is in the bar, so the page does not say it twice */
[data-skin="workshop"] .view__head .plate { display: none; }
/* chart bars: budget an outlined white bar, actual yellow with an ink stroke */
[data-skin="workshop"] .chart-legend__swatch:first-child { box-shadow: inset 0 0 0 2px var(--ink); }

/* ── Ledger: an annual report that happens to be live ──────────────────────
   Added 2026-10-09 (Sagar: "just add ledger"), from the design panel's
   typography board on the canvas. Paper and ink, square corners, no shadows;
   titles and figures in a Newsreader serif, everything else in Archivo, the
   phone's face. A row of figures is one ruled strip, a total is ruled off
   with the accountant's double rule, and a negative is written in brackets -
   ($1,417) - by ui.js money() while this theme is on. The quietest theme with
   a light sidebar; its one colour is a ledger-ink blue for links and focus. */
:root[data-skin="ledger"] {
  --argus-ink: var(--argus-navy);               /* a light sidebar */
  --font: 'Archivo', ui-sans-serif, system-ui, sans-serif;
  --font-display: 'Newsreader', 'Archivo', Georgia, serif;
  --font-num: 'Archivo', ui-sans-serif, system-ui, sans-serif;
  --r-lg: 0px; --r-xl: 0px;
  --plane: #F2F0EA; --surface: #FFFFFF; --surface-2: #F7F5F0; --surface-3: #EEEBE3;
  --hairline: #E3DFD5; --hairline-strong: #CBC5B8; --control-line: #8A8475;
  --grid: #ECE9E1; --axis: #CBC5B8; --neutral-bg: #F0EDE5;
  --ink: #1A1813; --ink-2: #4A453B; --ink-muted: #686253;
  --sidebar: #FAF8F3; --sidebar-2: #F2F0EA;
  --sidebar-ink: #2B2822; --sidebar-ink-hover: #1A1813; --sidebar-muted: #686253;
  --sidebar-hover: rgba(26, 24, 19, .05); --sidebar-active-bg: #1A1813; --sidebar-active-ink: #FFFFFF;
  --sidebar-line: #E3DFD5; --logo-ink: #1A1813;
  --accent: #2B4A7E; --link: #2B4A7E;
  --primary: #1A1813; --primary-hover: #36322A; --primary-ink: #FFFFFF; --primary-line: #1A1813;
  --lamp: #2B4A7E; --lamp-ring: #1A1813;
  --tab-ink: var(--ink); --tab-line: #1A1813;
  --head-bg: var(--surface-2); --band-bg: var(--surface-3);
  --card-line: #E3DFD5; --card-shadow: none; --card-shadow-hover: none;
  --topbar-bg: #FAF8F3; --topbar-ink: #1A1813; --topbar-muted: #686253; --topbar-line: #E3DFD5;
  --attention-bg: #FFF1B0; --attention-line: #1A1813; --attention-ink: #1A1813;
  --count-ink: #1A1813;
  --chip-bg: #1A1813; --chip-ink: #FFFFFF;
  --plate-bg: transparent; --plate-line: #1A1813; --plate-ink: #1A1813;
  --series-1: #2E5597; --series-2: #C0582E; --series-3: #1E8A68; --series-muted: #CBC5B8;
}
@media (prefers-color-scheme: dark) {
  :root[data-skin="ledger"]:where(:not([data-theme="light"])) {
    --argus-ink: var(--argus-white);
    --plane: #0C0B09; --surface: #1A1915; --surface-2: #24221D; --surface-3: #2E2B25;
    --hairline: #34312A; --hairline-strong: #4A463C; --control-line: #7D7664;
    --grid: #24221D; --axis: #4A463C; --neutral-bg: #2A2823;
    --ink: #F0ECE2; --ink-2: #CBC4B4; --ink-muted: #A39B89;
    --sidebar: #151410; --sidebar-2: #0F0E0B;
    --sidebar-ink: #E4DFD3; --sidebar-ink-hover: #FFFFFF; --sidebar-muted: #A39B89;
    --sidebar-hover: rgba(255, 255, 255, .06); --sidebar-active-bg: #F0ECE2; --sidebar-active-ink: #1A1915;
    --sidebar-line: #302D26; --logo-ink: #FFFFFF;
    --accent: #9DB5E3; --link: #A9BFEA;
    --primary: #F0ECE2; --primary-hover: #FFFFFF; --primary-ink: #1A1915; --primary-line: #F0ECE2;
    --lamp: #9DB5E3; --lamp-ring: #F0ECE2; --tab-line: #F0ECE2;
    --card-line: #34312A;
    --topbar-bg: #151410; --topbar-ink: #F0ECE2; --topbar-muted: #A39B89; --topbar-line: #302D26;
    --attention-bg: #3D3413; --attention-line: #F0ECE2; --attention-ink: #FFF0BF;
    --count-ink: #FFF0BF;
    --chip-bg: #F0ECE2; --chip-ink: #1A1915;
    --plate-line: #F0ECE2; --plate-ink: #F0ECE2;
    --series-1: #6A8DD4; --series-2: #D86C3E; --series-3: #3BA07F; --series-muted: #4A463C;
  }
}
:root[data-skin="ledger"][data-theme="dark"] {
  --argus-ink: var(--argus-white);
  --plane: #0C0B09; --surface: #1A1915; --surface-2: #24221D; --surface-3: #2E2B25;
  --hairline: #34312A; --hairline-strong: #4A463C; --control-line: #7D7664;
  --grid: #24221D; --axis: #4A463C; --neutral-bg: #2A2823;
  --ink: #F0ECE2; --ink-2: #CBC4B4; --ink-muted: #A39B89;
  --sidebar: #151410; --sidebar-2: #0F0E0B;
  --sidebar-ink: #E4DFD3; --sidebar-ink-hover: #FFFFFF; --sidebar-muted: #A39B89;
  --sidebar-hover: rgba(255, 255, 255, .06); --sidebar-active-bg: #F0ECE2; --sidebar-active-ink: #1A1915;
  --sidebar-line: #302D26; --logo-ink: #FFFFFF;
  --accent: #9DB5E3; --link: #A9BFEA;
  --primary: #F0ECE2; --primary-hover: #FFFFFF; --primary-ink: #1A1915; --primary-line: #F0ECE2;
  --lamp: #9DB5E3; --lamp-ring: #F0ECE2; --tab-line: #F0ECE2;
  --card-line: #34312A;
  --topbar-bg: #151410; --topbar-ink: #F0ECE2; --topbar-muted: #A39B89; --topbar-line: #302D26;
  --attention-bg: #3D3413; --attention-line: #F0ECE2; --attention-ink: #FFF0BF;
  --count-ink: #FFF0BF;
  --chip-bg: #F0ECE2; --chip-ink: #1A1915;
  --plate-line: #F0ECE2; --plate-ink: #F0ECE2;
  --series-1: #6A8DD4; --series-2: #D86C3E; --series-3: #3BA07F; --series-muted: #4A463C;
}
/* titles and figures in the serif, set lighter than a sans would be */
[data-skin="ledger"] :is(h1, .view__title) { font-weight: 500; letter-spacing: -.01em; }
[data-skin="ledger"] .view__title { font-size: var(--fs-22); }
[data-skin="ledger"] .card__title { font-size: var(--fs-17); font-weight: 600; letter-spacing: 0; }
[data-skin="ledger"] .kpi__value { font-weight: 500; letter-spacing: -.01em; font-variant-numeric: lining-nums; }
/* the page you are on is the inked row; no lamp beside it */
[data-skin="ledger"] .nav__item.is-active .nav__lamp { display: none; }
/* a row of figures is one ruled strip, as in a printed statement, not a row
   of boxes. The hover panel of a tile still opens over it (end of section). */
[data-skin="ledger"] .kpi-row {
  gap: 0; background: var(--surface); border: 1px solid var(--card-line); overflow: hidden;
}
[data-skin="ledger"] .kpi-row > .kpi {
  border: 0; border-radius: 0; background: transparent;
  box-shadow: -1px 0 0 var(--hairline), 0 -1px 0 var(--hairline);
}
/* column heads in sentence case over a ruled line; a total ruled off with one
   rule above and the double rule below */
[data-skin="ledger"] .tbl th {
  text-transform: none; letter-spacing: 0; font-size: var(--fs-7); font-weight: 600;
  border-bottom-color: var(--control-line);
}
[data-skin="ledger"] .tbl tfoot td {
  background: var(--surface); border-top: 1px solid var(--ink); border-bottom: 3px double var(--ink);
}
/* links in running text are underlined, as in print */
[data-skin="ledger"] .view a:not([class]) { text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
/* the band: a ruled line over a yellow ground, the count written as a figure
   rather than set in a tile, the nouns as underlined links */
[data-skin="ledger"] .band { border: 0; border-top: 1px solid var(--attention-line); border-radius: 0; }
[data-skin="ledger"] .band__tape { display: none; }
[data-skin="ledger"] .band__count {
  min-width: 0; height: auto; padding: 0 2px 0 0; border-radius: 0; background: none; color: var(--count-ink);
  font-size: var(--fs-23); font-weight: 600; font-variant-numeric: tabular-nums lining-nums;
}
[data-skin="ledger"] .band--clear .band__count { color: var(--good-text); }
[data-skin="ledger"] .band__title {
  font-family: var(--font-label); font-stretch: var(--label-stretch); font-size: var(--fs-7);
  font-weight: 700; letter-spacing: var(--label-spacing); text-transform: uppercase;
}
[data-skin="ledger"] .band__chip {
  height: auto; padding: 0 2px; border: 0; border-radius: 0; background: none; color: inherit;
  font-weight: 600; text-decoration: underline; text-underline-offset: 3px;
}
[data-skin="ledger"] a.band__chip:hover { text-decoration: underline; text-decoration-thickness: 2px; }
/* the job tag between two rules, like a statement's reference line */
[data-skin="ledger"] .plate { height: 24px; padding: 0 2px; border-width: 1px 0; border-radius: 0; }
[data-skin="ledger"] .plate__cell + .plate__cell { border-left: 0; padding-left: 6px; }

/* ── Fieldstone: slate, limewash plaster and one copper detail ───────────── */
:root[data-skin="fieldstone"] {
  --font: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif;
  --font-display: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif;
  --font-num: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif;
  --display-stretch: 87.5%;
  --font-label: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif;
  --label-stretch: 75%; --label-weight: 650; --label-spacing: .1em;
  --r-sm: 6px; --r-md: 6px; --r-lg: 8px; --r-xl: 8px; --r-pill: 999px;
  --plane: #E1E4DD; --surface: #FBFAF6; --surface-2: #F2F2EC; --surface-3: #E8E9E1;
  --hairline: #DCDDD4; --hairline-strong: #C3C6BC; --control-line: #7C8078;
  --ink: #1B201D; --ink-2: #454C47; --ink-muted: #5B625C;
  --sidebar: #1F2B27; --sidebar-2: #17211E;
  --sidebar-ink: #CBD5CF; --sidebar-ink-hover: #FFFFFF; --sidebar-muted: #93A39A;
  --sidebar-hover: rgba(255, 255, 255, .06); --sidebar-active-bg: #F1EEE6; --sidebar-active-ink: #1B201D;
  --sidebar-line: rgba(255, 255, 255, .08);
  --accent: #B8652F; --link: #255E6B;
  --primary: #2B4A40; --primary-hover: #345A4E; --primary-ink: #F7F5EF; --primary-line: #2B4A40;
  --btn-shadow: inset 0 1px 0 rgba(255,255,255,.14), 0 1px 2px rgba(31,43,39,.20);
  --lamp: #B8652F; --lamp-ring: #8A4A22;
  --tab-ink: var(--ink); --tab-line: #B8652F;
  --card-line: #DCDDD4;
  --card-shadow: inset 0 1px 0 rgba(255,255,255,.75), 0 1px 2px rgba(31,43,39,.06), 0 6px 16px -4px rgba(31,43,39,.08);
  --card-shadow-hover: inset 0 1px 0 rgba(255,255,255,.75), 0 2px 4px rgba(31,43,39,.08), 0 10px 22px -6px rgba(31,43,39,.14);
  --topbar-bg: #E1E4DD; --topbar-ink: #1B201D; --topbar-muted: #5B625C; --topbar-line: #C9CBC1;
  --attention-bg: #1F2B27; --attention-line: #1F2B27; --attention-ink: #F5F2EA;
  --count-bg: repeating-linear-gradient(90deg, #A4552A 0 10px, #9A4F26 10px 11px); --count-ink: #FFFFFF;
  --chip-bg: #1F2B27; --chip-ink: #F5F2EA;
  --plate-bg: #E9E4DA; --plate-line: #A39C8E; --plate-ink: #2A2C28;
  --series-1: #0698A4; --series-2: #AB663E; --series-3: #685698;
}
@media (prefers-color-scheme: dark) {
  :root[data-skin="fieldstone"]:where(:not([data-theme="light"])) {
    --plane: #0E1311; --surface: #1A201E; --surface-2: #232A28; --surface-3: #2C3431;
    --hairline: #333C38; --hairline-strong: #46504B; --control-line: #69756F;
    --ink: #EEF0EA; --ink-2: #BCC4BE; --ink-muted: #95A098;
    --sidebar: #1C2723; --sidebar-ink: #C9D3CD; --sidebar-muted: #8E9D95;
    --sidebar-active-bg: #33423C; --sidebar-active-ink: #F4F2EC;
    --accent: #D98A55; --link: #7FC0CC;
    --primary: #E8E3D8; --primary-hover: #F2EEE5; --primary-ink: #151A18; --primary-line: #E8E3D8;
    --btn-shadow: inset 0 1px 0 rgba(255,255,255,.55), 0 1px 2px rgba(0,0,0,.50);
    --lamp: #D98A55; --lamp-ring: #9A5A30; --tab-line: #D98A55;
    --head-bg: var(--surface-2); --card-line: #333C38;
    --card-shadow: inset 0 1px 0 rgba(255,255,255,.04), 0 1px 2px rgba(0,0,0,.40), 0 8px 20px -6px rgba(0,0,0,.50);
    --card-shadow-hover: inset 0 1px 0 rgba(255,255,255,.05), 0 2px 4px rgba(0,0,0,.45), 0 12px 26px -6px rgba(0,0,0,.55);
    --topbar-bg: #0E1311; --topbar-ink: #EEF0EA; --topbar-muted: #95A098; --topbar-line: #2C3431;
    --attention-bg: #2E2219; --attention-line: #D98A55; --attention-ink: #F5E9DE;
    --count-bg: repeating-linear-gradient(90deg, #D98A55 0 10px, #C97C49 10px 11px); --count-ink: #1A120C;
    --chip-bg: #EEF0EA; --chip-ink: #0E1311;
    --plate-bg: #262E2A; --plate-line: #3A4440; --plate-ink: #E6E2D8;
    --series-1: #199EAA; --series-2: #B26C44; --series-3: #7A63B5;
  }
}
:root[data-skin="fieldstone"][data-theme="dark"] {
  --plane: #0E1311; --surface: #1A201E; --surface-2: #232A28; --surface-3: #2C3431;
  --hairline: #333C38; --hairline-strong: #46504B; --control-line: #69756F;
  --ink: #EEF0EA; --ink-2: #BCC4BE; --ink-muted: #95A098;
  --sidebar: #1C2723; --sidebar-ink: #C9D3CD; --sidebar-muted: #8E9D95;
  --sidebar-active-bg: #33423C; --sidebar-active-ink: #F4F2EC;
  --accent: #D98A55; --link: #7FC0CC;
  --primary: #E8E3D8; --primary-hover: #F2EEE5; --primary-ink: #151A18; --primary-line: #E8E3D8;
  --btn-shadow: inset 0 1px 0 rgba(255,255,255,.55), 0 1px 2px rgba(0,0,0,.50);
  --lamp: #D98A55; --lamp-ring: #9A5A30; --tab-line: #D98A55;
  --head-bg: var(--surface-2); --card-line: #333C38;
  --card-shadow: inset 0 1px 0 rgba(255,255,255,.04), 0 1px 2px rgba(0,0,0,.40), 0 8px 20px -6px rgba(0,0,0,.50);
  --card-shadow-hover: inset 0 1px 0 rgba(255,255,255,.05), 0 2px 4px rgba(0,0,0,.45), 0 12px 26px -6px rgba(0,0,0,.55);
  --topbar-bg: #0E1311; --topbar-ink: #EEF0EA; --topbar-muted: #95A098; --topbar-line: #2C3431;
  --attention-bg: #2E2219; --attention-line: #D98A55; --attention-ink: #F5E9DE;
  --count-bg: repeating-linear-gradient(90deg, #D98A55 0 10px, #C97C49 10px 11px); --count-ink: #1A120C;
  --chip-bg: #EEF0EA; --chip-ink: #0E1311;
  --plate-bg: #262E2A; --plate-line: #3A4440; --plate-ink: #E6E2D8;
  --series-1: #199EAA; --series-2: #B26C44; --series-3: #7A63B5;
}
[data-skin="fieldstone"] :is(.view__title, h1) { font-weight: 600; }
/* the sidebar's headings stay sentence case here, in the theme's own type */
[data-skin="fieldstone"] :is(.nav__label, .nav__label--toggle) {
  text-transform: none; letter-spacing: 0; font-stretch: 100%; font-size: var(--fs-6); font-weight: 600;
}
/* the copper rivet sits inside the plaster tile, not in the gutter */
[data-skin="fieldstone"] .nav__item { padding-left: 26px; }
[data-skin="fieldstone"] .nav__item.is-active .nav__lamp {
  left: 10px; width: 7px; height: 7px; margin-top: -3.5px; background: var(--lamp);
  box-shadow: 0 0 0 1px var(--lamp-ring), inset 0 1px 0 rgba(255,255,255,.45);
}
/* a page header is a drawing's title block: the title on a rule */
[data-skin="fieldstone"] .view__head { padding-bottom: var(--sp-4); border-bottom: 1px solid var(--topbar-line); }
/* the band: slate, with a copper standing-seam count */
[data-skin="fieldstone"] .band__tape { display: none; }
[data-skin="fieldstone"] .band__chip { background: transparent; color: var(--attention-ink);
  border-color: color-mix(in srgb, var(--attention-ink) 35%, transparent); }
/* the stone tag with copper rivets, in the bar */
[data-skin="fieldstone"] .crumbs { display: none; }
[data-skin="fieldstone"] .topbar__plate {
  display: inline-flex; align-items: center; gap: 8px; height: 28px; padding: 0 10px; flex: none;
  border: 1px solid var(--plate-line); border-radius: 6px; background: var(--plate-bg); color: var(--plate-ink);
  font-family: var(--font-label); font-stretch: 75%; font-weight: 650; letter-spacing: .1em;
  text-transform: uppercase; font-size: var(--fs-6); white-space: nowrap;
}
[data-skin="fieldstone"] .topbar__plate .plate__cell + .plate__cell { padding-left: 8px; border-left: 1px solid rgba(128,120,104,.45); }
[data-skin="fieldstone"] .plate__fix {
  display: block; width: 6px; height: 6px; border-radius: 50%; flex: none; background: var(--lamp);
  box-shadow: 0 0 0 1px var(--lamp-ring), inset 0 1px 0 rgba(255,255,255,.5);
}
[data-skin="fieldstone"] .view__head .plate { display: none; }
[data-skin="fieldstone"] .date-chip { border-radius: 999px; padding: 0 11px; }

/* ── Blueprint: the business as a drawing set ───────────────────────────
   Ink line-work on white paper by day, a true cyanotype by night. Every page
   is a numbered sheet, a project has a title block, and the phone's yellow
   "you are here" becomes the estimator's highlighter. */
:root[data-skin="blueprint"] {
  --argus-ink: var(--argus-navy);               /* a light sidebar */
  --font: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif;
  --font-display: 'IBM Plex Sans Condensed', 'IBM Plex Sans', sans-serif;
  --font-num: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, Consolas, monospace;
  --font-label: 'IBM Plex Sans Condensed', 'IBM Plex Sans', sans-serif;
  --label-stretch: 100%; --label-weight: 600; --label-spacing: .06em;
  --r-sm: 0px; --r-md: 0px; --r-lg: 0px; --r-xl: 0px; --r-pill: 0px;
  --plane: #F1F4F7; --surface: #FFFFFF; --surface-2: #EEF2F6; --surface-3: #E3E9F0;
  --hairline: #D3DAE2; --hairline-strong: #66768A; --control-line: #66768A;
  --ink: #0E1A26; --ink-2: #364454; --ink-muted: #536170;
  --sidebar: #FFFFFF; --sidebar-2: #F1F4F7;
  --sidebar-ink: #0E1A26; --sidebar-ink-hover: #0E1A26; --sidebar-muted: #536170;
  --sidebar-hover: #EEF2F6; --sidebar-active-bg: transparent; --sidebar-active-ink: #0E1A26;
  --sidebar-line: #D3DAE2; --logo-ink: #0E1A26;
  --accent: #1F64B5; --link: #1D4E89;
  --primary: #0E1A26; --primary-hover: #22313F; --primary-ink: #FFFFFF; --primary-line: #0E1A26;
  --lamp: #FFD60A; --lamp-ring: #0E1A26;
  --tab-ink: var(--ink); --tab-line: #0E1A26;
  --card-line: #66768A; --card-shadow: none; --card-shadow-hover: none;
  --plane-texture:
    linear-gradient(rgba(29,78,137,.05) 1px, transparent 1px) 0 0 / 24px 24px,
    linear-gradient(90deg, rgba(29,78,137,.05) 1px, transparent 1px) 0 0 / 24px 24px,
    linear-gradient(rgba(29,78,137,.09) 1px, transparent 1px) 0 0 / 120px 120px,
    linear-gradient(90deg, rgba(29,78,137,.09) 1px, transparent 1px) 0 0 / 120px 120px;
  --topbar-bg: #FFFFFF; --topbar-ink: #0E1A26; --topbar-muted: #536170; --topbar-line: #D3DAE2;
  --attention-bg: #FFFFFF; --attention-line: #0E1A26; --attention-ink: #0E1A26;
  --cloud: #0E1A26;
  --count-bg: #0E1A26; --count-ink: #FFD60A;
  --chip-bg: #0E1A26; --chip-ink: #FFFFFF;
  --plate-bg: #FFFFFF; --plate-line: #0E1A26; --plate-ink: #0E1A26;
  --series-1: #2F6DB0; --series-2: #0E1A26; --series-3: #8A96A3;
}
@media (prefers-color-scheme: dark) {
  :root[data-skin="blueprint"]:where(:not([data-theme="light"])) {
    --argus-ink: var(--argus-white);
    --plane: #08182C; --surface: #0E2846; --surface-2: #143356; --surface-3: #1B3D64;
    --hairline: #2A4E78; --hairline-strong: #7894B6; --control-line: #7894B6;
    --ink: #EEF4FA; --ink-2: #C2D2E4; --ink-muted: #9DB3CC;
    --sidebar: #061528; --sidebar-2: #04101F;
    --sidebar-ink: #DCE7F3; --sidebar-ink-hover: #FFFFFF; --sidebar-muted: #93AAC4;
    --sidebar-hover: rgba(255,255,255,.06); --sidebar-active-ink: #FFFFFF;
    --sidebar-line: #1F3A5C; --logo-ink: #FFFFFF;
    --accent: #FFD60A; --link: #8EC5FF;
    --primary: #EEF4FA; --primary-hover: #FFFFFF; --primary-ink: #0A2038; --primary-line: #EEF4FA;
    --lamp-ring: #0A2038; --tab-line: #FFD60A;
    --head-bg: var(--surface-2); --card-line: #7894B6;
    --plane-texture:
      linear-gradient(rgba(142,197,255,.06) 1px, transparent 1px) 0 0 / 24px 24px,
      linear-gradient(90deg, rgba(142,197,255,.06) 1px, transparent 1px) 0 0 / 24px 24px,
      linear-gradient(rgba(142,197,255,.11) 1px, transparent 1px) 0 0 / 120px 120px,
      linear-gradient(90deg, rgba(142,197,255,.11) 1px, transparent 1px) 0 0 / 120px 120px;
    --topbar-bg: #08182C; --topbar-ink: #EEF4FA; --topbar-muted: #9DB3CC; --topbar-line: #2A4E78;
    --attention-bg: #163A63; --attention-line: #FFD60A; --attention-ink: #FFFFFF; --cloud: #FFD60A;
    --count-bg: #FFD60A; --count-ink: #0A2038;
    --chip-bg: #FFD60A; --chip-ink: #0A2038;
    --plate-bg: #0E2846; --plate-line: #7894B6; --plate-ink: #EEF4FA;
    --good-bg: #0F3A2A; --warning-bg: #3A3216; --serious-bg: #3A2420; --critical-bg: #3A1C2A;
    --brand-100: #1B3E66; --info-text: #8EC5FF;
    --series-1: #4A8EE0; --series-2: #EEF4FA; --series-3: #6F8299;
  }
}
:root[data-skin="blueprint"][data-theme="dark"] {
  --argus-ink: var(--argus-white);
  --plane: #08182C; --surface: #0E2846; --surface-2: #143356; --surface-3: #1B3D64;
  --hairline: #2A4E78; --hairline-strong: #7894B6; --control-line: #7894B6;
  --ink: #EEF4FA; --ink-2: #C2D2E4; --ink-muted: #9DB3CC;
  --sidebar: #061528; --sidebar-2: #04101F;
  --sidebar-ink: #DCE7F3; --sidebar-ink-hover: #FFFFFF; --sidebar-muted: #93AAC4;
  --sidebar-hover: rgba(255,255,255,.06); --sidebar-active-ink: #FFFFFF;
  --sidebar-line: #1F3A5C; --logo-ink: #FFFFFF;
  --accent: #FFD60A; --link: #8EC5FF;
  --primary: #EEF4FA; --primary-hover: #FFFFFF; --primary-ink: #0A2038; --primary-line: #EEF4FA;
  --lamp-ring: #0A2038; --tab-line: #FFD60A;
  --head-bg: var(--surface-2); --card-line: #7894B6;
  --plane-texture:
    linear-gradient(rgba(142,197,255,.06) 1px, transparent 1px) 0 0 / 24px 24px,
    linear-gradient(90deg, rgba(142,197,255,.06) 1px, transparent 1px) 0 0 / 24px 24px,
    linear-gradient(rgba(142,197,255,.11) 1px, transparent 1px) 0 0 / 120px 120px,
    linear-gradient(90deg, rgba(142,197,255,.11) 1px, transparent 1px) 0 0 / 120px 120px;
  --topbar-bg: #08182C; --topbar-ink: #EEF4FA; --topbar-muted: #9DB3CC; --topbar-line: #2A4E78;
  --attention-bg: #163A63; --attention-line: #FFD60A; --attention-ink: #FFFFFF; --cloud: #FFD60A;
  --count-bg: #FFD60A; --count-ink: #0A2038;
  --chip-bg: #FFD60A; --chip-ink: #0A2038;
  --plate-bg: #0E2846; --plate-line: #7894B6; --plate-ink: #EEF4FA;
  --good-bg: #0F3A2A; --warning-bg: #3A3216; --serious-bg: #3A2420; --critical-bg: #3A1C2A;
  --brand-100: #1B3E66; --info-text: #8EC5FF;
  --series-1: #4A8EE0; --series-2: #EEF4FA; --series-3: #6F8299;
}
/* the sidebar's right edge is a drawing border: a heavy rule and a thin one */
[data-skin="blueprint"] .sidebar {
  border-right: 0;
  box-shadow: inset -2px 0 0 var(--sidebar-ink), inset -5px 0 0 var(--sidebar), inset -6px 0 0 var(--sidebar-ink);
}
/* the sheet index: every page is numbered, the current sheet's number inverted */
[data-skin="blueprint"] .nav { padding-left: var(--sp-3); padding-right: var(--sp-3); }
[data-skin="blueprint"] .nav__item { gap: 8px; padding-left: 4px; padding-right: 6px; font-size: var(--fs-8); }
[data-skin="blueprint"] .nav__sheet {
  display: grid; place-items: center; flex: none; width: 40px; height: 22px;
  font-family: var(--mono); font-size: var(--fs-4); font-weight: 600;
  color: var(--sidebar-muted); border: 1px solid var(--sidebar-line);
}
[data-skin="blueprint"] .nav__item.is-active .nav__sheet { background: var(--sidebar-ink); color: #FFD60A; border-color: var(--sidebar-ink); }
[data-skin="blueprint"][data-theme="dark"] .nav__item.is-active .nav__sheet { background: #FFD60A; color: #0A2038; border-color: #FFD60A; }
@media (prefers-color-scheme: dark) {
  :root[data-skin="blueprint"]:where(:not([data-theme="light"])) .nav__item.is-active .nav__sheet { background: #FFD60A; color: #0A2038; border-color: #FFD60A; }
}
/* the highlighter is the light: no fill, no lamp */
[data-skin="blueprint"] .nav__item.is-active .nav__lamp { display: none; }
[data-skin="blueprint"] .nav__item.is-active > span:not([class]) {
  flex: 0 1 auto; margin-right: auto; padding: 0 5px; margin-left: -5px; color: #0E1A26;
  background: linear-gradient(transparent 12%, #FFD60A 12% 88%, transparent 88%);
}
/* sheet titles: condensed capitals over a heavy rule and a thin one */
[data-skin="blueprint"] .view__title { text-transform: uppercase; letter-spacing: .04em; }
[data-skin="blueprint"] .card__title { text-transform: uppercase; letter-spacing: .04em; }
[data-skin="blueprint"] .view__head {
  padding-bottom: var(--sp-3); margin-bottom: calc(var(--sp-5) + 6px);
  box-shadow: 0 2px 0 var(--ink), 0 5px 0 var(--plane), 0 6px 0 var(--ink);
}
/* the title block at the right of the bar */
[data-skin="blueprint"] .titleblock {
  display: inline-flex; align-items: stretch; height: 40px; flex: none;
  border: 2px solid var(--plate-line); background: var(--plate-bg); color: var(--plate-ink);
  font-family: var(--mono); text-transform: uppercase; font-size: var(--fs-4); font-weight: 600; letter-spacing: .04em;
}
[data-skin="blueprint"] .titleblock__cell { display: inline-flex; align-items: center; gap: 6px; padding: 0 10px; white-space: nowrap; }
[data-skin="blueprint"] .titleblock__cell + .titleblock__cell { border-left: 1px solid var(--plate-line); }
[data-skin="blueprint"] .titleblock__cell--big { font-size: var(--fs-12); }
/* the band: a revision cloud round it, the count in a delta */
[data-skin="blueprint"] .band {
  border: 0; padding: 6px 8px;
  background:
    radial-gradient(circle at 50% 100%, transparent 6px, var(--cloud) 6px 7.5px, transparent 8px) 0 0 / 16px 9px repeat-x,
    radial-gradient(circle at 50% 0, transparent 6px, var(--cloud) 6px 7.5px, transparent 8px) 0 100% / 16px 9px repeat-x,
    radial-gradient(circle at 100% 50%, transparent 6px, var(--cloud) 6px 7.5px, transparent 8px) 0 0 / 9px 16px repeat-y,
    radial-gradient(circle at 0 50%, transparent 6px, var(--cloud) 6px 7.5px, transparent 8px) 100% 0 / 9px 16px repeat-y,
    var(--attention-bg);
}
[data-skin="blueprint"] .band__tape { display: none; }
[data-skin="blueprint"] .band__count {
  width: 44px; height: 38px; padding: 0 0 3px; place-items: end center; border-radius: 0;
  clip-path: polygon(50% 0, 100% 100%, 0 100%); font-family: var(--mono); font-size: var(--fs-12); font-weight: 600;
}
[data-skin="blueprint"] .band__title {
  padding: 0 6px; color: #0E1A26; text-transform: uppercase; letter-spacing: .04em;
  background: linear-gradient(transparent 12%, #FFD60A 12% 88%, transparent 88%);
}
[data-skin="blueprint"] .band__chip { font-family: var(--mono); border-color: var(--attention-line); }
[data-skin="blueprint"] :is(.tbl__lead, .plate) { font-family: var(--mono); }
[data-skin="blueprint"] .plate { border-width: 1px; padding: 0; gap: 0; }
[data-skin="blueprint"] .plate .plate__cell { padding: 0 10px; height: 100%; }
[data-skin="blueprint"] .plate .plate__cell + .plate__cell { padding-left: 10px; }

/* ── Obsidian: dark-first graphite with one iris light ───────────────────── */
:root[data-skin="obsidian"] {
  --font: 'Geist', ui-sans-serif, system-ui, sans-serif;
  --font-display: 'Sora', 'Geist', ui-sans-serif, sans-serif;
  --font-num: 'Geist', ui-sans-serif, system-ui, sans-serif;
  --mono: 'Geist Mono', ui-monospace, Consolas, monospace;
  --font-label: 'Geist Mono', ui-monospace, monospace;
  --label-stretch: 100%; --label-weight: 500; --label-spacing: .08em;
  --r-sm: 6px; --r-md: 6px; --r-lg: 10px; --r-xl: 10px; --r-pill: 999px;
  --plane: #E8E9EC; --surface: #FAFAFB; --surface-2: #F1F2F4; --surface-3: #E7E8EB;
  --hairline: #DADCE0; --hairline-strong: #C2C5CB; --control-line: #7C8088;
  --ink: #131418; --ink-2: #42454C; --ink-muted: #60646C;
  --sidebar: #1D1E22; --sidebar-2: #141518;
  --sidebar-ink: #D3D5DA; --sidebar-ink-hover: #FFFFFF; --sidebar-muted: #9195A0;
  --sidebar-hover: rgba(255,255,255,.06); --sidebar-active-bg: #2D2E34; --sidebar-active-ink: #FFFFFF;
  --sidebar-line: rgba(255,255,255,.08);
  --accent: #5148C9; --link: #4A41BD;
  --primary: #5148C9; --primary-hover: #463EB3; --primary-ink: #FFFFFF; --primary-line: #5148C9;
  --btn-shadow: inset 0 1px 0 rgba(255,255,255,.16), 0 1px 2px rgba(0,0,0,.25);
  --lamp: #A9A2FA; --lamp-ring: rgba(169,162,250,.16);
  --tab-ink: var(--ink); --tab-line: #4A41BD;
  --card-line: #DADCE0;
  --card-shadow: inset 0 1px 0 #FFFFFF, 0 1px 2px rgba(19,20,24,.05), 0 8px 24px rgba(19,20,24,.05);
  --card-shadow-hover: inset 0 1px 0 #FFFFFF, 0 2px 4px rgba(19,20,24,.07), 0 12px 30px rgba(19,20,24,.09);
  --topbar-bg: #E8E9EC; --topbar-ink: #131418; --topbar-muted: #60646C; --topbar-line: #DADCE0;
  --attention-bg: #FAF2DD; --attention-line: #E7CF8E; --attention-ink: #131418;
  --count-bg: #131418; --count-ink: #F5C563; --tape-ink: #0A0B0D; --tape-fill: #FAB219;
  --chip-bg: #1D1E22; --chip-ink: #ECEDF0;
  --plate-bg: #1D1E22; --plate-line: #2A2C32; --plate-ink: #ECEDF0;
  --series-1: #2F63C6; --series-2: #AE5320; --series-3: #888C94;
}
@media (prefers-color-scheme: dark) {
  :root[data-skin="obsidian"]:where(:not([data-theme="light"])) {
    --plane: #08090B; --surface: #141519; --surface-2: #1C1D22; --surface-3: #25262C;
    --hairline: #2A2C32; --hairline-strong: #3A3C44; --control-line: #6B6F78;
    --ink: #ECEDF0; --ink-2: #B3B6BD; --ink-muted: #8D9199;
    --sidebar: #050607; --sidebar-ink: #C8CAD0; --sidebar-muted: #7F838C;
    --sidebar-active-bg: #1C1D23; --sidebar-active-ink: #F5F5F7; --sidebar-line: #1C1D21;
    --accent: #A9A2FA; --link: #B6B0FF;
    --primary: #A9A2FA; --primary-hover: #BDB7FF; --primary-ink: #0A0B0D; --primary-line: #A9A2FA;
    --tab-line: #B6B0FF; --head-bg: var(--surface-2); --card-line: #2A2C32;
    --card-shadow: inset 0 1px 0 rgba(255,255,255,.05), 0 1px 2px rgba(0,0,0,.55), 0 16px 40px rgba(0,0,0,.40);
    --card-shadow-hover: inset 0 1px 0 rgba(255,255,255,.07), 0 2px 4px rgba(0,0,0,.6), 0 18px 44px rgba(0,0,0,.45);
    --topbar-bg: #08090B; --topbar-ink: #ECEDF0; --topbar-muted: #8D9199; --topbar-line: #25272C;
    --attention-bg: #1B170D; --attention-line: #3A3019; --attention-ink: #F2EAD6;
    --count-bg: #08090B; --chip-bg: #202127;
    --plate-bg: #050607;
    --series-1: #7CA3F0; --series-2: #E0875A; --series-3: #737882;
  }
}
:root[data-skin="obsidian"][data-theme="dark"] {
  --plane: #08090B; --surface: #141519; --surface-2: #1C1D22; --surface-3: #25262C;
  --hairline: #2A2C32; --hairline-strong: #3A3C44; --control-line: #6B6F78;
  --ink: #ECEDF0; --ink-2: #B3B6BD; --ink-muted: #8D9199;
  --sidebar: #050607; --sidebar-ink: #C8CAD0; --sidebar-muted: #7F838C;
  --sidebar-active-bg: #1C1D23; --sidebar-active-ink: #F5F5F7; --sidebar-line: #1C1D21;
  --accent: #A9A2FA; --link: #B6B0FF;
  --primary: #A9A2FA; --primary-hover: #BDB7FF; --primary-ink: #0A0B0D; --primary-line: #A9A2FA;
  --tab-line: #B6B0FF; --head-bg: var(--surface-2); --card-line: #2A2C32;
  --card-shadow: inset 0 1px 0 rgba(255,255,255,.05), 0 1px 2px rgba(0,0,0,.55), 0 16px 40px rgba(0,0,0,.40);
  --card-shadow-hover: inset 0 1px 0 rgba(255,255,255,.07), 0 2px 4px rgba(0,0,0,.6), 0 18px 44px rgba(0,0,0,.45);
  --topbar-bg: #08090B; --topbar-ink: #ECEDF0; --topbar-muted: #8D9199; --topbar-line: #25272C;
  --attention-bg: #1B170D; --attention-line: #3A3019; --attention-ink: #F2EAD6;
  --count-bg: #08090B; --chip-bg: #202127;
  --plate-bg: #050607;
  --series-1: #7CA3F0; --series-2: #E0875A; --series-3: #737882;
}
/* the selected item: smoked graphite with a polished top edge, and the iris
   light - a quiet halo, not neon */
[data-skin="obsidian"] .nav__item.is-active { box-shadow: inset 0 1px 0 rgba(255,255,255,.06); }
[data-skin="obsidian"] .nav__item.is-active .nav__lamp {
  width: 6px; height: 6px; margin-top: -3px; left: -9px; background: var(--lamp);
  box-shadow: 0 0 0 3px rgba(169,162,250,.16), 0 0 8px rgba(169,162,250,.35);
}
/* figures in Sora Light; in the dark a faint glow, never on a status colour */
[data-skin="obsidian"] .kpi__value { font-weight: 300; }
[data-skin="obsidian"][data-theme="dark"] .kpi__value:not([class*="tone"]) { text-shadow: 0 0 22px rgba(236,237,240,.10); }
/* a row of supporting figures is one ruled strip, not a row of boxes - on a
   project under its headline row, and the Summary's own tiles (which lost
   their headline row in Drop 2's rebuild, so the strip had stopped matching) */
[data-skin="obsidian"] :is(.kpi-row--lead + .kpi-row, .kpi-row.summary-tiles) {
  gap: 0; background: var(--surface); border: 1px solid var(--card-line); border-radius: var(--r-lg);
  box-shadow: var(--card-shadow); overflow: hidden;
}
[data-skin="obsidian"] :is(.kpi-row--lead + .kpi-row, .kpi-row.summary-tiles) > .kpi {
  border: 0; border-radius: 0; background: transparent; box-shadow: -1px 0 0 var(--hairline), 0 -1px 0 var(--hairline);
}
/* the band: smoked amber, its count in an obsidian tile, the fine tape along
   its foot - the only caution yellow in the theme */
[data-skin="obsidian"] .band__tape { display: none; }
[data-skin="obsidian"] .band__tape--foot { display: block; height: 3px;
  background: repeating-linear-gradient(-45deg, var(--tape-ink) 0 4px, var(--tape-fill) 4px 8px); }
[data-skin="obsidian"] .band__count {
  width: 44px; height: 44px; border: 1px solid rgba(245,197,99,.45); font-weight: 600;
}
[data-skin="obsidian"] .band__title { font-family: var(--font); font-weight: 600; }
[data-skin="obsidian"] .date-chip { box-shadow: inset 0 1px 0 rgba(255,255,255,.08); letter-spacing: .1em; }
[data-skin="obsidian"] .plate { box-shadow: inset 0 1px 0 rgba(255,255,255,.06); }
[data-skin="obsidian"] .plate .plate__cell + .plate__cell { border-left-color: #3A3C44; }

/* A ruled strip clips to its own edge (Ledger, Obsidian), but a tile's
   "what this is made of" panel opens below the tile and must not be cut off -
   the base rule for this ties with the strips' and loses on order, so it is
   said again here, after them. */
[data-skin] .kpi-row:has(.kpi--top:hover), [data-skin] .kpi-row:has(.kpi--top:focus-within) { overflow: visible; }

/* ═══════════════════════════════════════════════════════════════════════════
   The test site.

   <html data-env="test"> is written into the test build's index.html, so it
   is there before anything paints, and app.js sets it again. Everything in
   this section hangs off that attribute: none of it can reach the live site,
   and no rule above had to change to make room for it.

   The test site has to be impossible to take for live, at a glance, on any
   screen:
     - a striped banner across the top of every page that nothing covers -
       dialogs, the phone drawer and its scrim all open below it
     - a purple sidebar and login panel on a lavender ground, where live is
       navy on grey
     - a fuchsia frame round the window, over everything
     - app.js adds a badge under the logo, "TEST - " on the tab, a "T"
       favicon, and the banner's colour in a phone's status bar
   Purple, not amber: amber and orange already MEAN something in this app
   ("look at this", "serious"), and a test site painted in them would blur
   the very warnings being tested on it. Only the chrome changes. Cards,
   figures, buttons, status colours and charts stay exactly as live has them,
   so the test site still shows what live will look like.
   ═══════════════════════════════════════════════════════════════════════════ */
:root[data-env="test"] {
  --argus-ink: var(--argus-white);             /* the test sidebar is purple in every theme */
  --env-bar-h: 34px;
  /* On a phone the banner also takes the notch, so everything below it
     moves by its whole height. */
  --env-offset: calc(var(--env-bar-h) + env(safe-area-inset-top, 0px));

  --env-accent:   #C026D3;   /* the frame, the badges, the favicon */
  --env-stripe-a: #86198F;   /* the banner, and a phone's status bar */
  --env-stripe-b: #701A75;
  --env-ink:      #FFFFFF;
  --env-glow:     rgba(192, 38, 211, .30);

  --sidebar:   #2E1065;
  --sidebar-2: #1E0B4B;
  /* #F0EBF9, not #EAE1F6: grey captions read 4.33:1 on the old lavender
     (2026-10-08, the design panel); 4.68:1 here and still plainly lavender. */
  --plane:     #F0EBF9;
  /* every theme's sidebar words, readable on the purple - Blueprint's are ink
     on white otherwise */
  --sidebar-ink: #E4DCF5; --sidebar-ink-hover: #FFFFFF; --sidebar-muted: #B8A6DA;
  --sidebar-hover: rgba(255, 255, 255, .08); --sidebar-line: rgba(255, 255, 255, .12); --logo-ink: #FFFFFF;
}
/* Dark, under both scopes like every themed value (see the top of the file).
   These come AFTER the block above on purpose: the media-query one has the
   same specificity, so it is the order that lets it win. */
@media (prefers-color-scheme: dark) {
  :root[data-env="test"]:where(:not([data-theme="light"])) {
    --sidebar: #1B0A38; --sidebar-2: #12062A; --plane: #0F0A1A;
    --sidebar-ink: #E4DCF5; --sidebar-ink-hover: #FFFFFF; --sidebar-muted: #B8A6DA;
    --sidebar-hover: rgba(255, 255, 255, .08); --sidebar-line: rgba(255, 255, 255, .12); --logo-ink: #FFFFFF;
  }
}
:root[data-env="test"][data-theme="dark"] {
  --sidebar: #1B0A38; --sidebar-2: #12062A; --plane: #0F0A1A;
  --sidebar-ink: #E4DCF5; --sidebar-ink-hover: #FFFFFF; --sidebar-muted: #B8A6DA;
  --sidebar-hover: rgba(255, 255, 255, .08); --sidebar-line: rgba(255, 255, 255, .12); --logo-ink: #FFFFFF;
}

/* The banner is fixed, so everything that is pinned to the top of the window,
   or sized to its height, moves down by it. */
:root[data-env="test"] body { padding-top: var(--env-offset); }
:root[data-env="test"] .app,
:root[data-env="test"] .login { min-height: calc(100 * var(--vh) - var(--env-offset)); }
:root[data-env="test"] .sidebar {
  top: var(--env-offset); height: calc(100 * var(--vh) - var(--env-offset));
  background-image: radial-gradient(ellipse 150% 42% at 50% 0%, var(--env-glow), transparent 70%);
}
:root[data-env="test"] .topbar { top: var(--env-offset); }
:root[data-env="test"] .nav__item.is-active .nav__lamp { --lamp: var(--env-accent); --lamp-ring: #FFFFFF; }
:root[data-env="test"] .login__brand { background: var(--sidebar); }
/* Dialogs open below the banner rather than over it. On a phone a dialog is
   the whole screen, and a sheet with no banner looks exactly like live. */
:root[data-env="test"] .modal-backdrop { top: var(--env-offset); }
:root[data-env="test"] .modal { max-height: calc(90 * var(--vh) - var(--env-offset)); }
@media (min-width: 721px) {
  :root[data-env="test"] .table-wrap {
    max-height: calc(100 * var(--vh) - var(--topbar-h) - 40px - var(--env-offset));
  }
}
@media (max-width: 1080px) {
  /* The drawer and its scrim open below the banner too. The banner has
     already taken the notch, so the drawer does not pad for it again. */
  :root[data-env="test"] .sidebar { padding-top: 0; }
  :root[data-env="test"] .scrim { top: var(--env-offset); }
}
@media (max-width: 720px) {
  :root[data-env="test"] { --env-bar-h: 30px; }
  /* likewise the topbar: the banner above it already cleared the notch */
  :root[data-env="test"] .topbar { height: var(--topbar-h); padding-top: 0; }
  :root[data-env="test"] .modal,
  :root[data-env="test"] .modal--wide,
  :root[data-env="test"] .modal--xwide {
    max-height: calc(100 * var(--dvh) - var(--env-offset));
    height: calc(100 * var(--dvh) - var(--env-offset));
  }
  .env-bar__more { display: none; }
}

.env-bar {
  position: fixed; top: 0; left: 0; right: 0; z-index: var(--z-env-bar);
  height: var(--env-offset); padding: env(safe-area-inset-top, 0px) var(--sp-4) 0;
  display: flex; align-items: center; justify-content: center; gap: var(--sp-3);
  background: repeating-linear-gradient(-45deg,
    var(--env-stripe-a) 0 16px, var(--env-stripe-b) 16px 32px);
  color: var(--env-ink); font-size: var(--fs-7); font-weight: 600; line-height: 1;
  white-space: nowrap; overflow: hidden;
}
.env-bar__tag {
  flex: none; background: var(--env-ink); color: var(--env-stripe-a);
  font-size: var(--fs-5); font-weight: 800; letter-spacing: .14em; text-transform: uppercase;
  padding: 4px 9px; border-radius: var(--r-pill);
}
.env-bar__text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* over everything, dialogs included, and click-through */
.env-frame {
  position: fixed; inset: 0; z-index: var(--z-env-frame); pointer-events: none;
  border: 3px solid var(--env-accent);
}
.env-badge {
  display: inline-block; margin-top: var(--sp-3);
  background: var(--env-accent); color: var(--env-ink);
  font-size: var(--fs-3); font-weight: 800; letter-spacing: .14em; text-transform: uppercase;
  padding: 3px 8px; border-radius: var(--r-pill);
}
.env-badge--form { margin: 0 0 var(--sp-3); }
.env-login { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-3); }
.env-login .env-badge { margin: 0; font-size: var(--fs-8); padding: 5px 11px; }

@media print {
  /* a printed test page still says so, at the top of the first sheet */
  :root[data-env="test"] body { padding-top: 0; }
  .env-bar {
    position: static; height: auto; padding: 6px 0;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }
  .env-frame { display: none; }
}

@media print {
  .sidebar, .topbar, .view__actions, .card__tools { display: none !important; }
  .app { grid-template-columns: 1fr; }
  .card { break-inside: avoid; box-shadow: none; }
  body { background: #fff; }
  /* the walk-through checklist printed (2026-10-22): the sheet alone, a part a page */
  body.is-printing-checklist > *:not(.cl-print) { display: none !important; }
  body.is-printing-checklist .cl-print { display: block; color: var(--ink); font-size: var(--fs-7); }
  .cl-print__part { break-after: page; }
  .cl-print__part:last-child { break-after: auto; }
  .cl-print__brand { font-size: var(--fs-18); font-weight: 800; margin: 0; }
  .cl-print__title { font-size: var(--fs-10); font-weight: 700; margin: 2px 0 var(--sp-3); text-transform: uppercase; }
  .cl-print__job { display: grid; grid-template-columns: 1fr 1fr; gap: 2px var(--sp-5); padding-bottom: var(--sp-2); border-bottom: 1px solid var(--ink); }
  .cl-print__section { break-inside: avoid; margin-top: var(--sp-3); }
  .cl-print__section h3 { font-size: var(--fs-8); font-weight: 700; text-transform: uppercase; background: var(--surface-3); padding: 2px 6px; margin: 0 0 2px; }
  .cl-print__row { display: grid; grid-template-columns: 14px minmax(0, 1.1fr) minmax(0, 1fr); gap: var(--sp-2); padding: 2px 6px; align-items: center; }
  .cl-print__box { width: 12px; height: 12px; border: 1px solid var(--ink); display: inline-flex; align-items: center; justify-content: center; }
  .cl-print__box svg { width: 10px; height: 10px; }
  .cl-print__answer { border-bottom: 1px solid var(--hairline-strong); min-height: 1.2em; }
}
/* The reduced-motion rule lives at the TOP of this file (see the motion
   section). There used to be a second copy here saying
   `animation: none !important`, and because it came later with the same
   selector and the same !important, it won.

   `animation: none` is not the same as a zero duration: it removes the
   animation entirely, so `animationend` never fires. closeModal() waits for
   that event to remove the dialog, which left anyone with "reduce motion"
   switched on looking at a FULLY OPAQUE dialog for the whole 500ms fallback —
   one that was already `pointer-events: none`, so clicking it did nothing
   visible and the click landed on the page behind. Reduced motion is on by
   default for a lot of people; this was not an edge case.

   The rule at the top uses `animation-duration: .001ms`, which is invisible
   AND still dispatches animationend. Nothing belongs here. */

/* ═══════════════════════════════════════════════════════════════════════════
   The phone app (2026-10-06), in the Workshop look (2026-10-07).

   Sagar: "I don't want the mobile version to look just like the desktop
   app" - so a phone (phone-kit.js decides, once, by the device) gets its own
   shell, <html data-ui="phone">. On 10-07 he picked its look from nine
   directions on the design canvas: "Workshop" (D) - white splash tiles with
   ink outlines and a hard drop shadow, Home Depot's sign energy, Archivo -
   in G's caution yellow and ink on D's warm concrete, caution tape as the
   signature, C's "Good morning" header, and from I the lights over the tabs,
   the sunken photo slots and the screwed-on plate.

   ONE look on iPhone and Android (his answer, 10-07): each phone still
   behaves as itself - back by swiping, haptics, its own share sheet, Maps -
   but nothing here hangs off data-platform any more.

   LIGHT AND DARK (2026-10-14; light only from 10-07 until then). The dark is
   the desk's Workshop dark (his pick, 10-14): dark concrete, dark tiles,
   off-white words, warm grey outlines, black hard lifts. Caution yellow and
   the black-and-yellow tape stay exactly as they are. More › Appearance picks
   Automatic (the phone's own setting - the default), Light or Dark, kept on
   the phone (phone-kit.js LOOK_KEY). The dark values are the block after
   ":root[data-ui="phone"]" below, under both scopes like every dark here.

   A SKIN IS ITS TOKENS (his "build the next skin" idea, 10-07). Every rule
   below reads its colour, outline, lift, corners and type from the --ph-*
   values in the next block, so another skin - Executive, Obsidian, Field
   Device - is a second set of those values, not a second set of rules.

   Colour carries one meaning, as everywhere in this app: red is late, past
   due or refused; green is OK / active; the caution yellow is the brand and
   "press this / you are here". A thing that needs someone's attention is
   marked with caution TAPE, never with a second amber.
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
  /* ground and surfaces */
  /* the phone's status bar: the page's ground, light and dark (phone.js reads
     both, so neither is redefined in the dark block) */
  --ph-status-light: #ECE9E2;
  --ph-status-dark: #161513;
  --ph-bg: #ECE9E2;                /* warm concrete */
  --ph-card: #FFFFFF;              /* a tile */
  --ph-card-press: #F4F1EA;        /* a tile or row while it is pressed */
  --ph-ink: #0B0B0B;
  --ph-ink-2: #4A4740;             /* secondary text: 7.6:1 on the concrete, 9:1 on a tile */
  --ph-argus-ink: var(--argus-navy); /* Argus's logo on the concrete (its guide: navy on light) */
  --ph-ink-3: #5E5A52;             /* chevrons */
  --ph-sep: #E0DCD2;               /* the rule between rows inside a tile */
  --ph-fill: #F4F1EA;              /* a quiet field inside a tile */
  --ph-fill-2: #E0DCD2;
  /* the one accent, and what sits on it */
  --ph-accent: #FFD60A;            /* caution yellow - ink on it is 13.9:1 */
  --ph-accent-ink: #0B0B0B;
  --ph-accent-soft: #FFF3B0;       /* a picked row */
  --ph-tint: #0B0B0B;              /* links and plain buttons are ink, underlined */
  --ph-tint-ink: #FFFFFF;
  --ph-tint-soft: #FFF3B0;
  /* an ink block - a count, the picked filter, a tag. Black with white words
     in light; in dark it turns over to off-white with black ones, as the
     desk's chips do. Never --ph-ink as a fill: that turns light in dark. */
  --ph-solid: #0B0B0B;
  --ph-solid-ink: #FFFFFF;
  /* meanings */
  --ph-danger: #C8211A;            /* a red fill: white on it is 5.7:1, light or dark */
  --ph-danger-ink: #FFFFFF;        /* words on that fill */
  --ph-danger-text: #C8211A;       /* red words on the ground or a tile (lighter in dark) */
  --ph-danger-soft: #FBE3E1;
  --ph-ok: #1E7B34;                /* green words, on the ground, a tile or ok-soft */
  --ph-ok-soft: #E3F1E6;
  /* the Workshop's construction: an ink outline, a hard lift, one corner */
  --ph-edge: #0B0B0B;
  --ph-line: 2px;
  --ph-lift: 4px 4px 0 var(--ph-edge);
  --ph-lift-press: 1px 1px 0 var(--ph-edge);
  --ph-r: 12px;                    /* tiles, buttons, cards, the memo */
  --ph-r-btn: 12px;
  --ph-r-chip: 6px;                /* chips, tags, the plate, pills */
  --ph-r-sheet: 16px;
  --ph-r-tile: 12px;
  --ph-r-nav: 16px;
  /* the caution sign's black: the tape's stripes, the band's edges and its
     count, the sign-in panel. Black in light AND dark - the outlines go grey
     in dark, the sign never does. */
  --ph-sign: #0B0B0B;
  --ph-sign-ink: #FFFFFF;
  /* caution tape - the signature, for "needs you" */
  --ph-tape: repeating-linear-gradient(-45deg, var(--ph-sign) 0 12px, var(--ph-accent) 12px 24px);
  --ph-tape-fine: repeating-linear-gradient(-45deg, var(--ph-sign) 0 6px, var(--ph-accent) 6px 12px);
  /* the black bar of tabs and its lights */
  --ph-nav: #0B0B0B;
  --ph-nav-ink: #E4E0D7;
  --ph-nav-edge: transparent;      /* black on concrete needs none; on dark concrete it does */
  --ph-led-off: #4A4741;
  --ph-led-ring: #5E5A52;
  /* the screwed-on plate */
  --ph-plate: linear-gradient(180deg, #F8F7F3 0%, #DDDAD2 100%);
  --ph-plate-edge: #A9A59B;
  --ph-plate-ink: #2B2A27;
  --ph-plate-shadow: inset 0 1px 0 rgba(255, 255, 255, .9), 0 1px 0 rgba(255, 255, 255, .7), 0 1px 3px rgba(11, 11, 11, .2);
  --ph-screw: radial-gradient(circle at 35% 30%, #FBFAF7 0%, #CFCCC5 50%, #98958D 100%);
  --ph-screw-shadow: inset 0 0 0 1px rgba(60, 56, 48, .4), 0 1px 0 rgba(255, 255, 255, .8);
  --ph-screw-slot: #5F5C55;
  /* a sunken photo slot, and what shows in it before the picture */
  --ph-sunk: inset 0 2px 6px rgba(0, 0, 0, .38), inset 0 0 0 1px rgba(0, 0, 0, .16), 0 1px 0 rgba(255, 255, 255, .85);
  --ph-slot: #D8D2C5;
  /* decorative icon tiles - never a status */
  --ph-hue-1: #0B0B0B;
  --ph-hue-2: #2B2A27;
  --ph-hue-3: #3A3832;
  --ph-hue-4: #5E5A52;
  --ph-hue-ink: #FFFFFF;
  /* measures */
  --ph-bar-h: 52px;
  --ph-tab-h: 64px;
  --ph-pad: 16px;
  --ph-font: 'Archivo', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --ph-font-display: 'Archivo Black', 'Archivo', system-ui, -apple-system, "Segoe UI", sans-serif;
  /* kept so a desk component that still asks for glass gets a solid tile */
  --ph-glass: var(--ph-card);
  --ph-glass-edge: transparent;
  --ph-glass-shadow: var(--ph-lift);
  --ph-seg-on: var(--ph-ink);
}

/* ── the whole page ─────────────────────────────────────────────────────
   Archivo everywhere - the desk's dialogs included, when one opens here -
   so nothing on a phone looks borrowed. */
:root[data-ui="phone"] {
  --font: var(--ph-font);
  --font-display: var(--ph-font-display);
  --gutter: var(--ph-pad);
  color-scheme: light;
  background: var(--ph-bg);
  /* the app does its own pull to refresh; Chrome's would reload the page */
  overscroll-behavior-y: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-text-size-adjust: 100%;
}
:root[data-ui="phone"] body { background: var(--ph-bg); font-size: var(--fs-16); color: var(--ph-ink); }
:root[data-ui="phone"] h1, :root[data-ui="phone"] h2, :root[data-ui="phone"] h3 { font-feature-settings: normal; }

/* ── the Workshop dark (2026-10-14) ─────────────────────────────────────
   The desk's Workshop dark, in the phone's tokens. Under BOTH scopes like
   every dark in this file, and AFTER the rule above: the media copy has its
   specificity, so it is the order that wins it the color-scheme. Words on a
   tile (#24231F): ink 13.7:1, ink-2 9.0:1, red 6.6:1, green 7.4:1.
   The desk's own neutrals are set too, for its dialogs, tables and pages
   drawn inside the phone: Classic's blue-black would clash with the warm
   concrete. Its status colours stay its own, as in every theme. */
@media (prefers-color-scheme: dark) {
  :root[data-ui="phone"]:where(:not([data-theme="light"])) {
    color-scheme: dark;
    --ph-bg: #161513; --ph-card: #24231F; --ph-card-press: #2F2D29;
    --ph-ink: #F2EFE8; --ph-ink-2: #C9C4B8; --ph-ink-3: #A39E93; --ph-argus-ink: var(--argus-white);
    --ph-sep: #45423C; --ph-fill: #2F2D29; --ph-fill-2: #45423C;
    --ph-accent-soft: #3D3416; --ph-tint: #F2EFE8; --ph-tint-ink: #0B0B0B; --ph-tint-soft: #3D3416;
    --ph-solid: #F2EFE8; --ph-solid-ink: #0B0B0B;
    --ph-danger-text: #F28B82; --ph-danger-soft: #3D1916; --ph-ok: #4FC96A; --ph-ok-soft: #173A20;
    --ph-edge: #9C978C; --ph-lift: 4px 4px 0 #000000; --ph-lift-press: 1px 1px 0 #000000;
    --ph-nav-edge: #5E5A52;
    --ph-plate: linear-gradient(180deg, #4A4842 0%, #33312C 100%); --ph-plate-edge: #6B675E; --ph-plate-ink: #E4E0D7;
    --ph-plate-shadow: inset 0 1px 0 rgba(255, 255, 255, .14), 0 1px 3px rgba(0, 0, 0, .6);
    --ph-screw: radial-gradient(circle at 35% 30%, #A39E93 0%, #6B675E 50%, #45423C 100%);
    --ph-screw-shadow: inset 0 0 0 1px rgba(0, 0, 0, .5), 0 1px 0 rgba(255, 255, 255, .1); --ph-screw-slot: #2B2A27;
    --ph-sunk: inset 0 2px 6px rgba(0, 0, 0, .7), inset 0 0 0 1px rgba(0, 0, 0, .5), 0 1px 0 rgba(255, 255, 255, .08);
    --ph-slot: #0F0E0C;
    --ph-hue-1: #45423C; --ph-hue-2: #4F4C45; --ph-hue-3: #5A564E; --ph-hue-4: #6B675E;
    --plane: #161513; --surface: #24231F; --surface-2: #2F2D29; --surface-3: #3A3833;
    --hairline: #45423C; --hairline-strong: #9C978C; --control-line: #9C978C;
    --ink: #F2EFE8; --ink-2: #C9C4B8; --ink-muted: #A39E93;
    --head-bg: var(--surface-2); --neutral-bg: #2F2D29; --grid: #2F2D29; --axis: #45423C;
  }
}
:root[data-ui="phone"][data-theme="dark"] {
  color-scheme: dark;
  --ph-bg: #161513; --ph-card: #24231F; --ph-card-press: #2F2D29;
  --ph-ink: #F2EFE8; --ph-ink-2: #C9C4B8; --ph-ink-3: #A39E93; --ph-argus-ink: var(--argus-white);
  --ph-sep: #45423C; --ph-fill: #2F2D29; --ph-fill-2: #45423C;
  --ph-accent-soft: #3D3416; --ph-tint: #F2EFE8; --ph-tint-ink: #0B0B0B; --ph-tint-soft: #3D3416;
  --ph-solid: #F2EFE8; --ph-solid-ink: #0B0B0B;
  --ph-danger-text: #F28B82; --ph-danger-soft: #3D1916; --ph-ok: #4FC96A; --ph-ok-soft: #173A20;
  --ph-edge: #9C978C; --ph-lift: 4px 4px 0 #000000; --ph-lift-press: 1px 1px 0 #000000;
  --ph-nav-edge: #5E5A52;
  --ph-plate: linear-gradient(180deg, #4A4842 0%, #33312C 100%); --ph-plate-edge: #6B675E; --ph-plate-ink: #E4E0D7;
  --ph-plate-shadow: inset 0 1px 0 rgba(255, 255, 255, .14), 0 1px 3px rgba(0, 0, 0, .6);
  --ph-screw: radial-gradient(circle at 35% 30%, #A39E93 0%, #6B675E 50%, #45423C 100%);
  --ph-screw-shadow: inset 0 0 0 1px rgba(0, 0, 0, .5), 0 1px 0 rgba(255, 255, 255, .1); --ph-screw-slot: #2B2A27;
  --ph-sunk: inset 0 2px 6px rgba(0, 0, 0, .7), inset 0 0 0 1px rgba(0, 0, 0, .5), 0 1px 0 rgba(255, 255, 255, .08);
  --ph-slot: #0F0E0C;
  --ph-hue-1: #45423C; --ph-hue-2: #4F4C45; --ph-hue-3: #5A564E; --ph-hue-4: #6B675E;
  --plane: #161513; --surface: #24231F; --surface-2: #2F2D29; --surface-3: #3A3833;
  --hairline: #45423C; --hairline-strong: #9C978C; --control-line: #9C978C;
  --ink: #F2EFE8; --ink-2: #C9C4B8; --ink-muted: #A39E93;
  --head-bg: var(--surface-2); --neutral-bg: #2F2D29; --grid: #2F2D29; --axis: #45423C;
}
/* The side insets are the notch when the phone is turned on its side:
   viewport-fit=cover hands the whole screen over, so everything keeps clear
   of it here, and the fixed bars and the sheets do the same below. Upright
   they are 0. */
.ph {
  min-height: calc(100 * var(--dvh));
  padding: 0 env(safe-area-inset-right) calc(var(--ph-tab-h) + env(safe-area-inset-bottom) + 40px) env(safe-area-inset-left);
  background: var(--ph-bg); color: var(--ph-ink); font-family: var(--ph-font);
}
.ph-view { outline: none; touch-action: pan-y pinch-zoom; }
.ph-screen { padding-bottom: var(--sp-5); }
.ph-tint { color: var(--ph-tint); }

/* arriving: deeper slides in from the side, back slides in from the other,
   another tab simply appears. No fill mode - the resting layout never waits
   on an animation (CONVENTIONS). */
.ph-screen[data-enter="push"] { animation: ph-push var(--dur-3) var(--ease-out); }
.ph-screen[data-enter="pop"]  { animation: ph-pop var(--dur-3) var(--ease-out); }
.ph-screen[data-enter="tab"]  { animation: gg-fade var(--dur-2) var(--ease); }
@keyframes ph-push { from { transform: translateX(32%); opacity: .3; } }
@keyframes ph-pop  { from { transform: translateX(-22%); opacity: .4; } }

/* The Workshop's two building marks, as utilities for the screens' markup:
   the caps label, and the press of anything with a lift. */
.ph-caps {
  font-size: var(--fs-6); font-weight: 800; font-stretch: 112.5%; letter-spacing: .08em; line-height: 16px; text-transform: uppercase;
}

/* ── the title bar ─────────────────────────────────────────────────────── */
.ph-bar {
  position: sticky; top: 0; z-index: var(--z-topbar);
  padding-top: env(safe-area-inset-top);
  background: transparent;
  transition: background-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.ph-bar.is-scrolled { background: var(--ph-bg); box-shadow: 0 var(--ph-line) 0 var(--ph-edge); }
.ph-bar__row {
  height: var(--ph-bar-h); padding: 0 calc(var(--ph-pad) + 4px) 0 var(--ph-pad);
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 8px;
}
.ph-bar__lead { justify-self: start; min-width: 0; display: flex; }
.ph-bar__trail { justify-self: end; display: flex; gap: 12px; }
.ph-bar__title {
  font-family: var(--ph-font-display); font-size: var(--fs-16); font-weight: 400; letter-spacing: -.01em;
  max-width: calc(52 * var(--vw)); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center;
  opacity: 1; transition: opacity var(--dur-2) var(--ease);
}
/* a screen with a large title: the bar's is hidden until that one scrolls under it */
.ph-bar--large:not(.is-titled) .ph-bar__title { opacity: 0; }

/* Back and the bar's buttons are splash tiles: white, outlined, lifted. */
.ph-back, .ph-iconbtn {
  display: inline-flex; align-items: center; justify-content: center; min-height: 44px; height: 44px;
  border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r-btn); background: var(--ph-card);
  box-shadow: var(--ph-lift); color: var(--ph-ink); text-decoration: none; cursor: pointer;
  transition: transform .08s var(--ease), box-shadow .08s var(--ease);
}
.ph-back { gap: 4px; padding: 0 12px 0 8px; max-width: 100%; font-size: var(--fs-12); font-weight: 800; }
.ph-back:hover { text-decoration: none; }
.ph-back svg { width: 20px; height: 20px; stroke-width: 2.6; flex: none; }
.ph-back__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* the back tile keeps to its third of the bar - a long title never runs over
   it; its label shortens instead. The padding is room for the tile's lift. */
.ph-bar__lead { justify-self: stretch; overflow: hidden; padding: 0 4px 4px 0; margin: 0 -4px -4px 0; }
/* The back tile says where it goes, whole (2026-10-20, "Proje…"). The title
   took the middle of the bar at up to 52% of the screen - even while hidden
   under a project's large title - and the back tile kept what was left: on
   every phone a project's read "Proje…", or only "…" beside a long name. Each
   side now keeps at least its tiles' whole width, and the title shortens
   instead; it sits a little off centre when the two sides differ, as the
   phones' own bars do. phone_check holds it. */
.ph-bar__row { grid-template-columns: minmax(max-content, 1fr) minmax(0, auto) minmax(max-content, 1fr); }
.ph-iconbtn { width: 44px; padding: 0; }
.ph-iconbtn svg { width: 20px; height: 20px; stroke-width: 2.4; }
.ph-back:active, .ph-iconbtn:active { transform: translate(3px, 3px); box-shadow: var(--ph-lift-press); }

/* ── the large title, and the head of a job, a request, an item ────────── */
.ph-hero { padding: 8px calc(var(--ph-pad) + 4px) 16px var(--ph-pad); }
.ph-hero__eyebrow { color: var(--ph-ink-2); font-size: var(--fs-6); font-weight: 800; font-stretch: 112.5%; letter-spacing: .08em; text-transform: uppercase; }
.ph-hero__row { display: flex; align-items: center; gap: 12px; }
.ph-hero__title {
  flex: 1; min-width: 0; font-family: var(--ph-font-display); font-size: var(--fs-ph-large); font-weight: 400;
  letter-spacing: -.02em; line-height: 1; text-wrap: balance;
}
.ph-hero__sub { margin-top: 8px; color: var(--ph-ink-2); font-size: var(--fs-8); font-weight: 600; }

.ph-head { padding: 4px calc(var(--ph-pad) + 4px) 16px var(--ph-pad); }
.ph-head__eyebrow { display: flex; align-items: center; gap: 8px; color: var(--ph-ink-2); font-size: var(--fs-10); font-weight: 700; }
.ph-head__title {
  margin-top: 12px; font-family: var(--ph-font-display); font-size: var(--fs-ph-title); font-weight: 400;
  line-height: 1; letter-spacing: -.01em; text-wrap: balance; overflow-wrap: anywhere;
}
.ph-head__sub, .ph-head__meta {
  margin-top: 8px; display: inline-flex; align-items: flex-start; gap: 6px; color: var(--ph-ink-2); font-size: var(--fs-12); font-weight: 600;
}
a.ph-head__sub { min-height: 44px; margin-top: 0; align-items: center; color: var(--ph-ink); text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 3px; }
.ph-head__sub svg { width: 17px; height: 17px; flex: none; stroke-width: 2.4; }

.ph-avatar {
  width: 44px; height: 44px; flex: none; border-radius: var(--ph-r-btn); border: var(--ph-line) solid var(--ph-edge);
  display: grid; place-items: center; background: var(--ph-card); box-shadow: var(--ph-lift);
  color: var(--ph-ink); font-family: var(--ph-font-display); font-weight: 400; font-size: var(--fs-12); cursor: pointer;
}
button.ph-avatar:active { transform: translate(3px, 3px); box-shadow: var(--ph-lift-press); }
.ph-avatar--big { width: 64px; height: 64px; font-size: var(--fs-21); cursor: default; }

/* ── the screwed-on plate (from I): a job's number and its status, or the
   company's name on Home. Its screws are decoration; its words are not. ── */
.ph-plate {
  display: inline-flex; align-items: center; gap: 8px; min-width: 0; height: 32px; padding: 0 8px; box-sizing: border-box;
  border: 1px solid var(--ph-plate-edge); border-radius: var(--ph-r-chip); background: var(--ph-plate);
  box-shadow: var(--ph-plate-shadow);
  color: var(--ph-plate-ink);
}
.ph-plate--big { height: 44px; }
.ph-plate__screw {
  position: relative; flex: none; width: 10px; height: 10px; border-radius: 50%; background: var(--ph-screw);
  box-shadow: var(--ph-screw-shadow);
}
.ph-plate__screw::after {
  content: ""; position: absolute; left: 2px; right: 2px; top: 4px; height: 2px; border-radius: 1px; background: var(--ph-screw-slot); transform: rotate(-40deg);
}
.ph-plate__screw + .ph-plate__text ~ .ph-plate__screw::after { transform: rotate(25deg); }
.ph-plate__text {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-6); font-weight: 800; font-stretch: 112.5%; letter-spacing: .08em; text-transform: uppercase;
}
.ph-plate--big .ph-plate__text { font-size: var(--fs-10); color: var(--ph-ink); letter-spacing: .06em; }
.ph-plate__status { flex: none; display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-6); font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
/* a status light: lit green for a working job, a dim lamp for anything else */
.ph-led {
  flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--ph-led-off);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .7), 0 0 0 1px var(--ph-led-ring);
}
.ph-led--good {
  background: radial-gradient(circle at 40% 35%, rgba(255, 255, 255, .9) 0 1px, transparent 2.5px), #1E9E45;
  box-shadow: 0 0 0 1px #14632B, 0 0 6px 1px rgba(30, 158, 69, .55);
}
.ph-led--warning { background: radial-gradient(circle at 40% 35%, rgba(255, 255, 255, .9) 0 1px, transparent 2.5px), var(--ph-accent); box-shadow: 0 0 0 1px #8A6D00; }
.ph-led--critical { background: radial-gradient(circle at 40% 35%, rgba(255, 255, 255, .9) 0 1px, transparent 2.5px), var(--ph-danger); box-shadow: 0 0 0 1px #7A1410; }

/* ── groups of rows: each list is one splash tile ──────────────────────── */
.ph-group { margin: 0 calc(var(--ph-pad) + 4px) 24px var(--ph-pad); }
.ph-group__head { display: flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 0 8px; }
.ph-group__title {
  flex: 1; display: flex; align-items: center; gap: 8px;
  font-family: var(--ph-font-display); font-size: var(--fs-18); font-weight: 400; line-height: 24px; color: var(--ph-ink);
}
.ph-group__name { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; }
.ph-group__name > .ph-group__title { flex: 0 1 auto; min-width: 0; }
.ph-group__count {
  display: inline-flex; align-items: center; height: 24px; padding: 0 8px; border-radius: var(--ph-r-chip);
  background: var(--ph-solid); color: var(--ph-solid-ink); font-family: var(--ph-font); font-size: var(--fs-8); font-weight: 800; line-height: 16px;
  font-variant-numeric: tabular-nums;
}
.ph-group__more {
  display: inline-flex; align-items: center; min-height: 44px; margin: -10px 0; padding-left: 12px;
  color: var(--ph-ink); font-size: var(--fs-12); font-weight: 800;
  text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 3px;
}
.ph-group__foot { padding: 12px 2px 0; font-size: var(--fs-8); font-weight: 600; color: var(--ph-ink-2); }

.ph-list {
  background: var(--ph-card); border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r);
  box-shadow: var(--ph-lift); overflow: hidden;
}
.ph-list--flush { border: 0; border-radius: 0; box-shadow: none; background: transparent; }
.ph-list__pad { padding: 12px; }
.ph-list__pad--tight { padding: 0 12px 12px; }
.ph-list__empty { padding: 16px 12px; color: var(--ph-ink-2); font-size: var(--fs-12); font-weight: 600; }
.ph-prewrap { white-space: pre-wrap; overflow-wrap: anywhere; }

.ph-row {
  position: relative; display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: 52px; padding: 12px;
  background: none; border: 0; text-align: left; font: inherit; color: var(--ph-ink); text-decoration: none;
}
a.ph-row:hover { text-decoration: none; }
a.ph-row:active, button.ph-row:active { background: var(--ph-card-press); }
.ph-list--flush .ph-row { background: var(--ph-card); }
/* the rule between rows: one quiet line across the tile */
.ph-row + .ph-row::before, .ph-row + .ph-noteitem::before, .ph-noteitem + .ph-row::before, .ph-noteitem + .ph-noteitem::before {
  content: ""; position: absolute; top: 0; right: 0; left: 0; height: var(--ph-line); background: var(--ph-sep);
}
.ph-row__lead { flex: none; display: grid; place-items: center; }
.ph-row__body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ph-row__title { font-size: var(--fs-12); font-weight: 700; line-height: 20px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ph-row__sub { font-size: var(--fs-8); font-weight: 600; line-height: 16px; color: var(--ph-ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ph-row__meta { flex: none; display: flex; align-items: center; gap: 6px; color: var(--ph-ink-2); font-size: var(--fs-8); font-weight: 700; }
.ph-row__chev { flex: none; color: var(--ph-ink); display: grid; }
.ph-row__chev svg { width: 16px; height: 16px; stroke-width: 2.8; }
.ph-row--center { justify-content: center; }
.ph-row--center .ph-row__body { align-items: center; }
.ph-row--critical .ph-row__title { color: var(--ph-danger-text); }
.ph-row[hidden] { display: none; }

/* a job's monogram, and an icon on its tile */
.ph-mono {
  width: 44px; height: 44px; border-radius: var(--ph-r-chip); border: var(--ph-line) solid var(--ph-edge);
  display: grid; place-items: center; background: var(--ph-card); color: var(--ph-ink);
  font-family: var(--ph-font-display); font-size: var(--fs-8); font-weight: 400; font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}
.ph-mono--good { background: var(--ph-ok-soft); color: var(--ph-ok); }
.ph-mono--warning { background: var(--ph-card); box-shadow: inset 0 -6px 0 0 var(--ph-accent); }
.ph-mono--info { background: var(--ph-accent-soft); }
.ph-mono--critical { background: var(--ph-danger-soft); color: var(--ph-danger-text); }
.ph-ico {
  width: 32px; height: 32px; border-radius: 8px; display: grid; place-items: center; color: var(--ph-hue-ink); background: var(--ph-hue-1);
}
.ph-ico svg { width: 19px; height: 19px; stroke-width: 2.2; }
.ph-ico[data-hue="1"] { background: var(--ph-hue-1); }
.ph-ico[data-hue="2"] { background: var(--ph-hue-2); }
.ph-ico[data-hue="3"] { background: var(--ph-hue-3); }
.ph-ico[data-hue="4"] { background: var(--ph-hue-4); }

/* pills, counts, badges: the caps chip. Warning wears the tape. */
.ph-pill {
  display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 8px; border-radius: var(--ph-r-chip); white-space: nowrap;
  font-size: var(--fs-4); font-weight: 800; letter-spacing: .05em; line-height: 16px; text-transform: uppercase;
  background: var(--ph-fill); color: var(--ph-ink-2);
}
.ph-pill--good { background: var(--ph-ok-soft); color: var(--ph-ok); }
.ph-pill--warning { background: var(--ph-card); color: var(--ph-ink); border: var(--ph-line) solid var(--ph-edge); padding-left: 0; overflow: hidden; }
.ph-pill--warning::before { content: ""; align-self: stretch; width: 10px; background: var(--ph-tape-fine); border-right: var(--ph-line) solid var(--ph-edge); }
.ph-pill--serious { background: var(--ph-danger-soft); color: var(--ph-danger-text); }
.ph-pill--critical { background: var(--ph-danger); color: var(--ph-danger-ink); }
.ph-pill--info { background: var(--ph-solid); color: var(--ph-solid-ink); }
.ph-count { display: flex; flex-direction: column; align-items: flex-end; line-height: 1.1; }
.ph-count b { font-family: var(--ph-font-display); font-size: var(--fs-17); font-weight: 400; color: var(--ph-ink); font-variant-numeric: tabular-nums; }
.ph-count small { font-size: var(--fs-4); font-weight: 700; color: var(--ph-ink-2); }
.ph-count--warning b { color: var(--ph-ink); }
.ph-count--critical b { color: var(--ph-danger-text); }
.ph-badge {
  min-width: 24px; height: 24px; padding: 0 6px; border-radius: var(--ph-r-chip); display: inline-grid; place-items: center;
  border: var(--ph-line) solid var(--ph-edge); background: var(--ph-accent); color: var(--ph-accent-ink);
  font-size: var(--fs-6); font-weight: 800; font-variant-numeric: tabular-nums;
}
.ph-badge--loud { background: var(--ph-danger); color: var(--ph-danger-ink); }
.ph-qty {
  min-width: 44px; height: 32px; padding: 0 6px; border-radius: var(--ph-r-chip); display: grid; place-items: center;
  border: var(--ph-line) solid var(--ph-edge); background: var(--ph-card);
  font-weight: 800; font-size: var(--fs-8); font-variant-numeric: tabular-nums;
}
.ph-time { min-width: 64px; font-size: var(--fs-8); font-weight: 800; color: var(--ph-ink); font-variant-numeric: tabular-nums; }
.ph-delta { font-weight: 800; font-variant-numeric: tabular-nums; }
.ph-chip {
  display: inline-flex; align-items: center; gap: 4px; height: 24px; padding: 0 8px; border-radius: var(--ph-r-chip);
  border: var(--ph-line) solid var(--ph-edge); background: var(--ph-card); color: var(--ph-ink);
  font-size: var(--fs-8); font-weight: 800; line-height: 16px; white-space: nowrap;
}

/* ── search and the filter chips ───────────────────────────────────────── */
.ph-search { position: relative; display: block; margin: 0 calc(var(--ph-pad) + 4px) 16px var(--ph-pad); }
.ph-search svg { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); width: 20px; height: 20px; color: var(--ph-ink); stroke-width: 2.4; pointer-events: none; }
.ph-search__input {
  width: 100%; height: 48px; padding: 0 12px 0 40px; border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r);
  background: var(--ph-card); color: var(--ph-ink); font: inherit; font-size: var(--fs-16); font-weight: 600; /* 17px: an iPhone never zooms */
  -webkit-appearance: none; appearance: none;
}
.ph-search__input::placeholder { color: var(--ph-ink-2); font-weight: 500; }
.ph-search__input:focus { outline: none; box-shadow: 0 0 0 3px var(--ph-accent); }

/* Filters are a row of outlined chips; the chosen one is ink. A row wider
   than the phone scrolls sideways, 6px taller than its chips each way -
   room for each chip's lift and its 44px press - and gives that back. */
.ph-seg {
  display: flex; gap: 8px; margin: -6px 0 10px; padding: 6px calc(var(--ph-pad) + 4px) 6px var(--ph-pad);
  overflow-x: auto; scrollbar-width: none;
}
.ph-seg::-webkit-scrollbar { display: none; }
/* A segment inside a card (More's Language) wraps instead: a card is not the
   phone's width, and a scroll box would cut off each chip's 44px press */
.ph-list__pad .ph-seg { flex-wrap: wrap; margin: 0; padding: 4px 0; overflow: visible; }
.ph-seg__btn {
  position: relative; flex: none; min-width: 0; height: 36px; padding: 0 12px;
  border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r-btn); background: var(--ph-card); box-shadow: var(--ph-lift);
  color: var(--ph-ink); font: inherit; font-size: var(--fs-10); font-weight: 800; white-space: nowrap; cursor: pointer;
}
.ph-seg__btn[aria-selected="true"] { background: var(--ph-solid); color: var(--ph-solid-ink); }
.ph-seg__btn:active { transform: translate(3px, 3px); box-shadow: var(--ph-lift-press); }
.ph-seg__label { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; vertical-align: bottom; }
.ph-seg__n { color: var(--ph-ink-2); font-weight: 700; font-variant-numeric: tabular-nums; }
.ph-seg__btn[aria-selected="true"] .ph-seg__n { color: inherit; opacity: .8; }

/* ── the action tiles under a header (Photos, Request, Directions…). The
   primary one - Photos - is the caution-yellow tile. ──────────────────── */
.ph-actions {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: 12px; margin: 0 calc(var(--ph-pad) + 4px) 24px var(--ph-pad);
}
.ph-action {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; min-height: 64px;
  padding: 8px 4px; border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r); background: var(--ph-card);
  box-shadow: var(--ph-lift); color: var(--ph-ink); font: inherit; font-size: var(--fs-8); font-weight: 800; line-height: 16px;
  text-decoration: none; cursor: pointer; transition: transform .08s var(--ease), box-shadow .08s var(--ease);
}
.ph-action:hover { text-decoration: none; }
.ph-action:active { transform: translate(3px, 3px); box-shadow: var(--ph-lift-press); }
.ph-action__icon svg { width: 24px; height: 24px; stroke-width: 2.4; display: block; }
.ph-action__label { color: inherit; white-space: nowrap; }
.ph-action--primary { background: var(--ph-accent); color: var(--ph-accent-ink); font-family: var(--ph-font-display); font-weight: 400; font-size: var(--fs-12); }
/* Home: Photos is the hero - a full-width yellow sign over the others,
   which share the row beneath it */
.ph-actions--hero { display: flex; flex-wrap: wrap; }
.ph-actions--hero .ph-action { flex: 1 1 0; min-width: 0; }
.ph-actions--hero .ph-action--primary {
  flex-basis: 100%; flex-direction: row; gap: 12px; min-height: 80px;
  font-size: var(--fs-ph-title); line-height: 1; letter-spacing: -.01em;
}
.ph-actions--hero .ph-action--primary .ph-action__icon svg { width: 32px; height: 32px; stroke-width: 2.3; }
.ph-actions--hero .ph-action:not(.ph-action--primary) { font-size: var(--fs-12); min-height: 64px; }

.ph-roundbtn {
  width: 44px; height: 44px; border-radius: var(--ph-r-btn); display: grid; place-items: center;
  border: var(--ph-line) solid var(--ph-edge); background: var(--ph-card); box-shadow: var(--ph-lift); color: var(--ph-ink);
}
.ph-roundbtn svg { width: 19px; height: 19px; stroke-width: 2.4; }
.ph-roundbtn:active { transform: translate(3px, 3px); box-shadow: var(--ph-lift-press); }
.ph-contact { display: flex; align-items: center; gap: 12px; padding: 12px; }
.ph-contact + .ph-contact { border-top: var(--ph-line) solid var(--ph-sep); }
.ph-contact__who { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.ph-contact__label { font-size: var(--fs-6); font-weight: 800; font-stretch: 112.5%; letter-spacing: .08em; text-transform: uppercase; color: var(--ph-ink-2); }
.ph-contact__name { font-size: var(--fs-12); font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ph-contact__btns { display: flex; gap: 12px; padding-right: 4px; }
.ph-kv { display: flex; justify-content: space-between; gap: 16px; padding: 8px 0; font-size: var(--fs-12); }
.ph-kv + .ph-kv { border-top: var(--ph-line) solid var(--ph-sep); }
.ph-kv__k { color: var(--ph-ink-2); font-weight: 600; }
.ph-kv__v { text-align: right; overflow-wrap: anywhere; font-weight: 700; }

/* ── a job's facts: PM, target, client - one strip, three cells ────────── */
.ph-facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); margin: -8px calc(var(--ph-pad) + 4px) 24px var(--ph-pad);
  border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r); background: var(--ph-card); box-shadow: var(--ph-lift); overflow: hidden;
}
.ph-fact {
  display: flex; flex-direction: column; justify-content: center; min-width: 0; min-height: 52px; padding: 4px 12px;
  color: var(--ph-ink); text-decoration: none; border: 0; background: none; font: inherit; text-align: left;
}
.ph-fact + .ph-fact { border-left: var(--ph-line) solid var(--ph-edge); }
.ph-fact__k { font-size: var(--fs-6); font-weight: 800; font-stretch: 112.5%; letter-spacing: .08em; line-height: 16px; text-transform: uppercase; color: var(--ph-ink-2); }
.ph-fact__v { font-size: var(--fs-12); font-weight: 700; line-height: 20px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
a.ph-fact .ph-fact__v, button.ph-fact .ph-fact__v { font-weight: 800; text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 3px; }
a.ph-fact:active, button.ph-fact:active { background: var(--ph-card-press); }

/* ── Home ──────────────────────────────────────────────────────────────── */
/* the header: the plate, the person's name, the day. Home's bar has no
   buttons, so the header rises into it rather than sit under an empty row;
   the bar lets a press through wherever it has nothing of its own. */
.ph-greet { margin-top: calc(-1 * var(--ph-bar-h)); padding: 8px calc(var(--ph-pad) + 4px) 16px var(--ph-pad); }
.ph-bar { pointer-events: none; }
.ph-bar__lead > *, .ph-bar__trail > * { pointer-events: auto; }
/* ...unless the no-signal (or desk) banner sits between them */
.ph-banner:not([hidden]) ~ .ph-view .ph-greet { margin-top: 0; }
.ph-greet__top { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 44px; }
.ph-greet__btns { display: flex; align-items: center; gap: 10px; flex: none; }
/* Home's one-tap light/dark (2026-10-14): the avatar's box beside it, its
   icon what a tap turns to - the moon in light, the sun in dark. Picked here
   rather than drawn, so it is right under Automatic as the phone flips. */
.ph-look {
  width: 44px; height: 44px; flex: none; padding: 0; border-radius: var(--ph-r-btn); border: var(--ph-line) solid var(--ph-edge);
  display: grid; place-items: center; background: var(--ph-card); box-shadow: var(--ph-lift); color: var(--ph-ink); cursor: pointer;
}
.ph-look:active { transform: translate(3px, 3px); box-shadow: var(--ph-lift-press); }
.ph-look svg { width: 20px; height: 20px; }
.ph-look__moon, .ph-look__sun { display: grid; place-items: center; }
.ph-look__sun { display: none; }
@media (prefers-color-scheme: dark) {
  :root[data-ui="phone"]:where(:not([data-theme="light"])) .ph-look__moon { display: none; }
  :root[data-ui="phone"]:where(:not([data-theme="light"])) .ph-look__sun { display: grid; }
}
:root[data-ui="phone"][data-theme="dark"] .ph-look__moon { display: none; }
:root[data-ui="phone"][data-theme="dark"] .ph-look__sun { display: grid; }
.ph-greet__title {
  margin: 8px 0 0; font-family: var(--ph-font-display); font-size: var(--fs-ph-large); font-weight: 400; line-height: 1;
  letter-spacing: -.02em; text-wrap: balance;
}
.ph-greet__line { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 8px; }
.ph-datechip {
  flex: none; padding: 4px 8px; border-radius: var(--ph-r-chip); background: var(--ph-solid); color: var(--ph-solid-ink);
  font-size: var(--fs-6); font-weight: 800; font-stretch: 112.5%; letter-spacing: .08em; line-height: 16px; text-transform: uppercase;
}
.ph-greet__cal {
  display: inline-flex; align-items: center; gap: 4px; color: var(--ph-ink-2);
  font-size: var(--fs-6); font-weight: 800; font-stretch: 112.5%; letter-spacing: .08em; line-height: 16px; text-transform: uppercase;
}
.ph-greet__cal svg { width: 16px; height: 16px; flex: none; stroke-width: 2.4; }

/* What is waiting on you: the caution band, edge to edge, taped top and
   bottom like a barricade. The count is a chip, not a poster numeral -
   Sagar did not want the giant 8. Nothing waiting is a calm tile. */
.ph-band {
  display: block; margin: 0 0 24px; border-top: var(--ph-line) solid var(--ph-sign); border-bottom: var(--ph-line) solid var(--ph-sign);
  background: var(--ph-accent); color: var(--ph-accent-ink); text-decoration: none;
}
.ph-band:hover { text-decoration: none; }
.ph-band__tape { display: block; height: 16px; background: var(--ph-tape); }
.ph-band__tape:first-child { border-bottom: var(--ph-line) solid var(--ph-sign); }
.ph-band__tape:last-child { border-top: var(--ph-line) solid var(--ph-sign); }
.ph-band__body { display: flex; align-items: center; gap: 16px; padding: 12px calc(var(--ph-pad) + env(safe-area-inset-right)) 12px calc(var(--ph-pad) + env(safe-area-inset-left)); }
.ph-band__n {
  flex: none; min-width: 52px; height: 52px; padding: 0 8px; box-sizing: border-box; display: grid; place-items: center;
  border-radius: var(--ph-r-chip); background: var(--ph-sign); color: var(--ph-accent);
  font-family: var(--ph-font-display); font-size: var(--fs-ph-title); line-height: 1; font-variant-numeric: tabular-nums;
}
.ph-band__text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.ph-band__title { font-family: var(--ph-font-display); font-size: var(--fs-18); line-height: 24px; }
.ph-band__chips { display: flex; flex-wrap: wrap; gap: 8px; }
/* the band is the sign, so its chips are the sign's too: white and black in
   light and dark alike, not the tiles' dark */
.ph-band .ph-chip { background: var(--ph-sign-ink); color: var(--ph-sign); border-color: var(--ph-sign); }
.ph-band__arrow { flex: none; display: grid; }
.ph-band__arrow svg { width: 24px; height: 24px; stroke-width: 2.6; }
.ph-band:active .ph-band__body { background: rgba(11, 11, 11, .06); }

/* the tiles: two columns of splash tiles, each one answer */
.ph-bento {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: minmax(116px, auto);
  grid-auto-flow: dense; gap: 12px; margin: 0 calc(var(--ph-pad) + 4px) 24px var(--ph-pad);
}
.ph-tile {
  position: relative; display: flex; flex-direction: column; gap: 4px; min-width: 0; padding: 12px;
  border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r-tile); background: var(--ph-card); box-shadow: var(--ph-lift);
  color: var(--ph-ink); text-decoration: none; overflow: hidden;
  transition: transform .08s var(--ease), box-shadow .08s var(--ease);
}
.ph-tile:hover { text-decoration: none; }
.ph-tile:active { transform: translate(3px, 3px); box-shadow: var(--ph-lift-press); background: var(--ph-card-press); }
.ph-tile--wide { grid-column: span 2; }
.ph-tile--tall { grid-row: span 2; }
.ph-tile__head { display: flex; align-items: center; gap: 6px; color: var(--ph-ink-2); font-size: var(--fs-8); font-weight: 700; line-height: 16px; }
.ph-tile__icon svg { width: 16px; height: 16px; stroke-width: 2.4; display: block; color: var(--ph-ink-2); }
/* one big number per tile - read at arm's length, in the sun, on a ladder */
.ph-tile__value {
  font-family: var(--ph-font-display); font-size: var(--fs-24); font-weight: 400; line-height: 1; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.ph-tile__value small { margin-left: 4px; font-family: var(--ph-font); font-size: var(--fs-10); font-weight: 800; letter-spacing: .04em; }
.ph-tile__foot { margin-top: auto; font-size: var(--fs-8); font-weight: 600; color: var(--ph-ink-2); display: flex; flex-wrap: wrap; gap: 6px; line-height: 16px; }
.ph-tile__list { display: flex; flex-direction: column; gap: 8px; width: 100%; }
.ph-tile__item { display: block; font-size: var(--fs-8); color: var(--ph-ink); line-height: 1.3;
  overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.ph-tile__item b { display: block; font-weight: 800; color: var(--ph-ink-2); font-size: var(--fs-6); }
.ph-tile__item--next { font-size: var(--fs-10); font-weight: 800; padding-bottom: 8px; border-bottom: var(--ph-line) solid var(--ph-sep); }
.ph-tile__item.is-past { color: var(--ph-ink-2); text-decoration: line-through; text-decoration-color: var(--ph-ink-3); }
/* a tile that needs you wears a strip of tape along its top */
.ph-tile--warning { padding-top: 20px; }
.ph-tile--warning::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 8px; background: var(--ph-tape-fine);
  border-bottom: var(--ph-line) solid var(--ph-edge);
}
.ph-tile--critical .ph-tile__icon svg { color: var(--ph-danger-text); }
.ph-tile .neg, .ph-tile__foot .neg {
  display: inline-flex; align-items: center; gap: 4px; min-height: 24px; padding: 4px 8px; box-sizing: border-box; border-radius: var(--ph-r-chip);
  line-height: 16px;
  background: var(--ph-danger); color: var(--ph-danger-ink);
  font-size: var(--fs-6); font-weight: 800; font-stretch: 112.5%; letter-spacing: .08em; text-transform: uppercase;
}
.ph-figs { display: flex; gap: 20px; flex-wrap: wrap; }
.ph-fig { display: flex; flex-direction: column; min-width: 0; }
.ph-fig__v { font-family: var(--ph-font-display); font-size: var(--fs-21); font-weight: 400; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.ph-fig__v.neg, .ph-fig__v.pos {
  display: inline; height: auto; padding: 0; border-radius: 0; background: none;
  font-size: var(--fs-21); font-weight: 400; font-stretch: 100%; letter-spacing: -.02em; text-transform: none;
}
.ph-fig__v.neg { color: var(--ph-danger-text); }
.ph-fig__v.pos { color: var(--ph-ok); }
.ph-fig__l { font-size: var(--fs-6); color: var(--ph-ink-2); font-weight: 700; }
.ph-figrow { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.ph-figrow__foot { margin-top: 8px; font-size: var(--fs-8); font-weight: 600; color: var(--ph-ink-2); }
.ph-meter { margin-top: 16px; height: 12px; border-radius: var(--ph-r-chip); border: var(--ph-line) solid var(--ph-edge); background: var(--ph-card); overflow: hidden; }
.ph-meter__fill { display: block; height: 100%; background: var(--ph-ink); }

/* ── a job: the photo strip (sunken slots, from I), the walk-through memo,
   notes, uploads waiting ───────────────────────────────────────────────── */
.ph-strip {
  display: flex; gap: 12px; padding: 4px 4px 8px; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none;
}
.ph-strip::-webkit-scrollbar { display: none; }
.ph-group--photos .ph-list { border: 0; border-radius: 0; box-shadow: none; background: transparent; overflow: visible; margin: 0 calc(-1 * var(--ph-pad) - 4px) 0 0; }
.ph-strip__item.photo-tile {
  position: relative; flex: none; width: 112px; height: 80px; aspect-ratio: auto; padding: 0; border: 0; border-radius: var(--ph-r); overflow: hidden;
  scroll-snap-align: start; background: var(--ph-slot); display: grid; place-items: center; cursor: pointer;
}
.ph-strip__item.photo-tile::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: var(--ph-sunk); pointer-events: none; }
.ph-strip__item img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ph-strip__item .file-thumb__label { color: var(--ph-ink); font-weight: 800; }
.ph-roll { margin: 0 4px; }
.ph-roll .photo-grid { display: grid; }
/* Select on the camera roll (2026-10-22): a ticked photo is outlined with a
   check in its corner; one this person may not change is dimmed */
.ph-roll.is-picking .photo-tile.is-picked::after {
  content: ""; position: absolute; inset: 0; border: 4px solid var(--ph-accent, var(--accent)); border-radius: inherit;
  background: rgba(17, 20, 24, .18); pointer-events: none;
}
.ph-roll.is-picking .photo-tile.is-picked::before {
  content: "✓"; position: absolute; top: 6px; right: 6px; z-index: var(--z-raised); width: 24px; height: 24px;
  display: grid; place-items: center; border-radius: 50%;
  background: var(--ph-accent, var(--accent)); color: var(--ph-ink, #111); font-weight: 900; font-size: var(--fs-7);
}
.ph-roll.is-picking .photo-tile.is-locked { opacity: .35; }
.ph-bulkbar__count { font-weight: 700; margin-bottom: 8px; }
.ph-bulkbar[hidden] { display: none; }

/* The walk-through: D's tile and G's site memo in one - white paper, an ink
   outline, faint ruled lines, a scrap of caution tape holding it on. It sits
   above the notes and folds to two lines (Sagar: "collapsable for real
   estate purposes"); the whole head is the switch. */
.ph-memo {
  position: relative; margin: 8px calc(var(--ph-pad) + 4px) 16px var(--ph-pad);
  border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r); background: var(--ph-card);
}
.ph-memo__tape {
  position: absolute; top: -10px; left: 44%; width: 72px; height: 18px; background: var(--ph-tape-fine);
  box-shadow: 0 1px 2px rgba(11, 11, 11, .25); transform: rotate(7deg); pointer-events: none;
}
.ph-memo__head {
  display: flex; align-items: center; gap: 8px; width: 100%; min-height: 48px; padding: 12px 12px 8px; border: 0;
  border-radius: calc(var(--ph-r) - 2px) calc(var(--ph-r) - 2px) 0 0; background: none; color: var(--ph-ink);
  font: inherit; text-align: left; cursor: pointer;
}
.ph-memo__tag {
  flex: none; display: inline-flex; align-items: center; height: 24px; padding: 0 6px; border-radius: var(--ph-r-chip);
  background: var(--ph-solid); color: var(--ph-solid-ink); font-size: var(--fs-6); font-weight: 800; letter-spacing: .08em; line-height: 16px;
}
.ph-memo__who { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-8); line-height: 16px; }
.ph-memo__who b { font-weight: 800; }
.ph-memo__who span { font-weight: 600; color: var(--ph-ink-2); }
.ph-memo__toggle {
  flex: none; display: inline-flex; align-items: center; gap: 2px; height: 32px; padding: 0 6px 0 10px;
  border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r-btn); background: var(--ph-card); box-shadow: var(--ph-lift);
  font-size: var(--fs-8); font-weight: 800; line-height: 16px; white-space: nowrap;
}
.ph-memo__toggle svg { width: 14px; height: 14px; stroke-width: 3; flex: none; transition: transform var(--dur-2) var(--ease); }
.ph-memo__head[aria-expanded="true"] .ph-memo__toggle svg { transform: rotate(180deg); }
.ph-memo__head:active .ph-memo__toggle { transform: translate(3px, 3px); box-shadow: var(--ph-lift-press); }
.ph-memo__body {
  margin: 0; padding: 0 12px 12px; white-space: pre-wrap; overflow-wrap: anywhere;
  font-size: var(--fs-12); font-weight: 500; line-height: 22px;
  background: repeating-linear-gradient(to bottom, transparent 0 21px, var(--ph-sep) 21px 22px) 12px 0 / calc(100% - 24px) 100% no-repeat;
}
.ph-memo__preview {
  margin: 0 12px 12px; font-size: var(--fs-12); font-weight: 500; line-height: 22px; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  background: repeating-linear-gradient(to bottom, transparent 0 21px, var(--ph-sep) 21px 22px);
}
.ph-memo__preview[hidden], .ph-memo__body[hidden] { display: none; }
.ph-memo__foot { display: flex; justify-content: flex-end; padding: 0 12px 8px; }
.ph-memo__foot[hidden] { display: none; }
/* the scope under it (2026-10-22, item 4): no tape, three lines folded */
.ph-memo--scope { margin-top: 0; }
.ph-memo--scope .ph-memo__preview { -webkit-line-clamp: 3; }
div.ph-memo__head { cursor: default; }
.ph-memo--start { display: flex; align-items: center; justify-content: center; min-height: 56px; border-style: dashed; color: var(--ph-ink); font: inherit; font-weight: 800; width: calc(100% - 2 * var(--ph-pad) - 4px); cursor: pointer; }

/* the notes under it: one tile, newest first */
.ph-notes__add {
  flex: none; display: inline-flex; align-items: center; height: 48px; margin: -4px 0; padding: 0; border: 0; background: none;
  color: var(--ph-ink); font: inherit; font-size: var(--fs-12); font-weight: 800; cursor: pointer;
}
.ph-notes__add > span {
  display: inline-flex; align-items: center; gap: 4px; height: 40px; padding: 0 12px 0 10px; box-sizing: border-box;
  border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r-btn); background: var(--ph-card); box-shadow: var(--ph-lift);
}
.ph-notes__add svg { width: 20px; height: 20px; stroke-width: 2.6; flex: none; }
.ph-notes__add:active > span { transform: translate(3px, 3px); box-shadow: var(--ph-lift-press); }
.ph-noteitem { position: relative; padding: 12px; }
.ph-noteitem__meta { display: flex; gap: 4px; margin: 0 0 2px; font-size: var(--fs-8); line-height: 16px; color: var(--ph-ink-2); font-weight: 600; }
.ph-noteitem__meta b { color: var(--ph-ink); font-weight: 800; }
.ph-noteitem__body { white-space: pre-wrap; overflow-wrap: anywhere; font-size: var(--fs-12); font-weight: 500; line-height: 20px; text-wrap: pretty; }
.ph-noteitem__body.is-folded { display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden; }
.ph-noteitem__more { padding: 4px 0 0; }
.ph-noteitem--pinned { background: var(--ph-accent-soft); }
.ph-noteitem__kicker { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; font-size: var(--fs-6); font-weight: 800; color: var(--ph-ink); }
.ph-noteitem__kicker svg { width: 15px; height: 15px; }
.ph-note {
  display: flex; align-items: center; gap: 12px; width: calc(100% - 2 * var(--ph-pad) - 4px); margin: 0 calc(var(--ph-pad) + 4px) 24px var(--ph-pad);
  padding: 12px; border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r); background: var(--ph-card); box-shadow: var(--ph-lift);
  text-align: left; font: inherit; font-size: var(--fs-10); font-weight: 700; color: var(--ph-ink); cursor: pointer;
}
.ph-note svg { width: 20px; height: 20px; flex: none; stroke-width: 2.4; }
.ph-note--warning { padding-left: 24px; background: linear-gradient(90deg, transparent 12px, var(--ph-edge) 12px 14px, transparent 14px), var(--ph-tape-fine) left / 12px 100% no-repeat, var(--ph-card); }
.ph-note--critical { background: var(--ph-danger); color: var(--ph-danger-ink); }

/* a request's track: done, now, still to come */
.ph-track { list-style: none; margin: 0; padding: 16px 12px 8px; }
.ph-track__step { position: relative; display: flex; gap: 12px; padding-bottom: 16px; }
.ph-track__step:not(:last-child)::after {
  content: ""; position: absolute; left: 11px; top: 26px; bottom: 2px; width: 2px; background: var(--ph-fill-2);
}
.ph-track__step--done:not(:last-child)::after { background: var(--ph-solid); }
.ph-track__dot {
  width: 24px; height: 24px; flex: none; border-radius: 50%; display: grid; place-items: center;
  border: var(--ph-line) solid var(--ph-fill-2); background: var(--ph-card); color: var(--ph-solid-ink);
}
.ph-track__dot svg { width: 14px; height: 14px; stroke-width: 3; }
.ph-track__step--done .ph-track__dot { background: var(--ph-solid); border-color: var(--ph-solid); }
.ph-track__step--now .ph-track__dot { border-color: var(--ph-ink); background: var(--ph-accent); }
.ph-track__step--bad .ph-track__dot { background: var(--ph-danger); border-color: var(--ph-danger); color: var(--ph-danger-ink); }
.ph-track__text { display: flex; flex-direction: column; padding-top: 2px; }
.ph-track__label { font-size: var(--fs-12); font-weight: 800; }
.ph-track__step--todo .ph-track__label { color: var(--ph-ink-2); font-weight: 600; }
.ph-track__step--bad .ph-track__label { color: var(--ph-danger-text); }
.ph-track__detail { font-size: var(--fs-8); font-weight: 600; color: var(--ph-ink-2); }

/* the bar of buttons a request's decisions sit in - the desk's own buttons,
   drawn for a thumb; "Open" is the row above it on a phone */
.ph-actionbar {
  position: sticky; bottom: calc(var(--ph-tab-h) + env(safe-area-inset-bottom) + 28px); z-index: var(--z-raised);
  margin: 0 calc(var(--ph-pad) + 4px) 0 var(--ph-pad); padding: 12px; border-radius: var(--ph-r);
  border: var(--ph-line) solid var(--ph-edge); background: var(--ph-card); box-shadow: var(--ph-lift);
}
.ph-actionbar .row { display: flex; gap: 12px; flex-wrap: nowrap; }
.ph-actionbar [data-view-order] { display: none; }
.ph-actionbar .btn { flex: 1; }

/* an item's figures */
.ph-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin: 0 calc(var(--ph-pad) + 4px) 24px var(--ph-pad); }
.ph-stat {
  display: flex; flex-direction: column; gap: 2px; padding: 12px; min-width: 0;
  border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r); background: var(--ph-card); box-shadow: var(--ph-lift);
}
.ph-stat__v { font-family: var(--ph-font-display); font-size: var(--fs-14); font-weight: 400; line-height: 1.2; font-variant-numeric: tabular-nums; overflow-wrap: break-word; }
.ph-stat__v small { font-family: var(--ph-font); font-size: var(--fs-6); font-weight: 700; color: var(--ph-ink-2); }
.ph-stat__l { font-size: var(--fs-6); font-weight: 700; color: var(--ph-ink-2); }
.ph-stat--warning { padding-top: 20px; position: relative; overflow: hidden; }
.ph-stat--warning::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 8px; background: var(--ph-tape-fine); border-bottom: var(--ph-line) solid var(--ph-edge); }
.ph-stat--critical .ph-stat__v { color: var(--ph-danger-text); }
.ph-stat--good .ph-stat__v { color: var(--ph-ok); }

/* the Inbox's steps */
.ph-steps { padding: 0 calc(var(--ph-pad) + 4px) 0 var(--ph-pad); }
.ph-stepgroup { margin-bottom: 24px; }
.ph-stepgroup__title {
  display: flex; align-items: center; gap: 8px; margin: 0 0 12px; font-family: var(--ph-font-display); font-size: var(--fs-18); font-weight: 400; line-height: 24px;
}
.ph-stepgroup__n {
  min-width: 24px; height: 24px; padding: 0 8px; border-radius: var(--ph-r-chip); display: inline-grid; place-items: center;
  background: var(--ph-solid); color: var(--ph-solid-ink); font-family: var(--ph-font); font-size: var(--fs-8); font-weight: 800;
}
.ph-step {
  position: relative; display: block; margin-bottom: 12px; padding: 12px; border-radius: var(--ph-r); overflow: hidden;
  border: var(--ph-line) solid var(--ph-edge); background: var(--ph-card); box-shadow: var(--ph-lift); color: var(--ph-ink); text-decoration: none;
}
/* waiting on YOU: the tape down its edge */
.ph-step--mine { padding-left: 24px; }
.ph-step--mine::before { content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 10px; background: var(--ph-tape-fine); border-right: var(--ph-line) solid var(--ph-edge); }
.ph-step--queue { padding-left: 24px; }
.ph-step--queue::before { content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 10px; background: var(--ph-fill-2); border-right: var(--ph-line) solid var(--ph-edge); }
.ph-step__top { display: flex; justify-content: space-between; gap: 12px; font-size: var(--fs-6); font-weight: 800; font-stretch: 112.5%; color: var(--ph-ink-2); text-transform: uppercase; letter-spacing: .08em; }
.ph-step__amt { color: var(--ph-ink); text-transform: none; letter-spacing: 0; font-variant-numeric: tabular-nums; }
.ph-step__title { margin-top: 4px; font-size: var(--fs-16); font-weight: 800; line-height: 1.25; }
.ph-step__detail { margin-top: 2px; font-size: var(--fs-10); font-weight: 600; color: var(--ph-ink-2); }
.ph-step__meta { margin-top: 8px; font-size: var(--fs-8); font-weight: 600; color: var(--ph-ink-2); }
.ph-step__acts { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 12px; }
.ph-step__acts .row { display: contents; }
.ph-step__acts .btn { flex: 1 1 auto; }
.ph-step__acts [data-view-order] { flex: 0 1 auto; }

/* More */
.ph-profile {
  display: flex; align-items: center; gap: 16px; margin: 0 calc(var(--ph-pad) + 4px) 24px var(--ph-pad); padding: 12px;
  border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r); background: var(--ph-card); box-shadow: var(--ph-lift);
}
.ph-profile .ph-avatar { box-shadow: none; }
.ph-profile__who { display: flex; flex-direction: column; min-width: 0; }
.ph-profile__name { font-family: var(--ph-font-display); font-size: var(--fs-18); font-weight: 400; }
.ph-profile__role { font-size: var(--fs-10); font-weight: 700; color: var(--ph-ink-2); }
.ph-profile__email { font-size: var(--fs-8); font-weight: 600; color: var(--ph-ink-2); overflow-wrap: anywhere; }
.ph-colophon { text-align: center; font-size: var(--fs-6); font-weight: 700; color: var(--ph-ink-2); margin: 4px 0 0; }
/* "Powered by Argus" under the colophon, at the foot of More (2026-10-12) */
.argus-tag--phone { display: flex; justify-content: center; margin: 12px 0 4px; color: var(--ph-ink-2); }
.argus-tag--phone .argus-mark { background: var(--ph-argus-ink); }
.ph-row--control { padding-bottom: 6px; }
.ph-steps-list { margin: 0 0 12px; padding-left: 20px; line-height: 1.6; font-size: var(--fs-12); }

/* empty states */
.ph-empty { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 8px; padding: 40px 28px; color: var(--ph-ink-2); }
.ph-empty__icon svg { width: 44px; height: 44px; stroke-width: 1.8; color: var(--ph-ink); }
.ph-empty__title { font-family: var(--ph-font-display); font-size: var(--fs-16); font-weight: 400; color: var(--ph-ink); }
.ph-empty__text { margin: 0; font-size: var(--fs-10); font-weight: 600; max-width: 30ch; }
.ph-empty .ph-btn { margin-top: 12px; }

/* ── buttons the phone screens draw: splash tiles too ─────────────────── */
.ph-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 48px; padding: 0 20px;
  border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r-btn); background: var(--ph-card); box-shadow: var(--ph-lift);
  color: var(--ph-ink); font: inherit; font-size: var(--fs-12); font-weight: 800; cursor: pointer;
  transition: transform .08s var(--ease), box-shadow .08s var(--ease);
}
.ph-btn svg { width: 21px; height: 21px; stroke-width: 2.4; }
.ph-btn--primary { background: var(--ph-accent); color: var(--ph-accent-ink); font-family: var(--ph-font-display); font-weight: 400; }
.ph-btn--tinted { background: var(--ph-card); color: var(--ph-ink); }
.ph-btn--danger { background: var(--ph-danger); color: var(--ph-danger-ink); }
.ph-btn--big { flex-direction: column; min-height: 92px; gap: 6px; border-radius: var(--ph-r-tile); font-size: var(--fs-16); }
.ph-btn--big svg { width: 32px; height: 32px; }
.ph-btn:active { transform: translate(3px, 3px); box-shadow: var(--ph-lift-press); }
.ph-btn[disabled] { opacity: .45; box-shadow: none; transform: none; }
.ph-link {
  min-height: 44px; border: 0; background: none; padding: 6px; font: inherit; font-size: var(--fs-12); font-weight: 800; color: var(--ph-ink);
  text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 3px; cursor: pointer;
}
.ph-link--danger { color: var(--ph-danger-text); }

/* ── the tab bar: D's black floating bar, with I's lights. A dim lamp over
   every tab; the one you are on is lit in the accent, and its tab is the
   accent block. It tucks a little smaller while you scroll down. ──────── */
.ph-tabs {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--z-topbar);
  padding: 0 calc(var(--ph-pad) + env(safe-area-inset-right)) calc(env(safe-area-inset-bottom) + 16px) calc(var(--ph-pad) + env(safe-area-inset-left));
  pointer-events: none;
}
.ph-tabs__inner {
  pointer-events: auto; display: flex; gap: 4px; height: var(--ph-tab-h); padding: 16px 4px 4px; max-width: 520px; margin: 0 auto;
  box-sizing: border-box; overflow: hidden; border-radius: var(--ph-r-nav); background: var(--ph-nav);
  box-shadow: 0 0 0 var(--ph-line) var(--ph-nav-edge);   /* an edge, not a border: the bar keeps its measure */
  transition: transform var(--dur-3) var(--ease-out);
}
.ph-tab {
  position: relative; flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  border-radius: var(--ph-r-btn); color: var(--ph-nav-ink); text-decoration: none;
  font-size: var(--fs-6); font-weight: 700; line-height: 14px;
}
.ph-tab:hover { text-decoration: none; }
/* the lamp over the tab */
.ph-tab::before {
  content: ""; position: absolute; top: -12px; left: calc(50% - 4px); width: 8px; height: 8px; border-radius: 50%;
  background: var(--ph-led-off); box-shadow: inset 0 1px 2px rgba(0, 0, 0, .8), 0 0 0 1px var(--ph-led-ring);
}
.ph-tab__icon { position: relative; display: grid; place-items: center; }
.ph-tab__icon svg { width: 24px; height: 24px; stroke-width: 2; }
.ph-tab__label { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ph-tab.is-active { background: var(--ph-accent); color: var(--ph-accent-ink); font-weight: 800; }
.ph-tab.is-active svg { stroke-width: 2.4; }
.ph-tab.is-active::before {
  background: radial-gradient(circle at 40% 35%, rgba(255, 255, 255, .95) 0 1px, transparent 2.5px), var(--ph-accent);
  box-shadow: 0 0 3px 1px var(--ph-accent), 0 0 8px 2px var(--ph-accent);
}
.ph-tab__badge {
  position: absolute; top: -6px; left: calc(100% - 8px); min-width: 20px; height: 20px; padding: 0 4px; box-sizing: border-box; border-radius: 10px;
  display: grid; place-items: center; border: var(--ph-line) solid var(--ph-nav); background: var(--ph-accent); color: var(--ph-accent-ink);
  font-size: var(--fs-6); font-weight: 800; line-height: 16px; font-variant-numeric: tabular-nums;
}
.ph-tab__badge--loud { background: var(--ph-danger); color: var(--ph-danger-ink); }
.ph-tab.is-active .ph-tab__badge { border-color: var(--ph-accent); }
.ph-tab__badge[hidden] { display: none; }
.ph-tabs.is-min .ph-tabs__inner { transform: scale(.94) translateY(6px); }

/* ── banners, the uploads pill, the save status, pull to refresh ──────── */
.ph-banner {
  display: flex; align-items: flex-start; gap: 12px; margin: 4px calc(var(--ph-pad) + 4px) 16px var(--ph-pad); padding: 12px;
  border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r); background: var(--ph-card);
  color: var(--ph-ink); font-size: var(--fs-8); font-weight: 600; line-height: 1.4;
}
.ph-banner svg { width: 20px; height: 20px; flex: none; color: var(--ph-ink); stroke-width: 2.4; }
.ph-banner--offline { padding-left: 24px; background: linear-gradient(90deg, transparent 10px, var(--ph-edge) 10px 12px, transparent 12px), var(--ph-tape-fine) left / 10px 100% no-repeat, var(--ph-card); font-weight: 700; }
.ph-uploads {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: calc(var(--ph-tab-h) + env(safe-area-inset-bottom) + 32px);
  z-index: var(--z-topbar); display: inline-flex; align-items: center; gap: 8px; max-width: calc(100% - 32px);
  padding: 8px 16px; min-height: 44px; border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r-btn);
  background: var(--ph-card); box-shadow: var(--ph-lift); color: var(--ph-ink);
  font: inherit; font-size: var(--fs-8); font-weight: 800; cursor: pointer;
}
.ph-uploads svg { width: 18px; height: 18px; color: var(--ph-ink); flex: none; }
.ph-uploads span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ph-uploads.is-bad { background: var(--ph-danger); color: var(--ph-danger-ink); }
.ph-uploads.is-bad svg { color: var(--ph-danger-ink); }
.ph-uploads[hidden] { display: none; }
.ph-sync:empty { display: none; }
.ph-sync {
  position: fixed; top: calc(env(safe-area-inset-top) + 56px); left: 50%; transform: translateX(-50%); z-index: var(--z-topbar);
  padding: 4px 12px; border-radius: var(--ph-r-chip); font-size: var(--fs-6); font-weight: 800;
  background: var(--ph-solid); color: var(--ph-solid-ink);
}
.ph-sync[data-state="error"] { background: var(--ph-danger); color: var(--ph-danger-ink); }
.ph-ptr {
  position: fixed; left: 50%; top: calc(env(safe-area-inset-top) + var(--ph-bar-h)); z-index: var(--z-topbar);
  width: 36px; height: 36px; margin-left: -18px; border-radius: 50%; display: grid; place-items: center;
  border: var(--ph-line) solid var(--ph-edge); background: var(--ph-card);
  transform: translateY(calc(var(--pull, 0px) - 44px)); opacity: 0; pointer-events: none;
}
.ph-ptr.is-pulling, .ph-ptr.is-loading { opacity: 1; }
.ph-ptr.is-loading { transform: translateY(16px); transition: transform var(--dur-2) var(--ease); }
.ph-ptr__spin {
  width: 18px; height: 18px; border-radius: 50%; border: 3px solid var(--ph-fill-2); border-top-color: var(--ph-ink);
}
.ph-ptr.is-armed .ph-ptr__spin { border-color: var(--ph-accent); border-top-color: var(--ph-ink); }
.ph-ptr.is-loading .ph-ptr__spin { animation: ph-spin .8s linear infinite; }
@keyframes ph-spin { to { transform: rotate(360deg); } }

/* ── sheets: every dialog in the app, from the bottom ───────────────────
   The same modal() and the same rule - only Cancel or the X closes it - but
   it rises from the bottom, as a phone's sheets do: white paper with an ink
   edge across the top. No grabber: a grabber says "drag me away", and these
   never go that way. A form with a list in it (modal--wide) takes the whole
   height. */
/* The backdrop is a grid: its one row is pinned to the screen, or a tall form
   sized the row to itself and the sheet ran off the bottom of a short phone
   (790px on an iPhone SE's 667 - found by tools/phone_check.html). */
:root[data-ui="phone"] .modal-backdrop {
  padding: calc(env(safe-area-inset-top) + 10px) 0 0; align-items: end; overflow: hidden;
  grid-template-rows: minmax(0, 1fr);
}
:root[data-ui="phone"] .modal,
:root[data-ui="phone"] .modal--wide,
:root[data-ui="phone"] .modal--xwide {
  width: 100%; max-width: 100%; height: auto; max-height: 100%;
  border: var(--ph-line) solid var(--ph-edge); border-bottom: 0;
  border-radius: var(--ph-r-sheet) var(--ph-r-sheet) 0 0; background: var(--ph-card); color: var(--ph-ink);
  animation: ph-sheet var(--dur-3) var(--ease-out);
}
:root[data-ui="phone"] .modal--wide, :root[data-ui="phone"] .modal--xwide { height: 100%; }
:root[data-ui="phone"] .modal-backdrop.is-closing .modal { animation: ph-sheet-away .18s var(--ease) forwards; }
@keyframes ph-sheet { from { transform: translateY(100%); } }
@keyframes ph-sheet-away { to { transform: translateY(100%); } }
:root[data-ui="phone"] .modal__head {
  display: grid; grid-template-columns: 44px minmax(0, 1fr) 44px; align-items: center; min-height: 60px;
  padding: 8px calc(12px + env(safe-area-inset-right)) 8px calc(12px + env(safe-area-inset-left));
  border-bottom: var(--ph-line) solid var(--ph-sep);
}
:root[data-ui="phone"] .modal__head .spacer { display: none; }
:root[data-ui="phone"] .modal__title {
  grid-column: 2; text-align: center; font-family: var(--ph-font-display); font-size: var(--fs-16); font-weight: 400; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
:root[data-ui="phone"] .modal__head [data-close] {
  position: relative; grid-column: 3; justify-self: end; width: 36px; height: 36px; border-radius: var(--ph-r-btn);
  border: var(--ph-line) solid var(--ph-edge); background: var(--ph-card); color: var(--ph-ink);
}
:root[data-ui="phone"] .modal__head [data-close] svg { width: 15px; height: 15px; stroke-width: 3; }
:root[data-ui="phone"] .modal__body { padding: 16px calc(16px + env(safe-area-inset-right)) 16px calc(16px + env(safe-area-inset-left)); }
:root[data-ui="phone"] .modal__foot {
  gap: 12px; background: var(--ph-card);
  padding: 12px calc(20px + env(safe-area-inset-right)) calc(16px + env(safe-area-inset-bottom)) calc(16px + env(safe-area-inset-left));
  border-top: var(--ph-line) solid var(--ph-sep); border-radius: 0;
}
:root[data-ui="phone"] .modal__foot .btn { flex: 1; height: 52px; font-size: var(--fs-12); }
:root[data-ui="phone"] .modal__foot .btn[hidden] { display: none; }
/* the photo viewer: the whole screen, dark, like Photos */
:root[data-ui="phone"] .modal--viewer { height: 100%; border: 0; border-radius: 0; background: var(--sidebar-2); color: #F4F7FB; }
:root[data-ui="phone"] .modal-backdrop:has(.modal--viewer) { padding-top: 0; }

/* the desk's controls, inside a phone - splash tiles, Archivo, thumb-sized */
:root[data-ui="phone"] .btn {
  height: 48px; border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r-btn);
  background: var(--ph-card); box-shadow: var(--ph-lift); color: var(--ph-ink);
  font-family: var(--ph-font); font-size: var(--fs-10); font-weight: 800;
}
:root[data-ui="phone"] .btn:active { transform: translate(3px, 3px); box-shadow: var(--ph-lift-press); }
:root[data-ui="phone"] .btn--sm { position: relative; height: 40px; font-size: var(--fs-8); }
:root[data-ui="phone"] .btn--icon { width: 48px; padding: 0; }
:root[data-ui="phone"] .btn--ghost { box-shadow: none; border-color: transparent; background: transparent; }
:root[data-ui="phone"] .btn--primary { background: var(--ph-accent); border-color: var(--ph-edge); color: var(--ph-accent-ink); }
:root[data-ui="phone"] .btn--danger { background: var(--ph-danger); border-color: var(--ph-edge); color: var(--ph-danger-ink); }
:root[data-ui="phone"] .btn[disabled] { box-shadow: none; transform: none; }
:root[data-ui="phone"] .input, :root[data-ui="phone"] .select {
  height: 48px; border: var(--ph-line) solid var(--ph-edge); border-radius: 10px; font-size: var(--fs-16); background: var(--ph-card); color: var(--ph-ink);
}
:root[data-ui="phone"] .textarea { border: var(--ph-line) solid var(--ph-edge); border-radius: 10px; font-size: var(--fs-16); background: var(--ph-card); color: var(--ph-ink); }
:root[data-ui="phone"] .input:focus, :root[data-ui="phone"] .select:focus, :root[data-ui="phone"] .textarea:focus { outline: none; box-shadow: 0 0 0 3px var(--ph-accent); }
:root[data-ui="phone"] .ph-textarea { min-height: 120px; line-height: 22px; }
:root[data-ui="phone"] .field__label { font-weight: 800; color: var(--ph-ink); }

/* the steps inside a sheet: new request, take photos */
.ph-flow__step .ph-search { margin: 0 0 12px; }
.ph-flow .ph-list--flush { margin: 0 -16px; }
.ph-shoot__job {
  display: flex; align-items: center; gap: 8px; margin-bottom: 16px; padding: 12px; border-radius: var(--ph-r);
  border: var(--ph-line) solid var(--ph-edge); background: var(--ph-card); font-size: var(--fs-10); font-weight: 700;
}
.ph-shoot__job svg { width: 20px; height: 20px; flex: none; color: var(--ph-ink); }
.ph-shoot__job span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ph-shoot__buttons { display: grid; grid-template-columns: 1.4fr 1fr; gap: 12px; margin-bottom: 16px; padding-right: 4px; }
.ph-shoot__hint { text-align: center; font-size: var(--fs-10); font-weight: 600; color: var(--ph-ink-2); margin: 8px 0 0; }
.ph-shoot__out { margin-top: 12px; }
/* the tray's questions (2026-10-14): a photo one of them is about wears the
   tape along its foot, like everything else waiting on someone */
.ph-shoot__checks { display: grid; gap: 10px; margin-top: 12px; }
.ph-shoot__checks:empty { display: none; }
.ph-shot.is-alike .ph-shot__face::after, .ph-shot.is-elsewhere .ph-shot__face::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 8px; background: var(--ph-tape-fine);
}
.ph-shots { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.ph-shot { position: relative; aspect-ratio: 1; border-radius: var(--ph-r); overflow: hidden; background: var(--ph-slot); }
.ph-shot::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: var(--ph-sunk); pointer-events: none; }
.ph-shot__face { position: absolute; inset: 0; display: grid; place-items: center; font-weight: 800; color: var(--ph-ink-2); }
.ph-shot__face img { width: 100%; height: 100%; object-fit: cover; }
.ph-shot.is-busy .ph-shot__face::after {
  content: ""; width: 22px; height: 22px; border-radius: 50%; border: 3px solid var(--ph-fill-2); border-top-color: var(--ph-ink);
  animation: ph-spin .8s linear infinite;
}
.ph-shot__drop {
  position: absolute; top: 0; right: 0; z-index: var(--z-raised); width: 44px; height: 44px; border: 0; border-radius: 50%; isolation: isolate;
  display: grid; place-items: center; background: none; color: #FFFFFF; cursor: pointer;
}
.ph-shot__drop svg { width: 14px; height: 14px; stroke-width: 3; }
.ph-shot__drop::before {
  content: ""; position: absolute; inset: 7px; z-index: var(--z-behind); border-radius: 50%; background: rgba(11, 11, 11, .7);
}
.ph-flow__confirm {
  display: flex; flex-direction: column; gap: 8px; padding: 16px; border-radius: var(--ph-r);
  border: var(--ph-line) solid var(--ph-edge); background: var(--ph-accent-soft);
}
.ph-flow__confirm p { margin: 0; color: var(--ph-ink); font-size: var(--fs-10); font-weight: 600; }
.ph-flow__confirm-btns { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 8px; padding-right: 4px; }
.ph-flow__confirm[hidden] { display: none; }
.ph-row--pick.is-picked { background: var(--ph-accent-soft); }
/* a line to buy, tinted the whole line (2026-10-14) */
.ph-row--buy { background: var(--ph-accent-soft); }
.ph-row--add { margin-bottom: 12px; border: var(--ph-line) dashed var(--ph-edge); border-radius: var(--ph-r); background: var(--ph-card); }
.ph-row--review { align-items: flex-start; }
.ph-select { height: 40px; margin-top: 6px; font-size: var(--fs-10); }
.ph-buyform {
  display: flex; flex-direction: column; gap: 12px; margin-bottom: 16px; padding: 16px; border-radius: var(--ph-r);
  border: var(--ph-line) solid var(--ph-edge); background: var(--ph-fill);
}
.ph-buyform__row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.ph-buyform__btns { display: grid; grid-template-columns: 1fr 1.4fr; gap: 12px; padding-right: 4px; }
.ph-buyform[hidden] { display: none; }
/* Who is at a meeting (2026-10-14): initials in ink beside a dot in the
   person's calendar colour (--who comes from the calendar's data) */
.ph-who-list { display: inline-flex; align-items: center; gap: 6px; flex-wrap: nowrap; }
.ph-who {
  --who: var(--ph-ink-2);                  /* overridden per person, inline */
  display: inline-flex; align-items: center; gap: 3px; font-size: var(--fs-4); font-weight: 800;
  letter-spacing: .03em; color: var(--ph-ink-2); line-height: 1;
}
.ph-who::before { content: ""; width: 9px; height: 9px; border-radius: 50%; background: var(--who); flex: none; }
.ph-who--more::before { display: none; }
/* "We stock something like this" (2026-10-14): it needs an answer, so it wears
   the tape like everything else waiting on someone; once answered, ink. */
.ph-alike {
  display: flex; flex-direction: column; gap: 8px; padding: 12px; border-radius: var(--ph-r);
  border: var(--ph-line) solid var(--ph-edge); background: var(--ph-card); position: relative; overflow: hidden;
  font-size: var(--fs-9); color: var(--ph-ink);
}
.ph-alike::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 8px; background: var(--ph-tape-fine); }
.ph-alike > * { margin-left: 6px; }
.ph-alike .ph-row { border-radius: var(--ph-r); border: var(--ph-line) solid var(--ph-edge); background: var(--ph-fill); }
.ph-alike--said { flex-direction: row; align-items: center; background: var(--ph-fill); }
.ph-alike--said::before { display: none; }
.ph-alike--said svg { width: 18px; height: 18px; flex: none; }
.ph-stepper { display: inline-flex; align-items: center; gap: 4px; padding: 2px; border-radius: var(--ph-r-btn); border: var(--ph-line) solid var(--ph-edge); background: var(--ph-card); }
.ph-stepper__btn, .ph-addbtn {
  position: relative; width: 44px; height: 44px; border: 0; border-radius: 10px; display: grid; place-items: center;
  background: var(--ph-fill); color: var(--ph-ink); cursor: pointer;
}
.ph-stepper__btn svg, .ph-addbtn svg { width: 18px; height: 18px; stroke-width: 2.8; }
.ph-stepper__btn[disabled] { opacity: .35; }
.ph-stepper__n { min-width: 24px; text-align: center; font-weight: 800; font-variant-numeric: tabular-nums; }
.ph-addbtn { border: var(--ph-line) solid var(--ph-edge); background: var(--ph-accent); color: var(--ph-accent-ink); }   /* the plus stays dark on the yellow in dark mode too */
/* Most ordered on a new request (2026-10-07): "+N" adds the usual quantity -
   the yellow add button, wide enough for its number, in the display face */
.ph-addbtn--usual {
  width: auto; min-width: 56px; padding: 0 12px; color: var(--ph-accent-ink);
  font-family: var(--ph-font-display); font-size: var(--fs-12); font-weight: 400; font-variant-numeric: tabular-nums;
}
.ph-addbtn--usual.is-on { background: var(--ph-card); color: var(--ph-ink); }
.ph-quick { margin-bottom: 12px; }
.ph-quick[hidden] { display: none; }
.ph-pickhead {
  margin: 4px 2px 8px; color: var(--ph-ink-2); font-size: var(--fs-4); font-weight: 800;
  letter-spacing: .05em; line-height: 16px; text-transform: uppercase;
}
/* Gloved hands (2026-10-06, the construction-app research: "one-handed use,
   weak cellular signal, direct sunlight, and gloved hands"): a control drawn
   smaller than 44px still takes a 44px press - its touch area reaches past its
   edges, invisibly. The sheet's X is one of only two ways out of a sheet. */
.ph-seg__btn::after,
:root[data-ui="phone"] .btn--sm::after,
:root[data-ui="phone"] .modal__head [data-close]::after {
  content: ""; position: absolute; left: 50%; top: 50%; width: max(100%, 44px); height: max(100%, 44px);
  transform: translate(-50%, -50%);
}

.ph-total { display: flex; justify-content: space-between; align-items: baseline; padding: 12px 2px 0; border-top: var(--ph-line) solid var(--ph-sep); color: var(--ph-ink-2); font-weight: 700; }
.ph-total strong { font-family: var(--ph-font-display); font-size: var(--fs-18); font-weight: 400; color: var(--ph-ink); font-variant-numeric: tabular-nums; }
.ph-total:empty { display: none; }
/* phone to-dos (2026-10-22): the round box ticks it, the rest of the row opens it */
.ph-todo { padding: 4px 4px 4px 4px; gap: 0; }
.ph-todo__tick {
  flex: none; display: grid; place-items: center; width: 44px; height: 44px; margin-right: 4px; padding: 0;
  border: 0; background: none; color: transparent; cursor: pointer;
}
.ph-todo__tick svg {
  width: 26px; height: 26px; padding: 3px; box-sizing: border-box; stroke-width: 3;
  border: var(--ph-line) solid var(--ph-edge); border-radius: 50%; background: var(--ph-card);
}
.ph-todo__tick[aria-pressed="true"] { color: var(--ph-accent-ink); }
.ph-todo__tick[aria-pressed="true"] svg { background: var(--ph-accent); }
.ph-todo__tick:disabled { cursor: default; opacity: .5; }
.ph-todo__tick:active svg { transform: scale(.9); }
.ph-todo__open {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; min-height: 44px; padding: 8px 8px 8px 0;
  border: 0; background: none; font: inherit; color: var(--ph-ink); text-align: left; cursor: pointer;
}
.ph-todo__open:active { background: var(--ph-card-press); }
.ph-todo.is-done .ph-row__title { color: var(--ph-ink-2); text-decoration: line-through; }
.ph-todo.is-flash { background: var(--ph-accent-soft); }
.ph-todo__read { margin: 0; font-size: var(--fs-16); font-weight: 700; color: var(--ph-ink); overflow-wrap: anywhere; }
/* phone checklist (2026-10-22): a to-do's row - the round box ticks a yes/no
   line; a line that wants words shows them, its box only a mark */
.ph-ck.is-done .ph-row__title { text-decoration: none; color: var(--ph-ink-2); }
.ph-ck__mark { flex: none; display: grid; place-items: center; width: 44px; height: 44px; margin-right: 4px; color: transparent; }
.ph-ck__mark svg {
  width: 26px; height: 26px; padding: 3px; box-sizing: border-box; stroke-width: 3;
  border: var(--ph-line) dashed var(--ph-sep); border-radius: 50%;
}
.ph-ck__mark.is-on { color: var(--ph-accent-ink); }
.ph-ck__mark.is-on svg { border-style: solid; border-color: var(--ph-edge); background: var(--ph-accent); }
.ph-ck__said { display: block; margin-top: 2px; font-size: var(--fs-14); font-weight: 600; color: var(--ph-ink);
  white-space: pre-wrap; overflow-wrap: anywhere; }
.ph-ck__more {
  display: flex; align-items: center; gap: 8px; width: 100%; min-height: 44px; padding: 0 16px 0 56px;
  border: 0; border-top: var(--ph-line) solid var(--ph-sep); background: none;
  font: inherit; font-size: var(--fs-14); font-weight: 700; color: var(--ph-ink); text-align: left; cursor: pointer;
}
.ph-ck__more svg { width: 16px; height: 16px; stroke-width: 3; flex: none; }
.ph-ck__more:active { background: var(--ph-card-press); }
.ph-ck__toggle { gap: 12px; min-height: 44px; font-size: var(--fs-16); }
.ph-ck__toggle input { width: 22px; height: 22px; }
.ph-ck__sheens { border: 0; padding: 0; margin: 0; }
.ph-ck__choices { display: flex; flex-wrap: wrap; gap: 8px; }
.ph-ck__choice { position: relative; }
.ph-ck__choice input { position: absolute; opacity: 0; pointer-events: none; }
.ph-ck__choice span {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 14px;
  border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r-btn); background: var(--ph-card);
  font-size: var(--fs-14); font-weight: 700; color: var(--ph-ink); cursor: pointer;
}
.ph-ck__choice input:checked + span { background: var(--ph-solid); color: var(--ph-solid-ink); }
.ph-ck__choice input:focus-visible + span { outline: 2px solid var(--ph-edge); outline-offset: 2px; }
.ph-sheet-list { display: flex; flex-direction: column; margin: -8px -16px; }
.ph-sheet-list__btn {
  display: flex; align-items: center; gap: 16px; min-height: 56px; padding: 0 20px; border: 0; background: none;
  font: inherit; font-size: var(--fs-16); font-weight: 700; color: var(--ph-ink); text-align: left; cursor: pointer;
}
.ph-sheet-list__btn + .ph-sheet-list__btn { border-top: var(--ph-line) solid var(--ph-sep); }
.ph-sheet-list__btn:active { background: var(--ph-card-press); }
.ph-sheet-list__icon svg { width: 22px; height: 22px; display: block; stroke-width: 2.2; }
.ph-sheet-list__btn--critical { color: var(--ph-danger-text); }
.ph-thumb { position: relative; width: 48px; height: 48px; border-radius: 8px; overflow: hidden; background: var(--ph-slot); display: block; }
.ph-thumb::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: var(--ph-sunk); pointer-events: none; }
.ph-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ph-row--outbox { align-items: flex-start; }
.ph-row--outbox .ph-row__sub { white-space: normal; }

/* ── messages: a slip at the top, ink on white, outlined ──────────────── */
:root[data-ui="phone"] .toasts {
  top: calc(env(safe-area-inset-top) + 8px); bottom: auto; align-items: center;
  left: calc(16px + env(safe-area-inset-left)); right: calc(20px + env(safe-area-inset-right));
}
:root[data-ui="phone"] .toast {
  max-width: 100%; border: var(--ph-line) solid var(--ph-edge); border-radius: var(--ph-r-btn); padding: 12px 16px;
  font-family: var(--ph-font); font-size: var(--fs-10); font-weight: 800; box-shadow: var(--ph-lift);
}
:root[data-ui="phone"] .toast:not(.toast--good):not(.toast--bad):not(.toast--warn) { background: var(--ph-card); color: var(--ph-ink); }

/* ── a desk page inside the phone: its own markup, a phone's margins ──── */
.ph-view--desk .view { padding: 0 var(--ph-pad) var(--sp-5); animation: none; }
.ph-view--desk .view__head { margin-bottom: var(--sp-3); }
.ph-view--desk .view__actions [data-help] { display: none; }

/* ── signing in, on a phone: the ink sign with a strip of tape under it ── */
:root[data-ui="phone"] .login { display: flex; flex-direction: column; min-height: calc(100 * var(--dvh)); background: var(--ph-bg); }
:root[data-ui="phone"] .login__brand {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 12px;
  min-height: calc(34 * var(--dvh)); color: var(--ph-sign-ink); background: var(--ph-sign);
  padding: calc(env(safe-area-inset-top) + 36px) calc(24px + env(safe-area-inset-right)) 44px calc(24px + env(safe-area-inset-left));
}
:root[data-ui="phone"] .login__brand::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 16px; background: var(--ph-tape);
  border-top: var(--ph-line) solid var(--ph-sign);
}
:root[data-ui="phone"] .login__brand .logo { max-width: 150px; }
:root[data-ui="phone"] .login__brand > div:not(.env-login), :root[data-ui="phone"] .login__brand [data-brandfoot] { display: none; }
/* "Powered by Argus" at the bottom of the phone's sign-in (2026-10-12). Every
   sign-in screen draws it beside the brand panel; `order` puts it after the
   form, and it rises into the form's safe-area padding rather than adding a
   second one under it. */
:root[data-ui="phone"] .argus-tag--login-foot {
  order: 1; display: flex; justify-content: center; color: var(--ph-ink-2); background: var(--ph-bg);
  margin-top: calc(-1 * env(safe-area-inset-bottom)); padding: 0 0 calc(env(safe-area-inset-bottom) + 24px);
}
:root[data-ui="phone"] .argus-tag--login-foot .argus-mark { background: var(--ph-argus-ink); }
:root[data-ui="phone"] .login__form {
  flex: 1; background: var(--ph-bg);
  padding: 24px calc(20px + env(safe-area-inset-right)) calc(env(safe-area-inset-bottom) + 24px) calc(20px + env(safe-area-inset-left));
}
:root[data-ui="phone"] .login__inner { max-width: 440px; margin: 0 auto; }
:root[data-ui="phone"] .login__inner h2 { font-family: var(--ph-font-display) !important; font-size: var(--fs-ph-title) !important; font-weight: 400 !important; }
:root[data-ui="phone"] .login .btn--primary { height: 52px; font-size: var(--fs-16); }

/* ── a phone on its side ──────────────────────────────────────────────────
   Short and wide (393px tall on an iPhone 15 Pro), so the chrome goes compact
   the way the phone's own does sideways: slimmer bars, each tab's label
   beside its icon, smaller large titles, lower sheet buttons. Every press
   stays 44px (tools/phone_check.html, the sideways device). */
@media (orientation: landscape) and (max-height: 500px) {
  /* the bar's lamp strip slims to 10px and it floats 8px up, so the tabs keep
     their 44px and the two bars stay under 30% of the screen: 48 + 58 + 8 */
  :root[data-ui="phone"] { --ph-tab-h: 58px; --ph-bar-h: 48px; --fs-ph-large: 28px; }
  .ph-tabs { padding-bottom: calc(env(safe-area-inset-bottom) + 8px); }
  .ph-tabs__inner { padding-top: 10px; }
  .ph-tab::before { top: -9px; }
  /* the gap is wide enough for a count badge (two digits, and its ring) to sit clear of the name */
  .ph-tab { flex-direction: row; gap: 14px; }
  .ph-tab__icon svg { width: 22px; height: 22px; }
  .ph-tab__label { font-size: var(--fs-8); }
  /* held by its RIGHT edge, so a two- or three-digit count grows back over the
     icon instead of across the name beside it (phone_check, 2026-10-08: the
     owner's Workflow reached 18 and its badge covered "Workflow") */
  .ph-tab__badge { top: -8px; left: auto; right: -10px; }
  :root[data-ui="phone"] .modal__head { min-height: 48px; padding-top: 2px; padding-bottom: 2px; }
  :root[data-ui="phone"] .modal__foot { padding-top: 8px; padding-bottom: calc(8px + env(safe-area-inset-bottom)); }
  :root[data-ui="phone"] .modal__foot .btn { height: 44px; }
  :root[data-ui="phone"] .ph-textarea { min-height: 88px; }
}

/* ── the test site, on a phone: the banner takes the notch, so the bar,
   the sheets and the messages all start below it ─────────────────────── */
:root[data-env="test"][data-ui="phone"] .ph-bar { top: var(--env-offset); padding-top: 0; }
:root[data-env="test"][data-ui="phone"] .modal-backdrop { top: var(--env-offset); padding-top: 10px; }
:root[data-env="test"][data-ui="phone"] .modal,
:root[data-env="test"][data-ui="phone"] .modal--wide,
:root[data-env="test"][data-ui="phone"] .modal--xwide { max-height: 100%; }
:root[data-env="test"][data-ui="phone"] .toasts { top: calc(var(--env-offset) + 8px); }
:root[data-env="test"][data-ui="phone"] .ph-ptr { top: calc(var(--env-offset) + var(--ph-bar-h)); }
:root[data-env="test"][data-ui="phone"] .login__brand { padding-top: 36px; }

/* ── Documents (2026-10-08) ── */
/* a document's name in the list opens its details: a link's colour, a button's keyboard */
.docs-link {
  background: none; border: 0; padding: 0; font: inherit; color: var(--link);
  cursor: pointer; text-align: left; overflow-wrap: anywhere;
}
.docs-link:hover { text-decoration: underline; }
.docs-list { list-style: none; margin: var(--sp-1) 0 0; padding: 0; }
.docs-list > li { padding: var(--sp-1) 0; border-bottom: 1px solid var(--hairline); }
.docs-list > li:last-child { border-bottom: 0; }
.docs-dl { display: grid; grid-template-columns: max-content 1fr; gap: var(--sp-1) var(--sp-4); margin: 0; }
.docs-dl dt { color: var(--ink-muted); font-size: var(--fs-7); }
.docs-dl dd { margin: 0; overflow-wrap: anywhere; }
/* the document beside its tags (2026-10-14, documents.js mountPreview): the
   pages on the left, the tags on the right, each scrolling on its own in a
   dialog the height of the window. Stacked below 900px, the page on top. */
.modal--doc { max-width: 1240px; height: calc(90 * var(--vh)); }
.modal--doc .modal__body { display: flex; min-height: 0; overflow: hidden; }
.doc-split {
  flex: 1; min-height: 0; display: grid; gap: var(--sp-5);
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); grid-template-rows: minmax(0, 1fr);
}
.doc-split__form { min-height: 0; overflow-y: auto; padding-right: 2px; }
/* ‹ 3 of 57 › beside the X: through the list it was opened from (2026-11-02) */
.doc-step { display: flex; align-items: center; gap: var(--sp-1); flex: none; }
.doc-step__count { font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--ink-2); font-size: var(--fs-13); min-width: 64px; text-align: center; }
/* before a file is chosen: the picker alone, at the old width */
.modal--doc.modal--doc-solo { max-width: 720px; height: auto; }
.modal--doc-solo .modal__body { display: block; overflow-y: auto; }
.doc-split--solo { display: block; }
.doc-split--solo > .doc-preview { display: none; }
.doc-preview {
  display: flex; flex-direction: column; min-height: 0; overflow: hidden;
  border: 1px solid var(--hairline); border-radius: var(--r-md); background: var(--surface-2);
}
.doc-preview__bar {
  display: flex; align-items: center; gap: var(--sp-2); min-height: 38px;
  padding: var(--sp-1) var(--sp-2); border-bottom: 1px solid var(--hairline); background: var(--surface);
}
.doc-preview__stage {
  flex: 1; min-height: 0; overflow: auto; padding: var(--sp-3);
  display: grid; place-items: start center;
}
/* a page is paper in either theme */
.doc-preview__page, .doc-preview__img { display: block; background: #fff; box-shadow: var(--shadow-md); }
.doc-preview__img { max-width: 100%; height: auto; }
.doc-preview__img.is-actual { max-width: none; }
.doc-preview__note { align-self: center; max-width: 38ch; margin: 0; text-align: center; color: var(--ink-muted); }
@media (max-width: 900px) {
  .modal--doc { height: auto; }
  .modal--doc .modal__body { display: block; overflow-y: auto; }
  .doc-split { display: flex; flex-direction: column; }
  .doc-split__form { overflow: visible; padding-right: 0; }
  .doc-preview { flex: none; }
  .doc-preview__stage { flex: none; height: calc(48 * var(--vh)); }
}
/* ...and on a phone the whole screen, as every dialog is there */
@media (max-width: 720px) {
  .modal--doc, .modal--doc.modal--doc-solo { max-width: 100%; height: calc(100 * var(--dvh)); }
}
/* A BANK LINE'S RECEIPT (2026-10-27, receipt-pane.js): its name and a step
   between receipts above the previewer. Beside a decision, or in a window of
   its own, it fills its column (--fill); anywhere else - the bank-line window,
   a phone's sheet - it is a block of its own height. */
.receipt-pane { display: flex; flex-direction: column; gap: var(--sp-2); min-height: 0; }
.receipt-pane[hidden] { display: none; }
.receipt-pane--fill { flex: 1; }
.receipt-pane > .doc-preview { flex: 1; }
.receipt-pane:not(.receipt-pane--fill) > .doc-preview { flex: none; }
.receipt-pane:not(.receipt-pane--fill) .doc-preview__stage { flex: none; height: calc(40 * var(--vh)); }
.receipt-pane__head { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.receipt-pane__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ── end Documents ── */

/* the Payment schedule's part payments, folded under their row (2026-10-22) */
.ms-pays summary { cursor: pointer; }
.ms-pays__list { display: grid; gap: 2px; margin: 4px 0 2px 16px; }
