/* The Bayocorp application shell.
 *
 * Ade's spec, 2026-09-01, after taking Groundplan apart: "a fixed black top
 * navigation bar, secondary white tab navigation, fixed left project sidebar
 * and a large white central workspace... generous white space, subtle grey
 * dividers, strong typography and very little visual decoration."
 *
 * And the warning that matters more than any of the layout: "do not redesign
 * this into one of those modern dashboards with 20 floating rounded cards
 * everywhere. The Groundplan screen works because it behaves like a
 * professional working tool." So: no shadows, no gradients, no card stacks.
 * Borders are 1px and grey. The only strong colour on the page is the yellow,
 * and it is only ever on a primary action, a selected state, or the project
 * tile.
 *
 * Yellow replaces Groundplan's green. #FFDE59 with BLACK text - never yellow
 * text on white, which is unreadable and is the one thing the brand rules
 * forbid outright.
 *
 * REVISED 2026-09-08: "the black colour should be more dominant in the branding
 * rather than white." The workspace quoted above is no longer white - see the
 * palette note below. Everything else in that spec stands unchanged: no
 * shadows, no gradients, no card stacks, 1px borders, and yellow only on the
 * one thing to press.
 */
/* ---- the palette ------------------------------------------------------------
 * THE DEFAULT IS BLACK. Ade, 2026-09-08: "the black colour should be more
 * dominant in the branding rather than white."
 *
 * That reverses his earlier "large white central workspace" on purpose, and the
 * black is the WEBSITE's black - #111111 for the page, #1e1e1e for a card - so
 * there is now one Bayocorp black across bayocorp.uk, the newsletter email and
 * every app, rather than three that nearly match. The layout did not change;
 * the surfaces did.
 *
 * WHAT "FOLLOWS THE MACHINE" MEANT, AND WHY IT STOPPED. Ade, 2026-09-02: "make
 * sure the UI had auto - black and white backgrounds like apple and mac", so
 * auto used to resolve to the machine's setting. Most laptops are in light
 * mode, so in practice the brand's black appeared only on the top bar. A brand
 * is not a per-machine preference: the default is black everywhere now, and
 * white is an explicit choice in the picker. theme.js changed with this.
 *
 * THE RULE THAT MAKES THIS MAINTAINABLE, unchanged: every colour is a token
 * defined HERE, on bare :root, and the light block below redefines ONLY tokens,
 * never a rule. A colour whose only definition lives inside one of these blocks
 * is missing in the other scheme, and nothing reports it - the element just
 * inherits something and looks nearly right.
 *
 * The nav bar is still the one thing that does not invert, and it now works
 * harder: on a white page #111111 read as chrome on its own; on a #111111 page
 * it would merge into it, so it goes one step darker with a hairline above the
 * content.
 */
:root{
  color-scheme:dark;

  /* surfaces */
  --page:#111111;
  --surface:#1e1e1e;
  --wash:#191919;
  --line:#2e2e2e;
  --line-soft:#242424;
  --line-strong:#3d3d3d;

  /* the black bar */
  --black:#111111;
  --nav:#0d0d0d;
  --nav-text:#ffffff;
  --nav-dim:#d9d9d9;
  --nav-hover:#242424;
  --nav-active:#2c2c2c;
  --nav-line:#333333;
  --nav-line-hover:#5a5a5a;

  /* text */
  --ink:#ececec;
  --body:#c9c9c9;
  --grey:#9d9d9d;
  --faint:#7d7d7d;
  --ghost:#5a5a5a;

  /* the one strong colour, and the only text colour that may sit on it */
  --yellow:#FFDE59;
  --yellow-deep:#e9c63d;
  --on-yellow:#111111;
  /* Yellow can never be TEXT on white - the brand rule - so anywhere a yellow
   * needs to be legible as type it uses this instead. */
  --yellow-ink:#f0d268;
  --yellow-wash:#2a2410;
  --yellow-wash-soft:#221e10;
  --yellow-line:#4a3f1a;

  /* status */
  --strong:#ececec;
  --on-strong:#111111;
  --good-bg:#16281a;
  --good-line:#2a4a2e;
  --good-ink:#8fd39a;
  --bad:#ff8a7a;
  --bad-soft:#ff8a7a;
  --bad-ink:#ff9d90;
  --bad-line:#5a2a24;
  --bad-wash:#2b1613;
  --good-dot:#5cb46e;

  --shade:rgba(0,0,0,.62);
  --radius:6px;
  --card-radius:12px;
  /* Ade, 2026-09-03: "make the font look like the font on the bayo corp logo"
     and "chill the font out as well a little bit". The wordmark on the logo is a
     rounded geometric lowercase with soft stroke ends - Poppins has flat ones
     and reads harder than the brand does. Quicksand is the closest thing to it
     on Google Fonts, and it tops out at 700 rather than 800, which does half
     the chilling on its own. */
  --font-display:'Quicksand',sans-serif;
  --font-body:'Inter',sans-serif;
}

/* WHITE IS AN EXPLICIT CHOICE NOW - the whole light palette, kept exactly as it
 * was, so nothing is lost by the default changing. It outranks the block above
 * on specificity (0,2,0 against 0,1,0), which is what lets the picker win
 * without an !important anywhere. */
:root[data-theme="light"]{
  color-scheme:light;

  /* surfaces */
  --page:#ffffff;
  --surface:#ffffff;
  --wash:#fafafa;
  --line:#e4e4e4;
  --line-soft:#f0f0f0;
  --line-strong:#c9c9c9;

  /* the black bar */
  --black:#111111;
  --nav:#111111;
  --nav-text:#ffffff;
  --nav-dim:#d9d9d9;
  --nav-hover:#1f1f1f;
  --nav-active:#242424;
  --nav-line:#333333;
  --nav-line-hover:#555555;

  /* text */
  --ink:#1a1a1a;
  --body:#3d3d3d;
  --grey:#6b6b6b;
  --faint:#9a9a9a;
  --ghost:#bbbbbb;

  /* the one strong colour, and the only text colour that may sit on it */
  --yellow:#FFDE59;
  --yellow-deep:#e9c63d;
  --on-yellow:#111111;
  /* Yellow can never be TEXT on white - the brand rule - so anywhere a yellow
   * needs to be legible as type it uses this instead. */
  --yellow-ink:#8a6d00;
  --yellow-wash:#fff8dd;
  --yellow-wash-soft:#fffbea;
  --yellow-line:#f0dfa0;

  /* status */
  --strong:#111111;
  --on-strong:#ffffff;
  --good-bg:#eaf6ea;
  --good-line:#cfe6cf;
  --good-ink:#1f5c1f;
  --bad:#b23a2e;
  --bad-soft:#e05252;
  --bad-ink:#8d2b20;
  --bad-line:#e0b4ad;
  --bad-wash:#fdf3f1;
  --good-dot:#3f8f4f;

  --shade:rgba(0,0,0,.45);
  --radius:6px;
  --card-radius:12px;
  /* Ade, 2026-09-03: "make the font look like the font on the bayo corp logo"
     and "chill the font out as well a little bit". The wordmark on the logo is a
     rounded geometric lowercase with soft stroke ends - Poppins has flat ones
     and reads harder than the brand does. Quicksand is the closest thing to it
     on Google Fonts, and it tops out at 700 rather than 800, which does half
     the chilling on its own. */
  --font-display:'Quicksand',sans-serif;
  --font-body:'Inter',sans-serif;
}

*{box-sizing:border-box}

/* `hidden` HAS TO WIN. The browser's own `[hidden]{display:none}` sits in the
 * UA stylesheet, so ANY author rule that sets `display` beats it - and
 * `.btn{display:inline-flex}` does exactly that. Every `hidden` button in this
 * app was therefore still on screen: found 2026-09-08 when a "Stop this send"
 * button appeared on a draft campaign, where it does nothing.
 *
 * It is `!important` on purpose. `hidden` is not a style, it is a statement
 * that the element is not part of the page - so nothing about how something
 * looks should be able to overrule it. Anything that needs to be laid out is
 * not hidden. */
[hidden]{display:none!important}
html,body{height:100%}
body{margin:0;background:var(--page);color:var(--ink);font-family:var(--font-body);font-size:13px;-webkit-font-smoothing:antialiased}
a{color:inherit}
button,input,select{font-family:inherit;font-size:13px}
button{cursor:pointer}

/* ---- 1. black top navigation ------------------------------------------- */
.topnav{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:13px;
  background:var(--nav);color:var(--nav-text);padding:0 14px;height:48px}
/* The mark, not the lockup. logo.png is the stacked lockup with "bayocorp"
   set under the B: at nav height the wordmark is an unreadable four-pixel
   smear and the black bird vanishes into the black bar. mark-gold.png is the
   mark alone with the B recoloured brand yellow (scripts/build-brand-marks.js)
   - gold on the black bar, which is where gold belongs, and big enough that
   the bird reads. The app name beside it already says the words, so nothing
   here re-typesets them. */
.topnav .mark{display:flex;align-items:center;justify-content:center;flex:none;
  width:38px;height:38px;border-radius:11px;background:var(--nav-hover);
  box-shadow:inset 0 0 0 1px rgba(255,222,89,.28);text-decoration:none;
  transition:box-shadow .14s,background .14s}
.topnav .mark img{height:24px;width:auto;display:block}
.topnav .mark:hover{background:var(--nav-active);box-shadow:inset 0 0 0 1px rgba(255,222,89,.6)}
.topnav .appname{font-family:var(--font-display);font-weight:700;font-size:15px;letter-spacing:.005em;white-space:nowrap}
.topnav .appname span{color:var(--yellow)}
.topnav nav{display:flex;align-items:center;gap:4px;margin-left:6px}
.topnav nav a{padding:6px 10px;border-radius:var(--radius);color:var(--nav-dim);text-decoration:none;font-weight:500;white-space:nowrap}
.topnav nav a:hover{background:var(--nav-hover);color:var(--nav-text)}
.topnav nav a.on{background:var(--nav-active);color:var(--nav-text)}
.topnav .spacer{flex:1}
.topnav .newbtn{background:var(--yellow);color:var(--on-yellow);border:0;border-radius:var(--radius);
  padding:6px 12px;font-weight:700;white-space:nowrap}
.topnav .newbtn:hover{background:var(--nav-text)}
.topnav .ghost{background:transparent;border:1px solid var(--nav-line);color:var(--nav-dim);border-radius:var(--radius);padding:5px 10px}
.topnav .ghost:hover{border-color:var(--nav-line-hover);color:var(--nav-text)}

/* ---- 2. white secondary tabs -------------------------------------------- */
/* The PROJECT header and tab bar. Lived in plans.html's own <style>, which
   worked while plans.html was the only page that used them - the Document
   review screen then rendered the same markup with no CSS at all and the tabs
   came out as one run-on line of words. Shared markup belongs in the shared
   stylesheet, the same reason projtabs.js exists. */
.projhead{padding:20px 26px 0}
.projhead h1{font-family:var(--font-display);font-size:24px;margin:0 0 3px;font-weight:700}
.projhead .meta{color:var(--grey);font-size:12.5px}

/* WHO IS ON THIS JOB. Ade, 2026-09-04: "the contacts under the project header
   carry the role and the name and nothing else." Until now the only screen
   that showed anybody was the workspace rail.
   The role is the small word and the name is the readable one, so the strip
   scans down the names with the roles as labels rather than reading as a list
   of job titles. --grey throughout, never --faint: --faint is a hairline
   colour and measures 2.8:1 on white. */
.projpeople{display:flex;flex-wrap:wrap;gap:6px 8px;align-items:center;margin-top:9px}
.projpeople .who{display:inline-flex;align-items:baseline;gap:6px;
  border:1px solid var(--line);border-radius:20px;padding:3px 11px;background:var(--surface);
  max-width:100%;overflow:hidden}
.projpeople .who .role{color:var(--grey);font-size:10px;letter-spacing:.06em;
  text-transform:uppercase;white-space:nowrap;flex:none}
.projpeople .who .nm{color:var(--ink);font-size:12.5px;font-weight:600;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* The ones left behind are a count and never a name - they have no role on the
   job, so naming them would be the header answering a question nobody asked. */
.projpeople .who.more{color:var(--grey);font-size:11.5px;border-style:dashed}
.projpeople.empty{color:var(--grey);font-size:12.5px;display:block}
.projpeople #projSync{margin-left:2px}
.projtabs{display:flex;gap:2px;padding:14px 26px 0;border-bottom:1px solid var(--line);flex-wrap:wrap}
.projtabs a,.projtabs span{padding:11px 14px;text-decoration:none;color:var(--grey);font-weight:500;border-bottom:2px solid transparent;white-space:nowrap}
.projtabs a:hover{color:var(--ink)}
.projtabs .on{color:var(--ink);border-bottom-color:var(--yellow);font-weight:600}

.tabs{display:flex;align-items:center;gap:2px;background:var(--surface);border-bottom:1px solid var(--line);padding:0 18px}
.tabs a,.tabs span{padding:10px 13px;text-decoration:none;color:var(--grey);font-weight:500;border-bottom:2px solid transparent;white-space:nowrap}
.tabs a:hover{color:var(--ink)}
/* `span` as well as `a`: the CURRENT tab is rendered as a span, not a link to
   where you already are, so styling only the anchor left the tab you are on as
   the one with no underline. */
.tabs a.on,.tabs span.on{color:var(--ink);border-bottom-color:var(--yellow);font-weight:600}

/* ---- 3. left sidebar ----------------------------------------------------- */
.layout{display:grid;grid-template-columns:206px minmax(0,1fr);align-items:start;min-height:calc(100vh - 48px)}
.side{border-right:1px solid var(--line);padding:13px 11px;position:sticky;top:48px;align-self:start}
.side .primary{width:100%;background:var(--yellow);color:var(--on-yellow);border:0;border-radius:var(--radius);
  padding:9px;font-weight:700;margin-bottom:12px}
.side .primary:hover{background:var(--ink);color:var(--page)}
.side .label{font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--grey);margin:4px 0 6px}
.side select{width:100%;border:1px solid var(--line);border-radius:var(--radius);padding:8px;background:var(--surface);color:var(--ink)}
.side hr{border:0;border-top:1px solid var(--line);margin:12px 0}
.side a{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:var(--radius);
  text-decoration:none;color:var(--ink);font-weight:500}
.side a:hover{background:var(--wash)}
.side a.on{background:var(--yellow-wash);font-weight:600}
.side a .ico{width:16px;text-align:center;opacity:.6}
/* How many are in Archived or Trash, on the link itself. A nav item that leads
   to an empty list is worth knowing about before the click, and the count is
   dropped entirely when it is zero rather than shown as a 0. */
.side a .railcount{margin-left:auto;font-size:11px;font-weight:600;color:var(--body);
  background:var(--wash);border-radius:20px;padding:1px 7px;min-width:20px;text-align:center}

/* A railed screen whose content is NOT a single <main class="work"> - the
   system library carries a .projhead, a sub-tab bar and two panes. The grid
   needs one child beside the rail; this is it, and minmax(0,1fr) is on .layout
   above for the reason the workspace already learnt: a 1fr track's automatic
   minimum is min-content, so one wide table drags the column past the window. */
.layout > .railed{min-width:0}

/* ONE INSET FOR A CARD, so two cards stacked in the same column start their
   text in the same place. Measured 2026-09-06: on Trash the warning notice used
   13px and the rows below it used 15px, and with a 1px border on each their
   text landed 246px and 248px from the left - two pixels apart, down the whole
   screen. Nobody would ever see that and everybody feels it.
   THE BORDER COUNTS. Measured on the Project screen the same day: the record
   list insets 16px (1px border + 15px), the summary cards 17px (1px + 16px) and
   the warning notice 18px (a 3px accent stripe + 15px) - three things stacked
   in one column, three different left edges, none of them visible to anybody
   reading the CSS. A card with a thicker accent border takes it OFF its padding
   so the text still lands with the rest. */
:root{--card-pad:15px}

/* ---- 4. workspace -------------------------------------------------------- */
.work{padding:22px 26px 60px}
.workhead{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin-bottom:16px}
.workhead h1{font-family:var(--font-display);font-size:22px;margin:0;font-weight:700;letter-spacing:0}
.workhead .spacer{flex:1}
.search{display:flex;align-items:center;gap:8px;border:1px solid var(--line);border-radius:var(--radius);
  padding:9px 12px;min-width:340px;background:var(--surface)}
.search input{border:0;outline:0;flex:1;font-size:14px}
.controls{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:8px;color:var(--grey)}
.controls .spacer{flex:1}
.chipbtn{border:1px solid var(--line);background:var(--surface);border-radius:var(--radius);padding:7px 11px;color:var(--ink)}
.chipbtn:hover{border-color:var(--line-strong)}
.chipbtn.on{border-color:var(--yellow-deep);background:var(--yellow-wash-soft);font-weight:600}

/* ---- 5. project rows ------------------------------------------------------
 * Wide rows, not cards. Ade: "each project occupies a wide horizontal row...
 * that lets the user see much more contextual information without opening
 * every project." Separated by a hairline, not enclosed in boxes.
 */
.rows{border-top:1px solid var(--line)}
/* The row is a div, not an anchor, so it can carry its own buttons: an anchor
   wrapping a button is invalid and swallows the click. The name is the link and
   its ::after stretches over the whole row, so the row still opens the project
   from anywhere that is not itself a control. */
.row{display:grid;grid-template-columns:104px 1fr 138px;gap:16px;padding:13px 4px;position:relative;
  border-bottom:1px solid var(--line);align-items:start;color:inherit}
.row:hover{background:var(--wash)}
.row .rowlink{text-decoration:none;color:inherit}
/* THE WHOLE ROW OPENS THE PROJECT, via a stretched overlay on the name's link.
   Ade, 2026-09-15: the project name and the yellow drawings tile both open that
   project's plans, and this is what makes the tile do it without a second link
   wrapped round it.
   IT IS ALSO WHY "View all activity" OPENED THE PLAN TILES. The overlay covers
   the entire row, so anything inside it that is meant to be pressed on its own
   has to be lifted above it - `.rowedit` already was, and the activity controls
   were not, so every press landed on the project link instead. A button with a
   perfectly good handler, completely unreachable: the handler fires under a
   synthetic click and never under a finger. */
.row .rowlink::after{content:'';position:absolute;inset:0}
/* Everything inside the row that is its own control, lifted clear of that
   overlay. z-index, not just position, because the overlay is a later sibling
   and would win on document order alone. */
.row .act-all,.row .act a{position:relative;z-index:1}
/* Always visible, not hover-only: a control that only appears on hover does not
   exist on a touch screen, and is not findable on a desktop either. */
.row .rowedit{position:relative;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);
  color:var(--grey);font-size:12px;padding:4px 9px;margin-bottom:8px}
.row .rowedit:hover{border-color:var(--line-strong);color:var(--ink)}
/* Three actions on a row, stacked, so they read as a set and none of them is
   the accidental one next to Rename. */
.row .rowacts{display:flex;flex-direction:column;gap:4px;align-items:stretch}
.row .rowacts .rowedit{width:100%;text-align:center}
.row .rowedit.danger:hover{border-color:#d98b80;color:var(--bad-ink)}
.tile{background:var(--yellow);border-radius:var(--radius);padding:11px 8px;text-align:center;color:var(--on-yellow)}
.tile .n{font-family:var(--font-display);font-size:25px;font-weight:700;line-height:1}
.tile .l{font-size:10px;letter-spacing:.1em;text-transform:uppercase;margin-top:3px;font-weight:600}
.tile .s{font-size:10.5px;margin-top:6px;opacity:.75}
.row h2{font-family:var(--font-display);font-size:16px;margin:0 0 2px;font-weight:700}
.row .meta{color:var(--grey);font-size:12px;margin-bottom:7px}
.row .acthead{font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--grey);margin-bottom:5px}
.act{display:flex;gap:10px;font-size:12px;color:var(--body);padding:1px 0}
.act .t{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* WHO DID IT, a column on the Projects screen as it already is on Recent.
   Ade, 2026-09-15: "the user's name should appear for all supported activity
   types", and "do not leave the user field blank". Fixed width, so the names
   line up down the card rather than landing wherever each sentence ends. */
.act .by{flex:none;width:66px;color:var(--ink);font-weight:600;font-size:11.5px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Nobody recorded. --grey and never --faint, which measures 2.8:1 on white and
   is a hairline colour, not a text one (docs/take-off.md). */
.act .by.none{color:var(--grey);font-weight:400;font-size:11px}
.act .w{color:var(--grey);white-space:nowrap;font-size:11.5px;width:58px;text-align:right;flex:none}
/* "View all activity" - a link, not a button, because it goes somewhere. */
.act-all{display:inline-block;margin-top:5px;font-size:11.5px;color:var(--ink);
  background:none;border:0;padding:0;cursor:pointer;text-decoration:underline;font-family:inherit}
.act-all:hover{color:var(--ink);text-decoration-thickness:2px}
/* The name of the thing, inside the sentence, as a link to the thing itself.
   Underlined on hover only: a feed of underlined names reads as a page of
   links rather than as a history with one in each line. */
.act a{color:inherit;text-decoration:none;border-bottom:1px dotted var(--line-strong)}
.act a:hover{color:var(--ink);border-bottom-color:currentColor}
.controls .range{display:flex;gap:6px;align-items:center}
.controls .updated{font-size:11.5px;color:var(--grey);white-space:nowrap}
.controls #btnClear{border-color:var(--gold);font-weight:600;color:var(--ink)}
.statuscol{text-align:right}
.statuscol .h{font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--grey);margin-bottom:6px}
.badge{display:inline-block;border:1px solid var(--line);border-radius:20px;padding:4px 11px;
  font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}
.badge.takeoff,.badge.estimating{background:var(--yellow);border-color:var(--yellow-deep);color:var(--on-yellow)}
.badge.live,.badge.priced{background:var(--strong);color:var(--on-strong);border-color:var(--strong)}
.badge.complete{background:var(--good-bg);border-color:var(--good-line);color:var(--good-ink)}
.badge.lead,.badge.survey{background:var(--surface)}
.review-pill{display:inline-block;margin-top:8px;font-size:11.5px;color:var(--yellow-ink);background:var(--yellow-wash);
  border:1px solid var(--yellow-line);border-radius:20px;padding:3px 9px}

/* ---- plans grid ----------------------------------------------------------- */
.grouphead{display:flex;align-items:baseline;gap:10px;margin:26px 0 12px}
.grouphead h3{font-family:var(--font-display);font-size:16px;margin:0}
.grouphead span{color:var(--grey);font-size:12.5px}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:18px}
.plan{border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;background:var(--surface);
  text-decoration:none;color:inherit;display:block}
.plan:hover{border-color:var(--yellow-deep)}
.plan.sel{border-color:var(--yellow-deep);box-shadow:inset 0 0 0 1px var(--yellow-deep)}
.plan .prev{height:168px;background:var(--wash);display:flex;align-items:center;justify-content:center;
  border-bottom:1px solid var(--line-soft);overflow:hidden}
.plan .prev img{width:100%;height:100%;object-fit:contain}
.plan .prev .none{color:var(--ghost);font-size:12px}
.plan .info{padding:11px 12px}
.plan .no{font-family:var(--font-display);font-weight:700;font-size:13px}
.plan .ti{font-size:12.5px;color:var(--body);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.plan .ind{display:flex;gap:11px;margin-top:8px;font-size:11.5px;color:var(--grey);flex-wrap:wrap}
.plan .rev{display:inline-block;border:1px solid var(--line);border-radius:20px;padding:1px 7px;font-size:10.5px;margin-left:6px}
.plan .warn{color:var(--yellow-ink)}
.plan .sup{background:var(--wash);color:var(--grey)}

table.plans{width:100%;border-collapse:collapse;font-size:13px}
table.plans th{text-align:left;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--grey);
  border-bottom:1px solid var(--line);padding:9px 10px;font-weight:600}
table.plans td{border-bottom:1px solid var(--line-soft);padding:10px}
table.plans tr:hover td{background:var(--wash)}

.empty-state{padding:70px 20px;text-align:center;color:var(--grey)}
.empty-state .big{font-family:var(--font-display);font-size:17px;color:var(--ink);margin-bottom:6px}
.muted{color:var(--grey)}
/* A MESSAGE MUST NEVER EAT A CLICK. Nothing in a toast is clickable, and it
   parks itself over the bottom-right corner of every screen for four seconds -
   so anything a person reaches for down there is unreachable while it shows,
   with no symptom at all beyond "nothing happened". Live-caught 2026-09-09: the
   confirmation of a saved measurement sat exactly on top of the Complete button
   for the NEXT one. Same family as the voice recorder's unreachable button, and
   `elementFromPoint` is what found it - a synthetic .click() passes straight
   through this. */
.toast{pointer-events:none;position:fixed;bottom:18px;right:18px;background:var(--strong);color:var(--on-strong);border-left:3px solid var(--yellow);
  border-radius:var(--radius);padding:11px 15px;font-size:13px;max-width:360px;opacity:0;transform:translateY(8px);
  transition:.2s;z-index:99}
.toast.show{opacity:1;transform:none}
.toast.bad{border-left-color:var(--bad-soft)}
dialog{border:1px solid var(--line);border-radius:8px;padding:22px;width:min(94vw,460px)}
/* A dialog that carries a list rather than three fields. index.html has its own
   .wide; this is the same idea for the pages on the shell. */
dialog.wide{width:min(94vw,660px)}
dialog::backdrop{background:var(--shade)}
dialog h3{font-family:var(--font-display);margin:0 0 10px;font-size:18px}
dialog label{display:block;font-size:12px;color:var(--grey);margin:12px 0 4px}
dialog input,dialog select{width:100%;border:1px solid var(--line);border-radius:var(--radius);padding:9px}
dialog .actions{display:flex;gap:8px;justify-content:flex-end;margin-top:18px}
.btn{border:1px solid var(--line);background:var(--surface);color:var(--ink);border-radius:var(--radius);padding:9px 13px}
.btn:hover{border-color:var(--line-strong)}
/* The label goes with the panel. Without an explicit colour the button
 * inherited --ink, which is near-white in dark mode - so the primary button
 * on every screen read #ececec on #FFDE59 at 1.12:1. Measured 2026-09-04. */
.btn.yellow{background:var(--yellow);border-color:var(--yellow-deep);color:var(--on-yellow);font-weight:700}
.btn.yellow:hover{background:var(--ink);color:var(--page)}

/* ---- the upload progress bar ---------------------------------------------
   Ade, 2026-09-05: "when plans and documents are uploaded I want to see a
   progress bar so we can see how long we have to wait." Two phases, both
   named in words beside the bar - see public/upload.js for why the second one
   exists and why it never fills. */
.upbar{margin:14px 0 4px}
.uptrack{height:8px;border-radius:20px;background:var(--line-soft);border:1px solid var(--line);overflow:hidden}
.upfill{height:100%;width:0;border-radius:20px;background:var(--yellow);
  transition:width .18s linear}
.upbar.bad .upfill{background:var(--bad-soft)}
/* The preparing phase, once its estimate has run out: the bar stops moving, so
   the stripes are what say the machine has not stopped. */
.upbar.unsure .upfill{
  background-image:linear-gradient(115deg,
    rgba(255,255,255,.55) 25%, transparent 25%, transparent 50%,
    rgba(255,255,255,.55) 50%, rgba(255,255,255,.55) 75%, transparent 75%);
  background-size:18px 18px;
  animation:upstripe .7s linear infinite}
@keyframes upstripe{to{background-position:18px 0}}
@media (prefers-reduced-motion:reduce){.upbar.unsure .upfill{animation:none}}
.upsay{display:flex;gap:12px;align-items:baseline;margin-top:7px;font-size:12.5px;line-height:1.4}
.upwhat{color:var(--ink);flex:1;min-width:0}
.upleft{color:var(--grey);flex:none;white-space:nowrap}
.upbar.bad .upwhat{color:var(--bad-ink)}

/* THE SAME BAR, WITH NO DIALOG AROUND IT.
   Ade, 2026-09-09: "when you drag and drop plans into the upload plans section
   the pop up window should disappear and the plans should just start uploading
   - you shouldn't have to press upload." Once the dialog goes, the progress has
   to live somewhere on the page or the slowest thing in the app happens with
   nothing to watch. A card in the corner, out of the way of the list it is
   about to change. */
.upfloat{position:fixed;right:18px;bottom:18px;z-index:120;width:min(360px,calc(100vw - 36px));
  background:var(--card);border:1px solid var(--line);border-radius:12px;
  padding:12px 14px 10px;box-shadow:0 10px 30px rgba(0,0,0,.18)}
.upfloat .upbar{margin:0}

/* ---- searching every project, and the category a project is filed under ----
   Ade, 2026-09-05. The search results replace the project list while there is
   a query, so they have to read as results and not as rows - hence the tighter
   line and the label saying where each hit lives. */
.hitgroup{margin-bottom:22px}
.hitgroup h3{margin:0 0 8px;font-family:var(--font-display);font-size:12px;font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;color:var(--grey)}
.hit{display:flex;flex-direction:column;gap:3px;padding:11px 14px;background:var(--surface);
  border:1px solid var(--line);border-radius:var(--radius);margin-bottom:6px;text-decoration:none}
.hit:hover{border-color:var(--line-strong)}
.hitmain{display:flex;align-items:center;gap:8px;font-size:14.5px;color:var(--ink)}
.hitmain b{font-family:var(--font-display);font-weight:700}
.hitmain .rev{font-size:12px;color:var(--grey);font-weight:500}
.hitsub{font-size:12.5px;color:var(--grey);line-height:1.4}
.wherepill{font-size:11px;padding:1px 8px;border-radius:20px;border:1px solid var(--line-strong);
  color:var(--grey)}
.wherepill.bad{border-color:var(--bad-line);background:var(--bad-wash);color:var(--bad-ink)}
/* The category on a project row: a coloured dot and the name, ahead of the
   client and the date, because it is what the list is filtered by. */
.catdot{display:inline-flex;align-items:center;gap:6px;margin-right:10px;font-weight:600;color:var(--ink)}
.catdot::before{content:'';width:9px;height:9px;border-radius:50%;background:var(--dot,var(--grey));flex:none}

/* ==========================================================================
   A DIALOG THAT CARRIES A REGISTER.

   Ade, 2026-09-11: "The column headings do not line up with the information,
   text is being squeezed into extremely narrow columns, content is cut off on
   the right, and individual rows become unnecessarily tall."

   The cause was one number: `dialog` is width:min(94vw,460px), and the sheet
   review puts NINE columns in it. Nothing about the table was wrong - there
   was 460 pixels to put it in. This is the wide one.
   ========================================================================== */
dialog.huge {
  width: min(95vw, 1600px);
  max-width: 95vw;
  height: 88vh;
  max-height: 88vh;
  padding: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
/* Header and footer hold still while the list moves. A footer that scrolls
   away is a Save button somebody has to hunt for. */
dialog.huge > .head,
dialog.huge > .foot { flex: 0 0 auto; padding: 14px 18px; background: var(--card); }
dialog.huge > .head { border-bottom: 1px solid var(--line); }
dialog.huge > .foot {
  border-top: 1px solid var(--line); display: flex; gap: 8px;
  align-items: center; justify-content: flex-end;
}
dialog.huge > .foot .spacer { margin-right: auto; color: var(--grey); font-size: 12.5px; }
/* `min-height:0` is load-bearing: a column flex item defaults to min-content,
   so without it the body grows to its content and the dialog scrolls the page
   instead of the table. Same trap as the drawing viewer. */
dialog.huge > .body { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 0; }

/* The register itself. Fixed layout so the browser obeys the widths given
   rather than sizing to content - which is what put the title in a sliver and
   gave a one-word column half the table. */
.register { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: 13px; }
.register thead th {
  position: sticky; top: 0; z-index: 2; background: var(--card);
  text-align: left; font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--grey); padding: 10px 10px; border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.register td { padding: 9px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
.register tr:hover td { background: var(--hover, rgba(255,255,255,.03)); }
/* A row is COMPACT. It does not become hundreds of pixels tall because of a
   warning - the warning is its own line, and the long version is behind
   Review. */
.register .doc-title { font-weight: 600; }
.register .doc-file { color: var(--grey); font-size: 11.5px; margin-top: 2px; }
.register .truncate { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.register select { width: 100%; font-size: 12px; padding: 4px 6px; }

/* Warnings sit BELOW the title, in amber, with what to do about them. */
.warn-line {
  margin-top: 5px; font-size: 11.5px; color: var(--warn-ink, #d08b2c);
  display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap;
}
.warn-line .btn.text { font-size: 11.5px; padding: 0; }
.warn-full { margin-top: 6px; font-size: 12px; color: var(--grey); }

/* Where a value came from, said rather than implied by a bare dropdown arrow. */
.conf { display: inline-block; font-size: 10.5px; letter-spacing: .04em; margin-top: 3px; }
.conf.high { color: var(--good-ink, #4b9e6a); }
.conf.review { color: var(--warn-ink, #d08b2c); }
.conf.none { color: var(--bad, #c0562e); }
.conf.manual { color: var(--grey); }

.register .pkgs { display: flex; gap: 4px; flex-wrap: wrap; }
.register .pkgs .pill { font-size: 11px; }

/* The filter bar above the list, which holds still with the header. */
.register-bar {
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  padding: 10px 18px; border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 3; background: var(--card);
}
.register-bar input[type="search"] { flex: 1 1 220px; min-width: 160px; }
.register-bar select { font-size: 12px; padding: 4px 6px; }

/* HIGHER SPECIFICITY THAN THE PAGE'S OWN dialog.sheet RULES, which live in a
   <style> block inside tender.html and therefore come after this file. Equal
   specificity loses on order, so `.huge` was being applied and then overruled:
   max-width went back to 760px, the body kept max-height:64vh, and the footer
   sat on top of the last rows. That is what the reachability check caught -
   "select covered by div#dlgFoot" - and it is exactly the kind of thing that
   looks like a broken layout rather than a losing cascade. */
dialog.sheet.huge {
  max-width: 95vw; width: min(95vw, 1600px);
  height: 88vh; max-height: 88vh;
  display: flex; flex-direction: column; padding: 0;
}
dialog.sheet.huge > .body {
  max-height: none; flex: 1 1 auto; min-height: 0; overflow: auto; padding: 0;
}
dialog.sheet.huge > .head, dialog.sheet.huge > .foot { flex: 0 0 auto; }
