/* DBOT QS Platform — a dense, calm interface for people who live in a grid.
   Two rules drive every colour choice here:
     white cells are yours, grey cells are the system's;
     nothing that is missing is ever drawn as a zero. */

:root {
  --bg: #f6f7f9;
  --surface: #ffffff;
  --surface-2: #fbfcfd;
  --line: #e3e7ec;
  --line-strong: #cdd4dc;
  --ink: #1a2230;
  --ink-2: #5b6777;
  /* The quiet tokens still have to be readable. --ink-3 carries the smallest
     text in the app -- tile captions, column units, the grid's status line,
     all at 10.5-12px -- and at #8b96a5 it measured 3.0:1 on white, well under
     the 4.5:1 minimum. #636c79 is 5.31:1 on white and clears 4.5:1 on every
     ground it is used against, down to the totals row. --warn and --ok were
     4.25:1 and 4.46:1 on their own chip backgrounds: just under, and just
     under is still under. */
  --ink-3: #636c79;
  --accent: #3050c8;
  --accent-soft: #eaeeff;
  /* The fill of a chart bar, and its own token rather than --accent.
     On white the two are the same colour and this could have been --accent;
     they part in dark, where --accent has to carry text and is lightened
     until it does, which puts it outside the lightness band a bar fill has
     to sit in. A bar is a shape on a surface, not a word on a ground, so it
     is graded against the surface and the text colour is left alone. */
  --chart-bar: #3050c8;
  --chart-track: #eef0f4;
  /* A difference has a direction, so it gets a diverging pair rather than a
     single hue: warm for a band that grew, cool for one that shrank, and the
     cool one is the chart's own blue so the two screens read as one family.
     Not --ok and --bad: those are reserved for good and bad, and a cost going
     up is neither. Both clear 4.5:1 on this ground, so the same token paints
     the bar and writes the figure beside it. */
  --chart-up: #c2410c;
  --chart-down: #3050c8;
  --derived-bg: #f1f3f6;
  --derived-ink: #48566a;
  --ok: #146c3f;
  --ok-soft: #e6f5ec;
  --warn: #8a5200;
  --warn-soft: #fdf3e2;
  --bad: #c02828;
  --bad-soft: #fdecec;
  --shadow: 0 1px 2px rgba(20,30,50,.06), 0 4px 16px rgba(20,30,50,.06);
  /* ---- the rail ----------------------------------------------------------
     One width for the sidebar and for the brand block above it, so the
     hairline after the project's name in the bar falls on the sidebar's
     own edge instead of 39px to the right of it. `--rail-inset` is the
     column's side padding, and it is also where the active mark and the
     project's name box start and stop, so every pill in the column shares
     one left and one right. */
  --rail: 248px;
  --rail-inset: 10px;
  /* ---- surfaces that flip -------------------------------------------------
     Every colour below is the value it already was; naming them changes
     nothing on screen and is the whole of what makes a dark palette
     possible -- a hard-coded grey cannot be redefined, and there were 52 of
     them outside this block. Each one that a dark theme has to answer for
     is named here; the ones that stay put (white on a dark button, the
     brand's own navy) are not. */
  --on-ink: #fff;                 /* text on --ink and on --accent */
  --row-hover: #eef1f6;           /* a table row under the pointer */
  --row-hover-derived: #e7ebf1;   /* ... and its computed cells, a shade down */
  --derived-hover: #e8ebf0;
  --total-bg: #f0f3f8;            /* a totals column */

  /* ---------- the workbook's own colour code ----------------------------
     AVS colours by role, and a QS reads the colour before they read the
     words. Three of the four are on `Summary`, the sheet anybody opens
     first; the fourth is on the BOQ sheets, which are the ones with
     sections to total:

        #002060  navy   the sheet's title  `Summary!A2`, and 21 tabs besides
        #DEEBF7  blue   a column header    `Summary!A3` (`Sr.No`)
        #C5E0B4  green  a section subtotal `Civil BOQ (LD Patel R1)!B18`
                                           (`Excavation Total`)
        #FFE599  amber  the total          `Summary!B15` (` Total`)

     The navy and the amber are the workbook's own RGB. The blue and the
     green are a theme colour and a tint -- accent1 at 0.8 and accent6 at
     0.6 -- resolved against `xl/theme/theme1.xml` the way Excel resolves
     one. `test_screens.py` reads all four back out of the workbook, so a
     token that drifts from the sheet fails rather than waiting to be
     spotted by somebody holding the two side by side.

     Thirty-five hand-coloured tabs also hold near neighbours of each: the
     amber is `#FFE699` on some sheets, the green `#A9D18E`, the blue
     `#B4C7E7`. One of each is carried and not all three, because the
     platform has one totals row rather than three shades of one -- the
     difference is how the workbook was coloured, not what it meant.

     The ink beside each is ours: the workbook prints black on all four, and
     black on #002060 is unreadable, so the dark one gets white and the
     three pale ones get a deep version of their own hue rather than a flat
     black that would read as a fifth colour. */
  --wb-title: #002060;
  --wb-title-ink: #ffffff;
  --wb-title-line: #0a3a86;
  --wb-header: #deebf7;
  --wb-header-ink: #13324f;
  --wb-subtotal: #c5e0b4;
  --wb-subtotal-ink: #22401a;
  --wb-total: #ffe599;
  --wb-total-ink: #4d3c08;
  --flash-warn: #fff3b0;          /* a headline figure that just moved */
  --flash-ok: #dff3e6;            /* a cell the server has accepted */
  --skeleton-a: #edf0f4;          /* the placeholder slab, and its sweep */
  --skeleton-b: #f7f9fb;
  --tip-bg: #1c2534;              /* the tooltip: dark on light, light on dark */
  --tip-ink: #f2f5f9;
  --scroll-thumb: #c9d0da;
  --scroll-thumb-hover: #adb7c4;
  --select-bg: #c9d6ff;           /* ::selection */
  --ink-hover: #2c3646;           /* the primary button, pressed on */
  --notice-ink: #7a4b00;          /* words on --warn-soft */
  /* The chips' borders were three more hard-coded tints of the three status
     colours. Named, at exactly the values they were: a mix off --ok/--warn/
     --bad would be tidier and it lands four units away from each of these,
     which is four units this pass promised not to move. */
  --ok-line: #c6e6d4;
  --warn-line: #f0ddb9;
  --bad-line: #f3caca;

  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, sans-serif;

  /* A 4/8 rhythm on the dense end of the scale. Spacing was ad-hoc before, so
     cards, toolbars and card bodies each drifted a few pixels apart. */
  --space-1: 4px;  --space-2: 8px;   --space-3: 12px;
  --space-4: 16px; --space-5: 24px;  --space-6: 32px;

  /* One duration for hover and state, per the Swiss/minimal direction: motion
     here confirms an interaction, it never performs. */
  --tempo: 160ms;
  --ease: cubic-bezier(.2, .6, .3, 1);
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font: 13px/1.45 var(--sans);
  -webkit-font-smoothing: antialiased;
}
button, input, select { font: inherit; color: inherit; }

#app {
  display: grid; height: 100vh;
  grid-template-columns: var(--rail) 1fr;
  grid-template-rows: 56px 1fr;
  grid-template-areas: "top top" "side main";
}

/* ---------- top bar ---------- */
/* The header carries three different kinds of thing and used to style them
   as one flat row of identical pills: the project it is about, figures that
   report, and controls that do something. Your own name, the validation
   count and Sign out were the same grey lozenge, so a caption invited a click
   and a session-ending action read as a label.
   Three groups now, separated rather than merely spaced. */
/* A screen on its way. The one you are on stays put and live while the next
   one's figures arrive; this line at the very top says your click was heard.
   Only after 150 ms (app.js `FETCHING_AFTER_MS`), so a quick change shows
   nothing at all. Fixed to the viewport, so it moves no layout. */
#app.fetching::before {
  content: ""; position: fixed; top: 0; left: 0; right: 0; height: 2px;
  z-index: 1000; pointer-events: none; background: var(--accent);
  transform-origin: left; animation: fetching 1.1s cubic-bezier(.2, .7, .3, 1) infinite;
}
#app.fetching #main { cursor: progress; }
@keyframes fetching {
  0% { transform: scaleX(0); opacity: 1; }
  70% { transform: scaleX(.8); opacity: 1; }
  100% { transform: scaleX(1); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  #app.fetching::before { animation: none; opacity: .6; }
}

#topbar {
  grid-area: top; display: flex; align-items: center; gap: 20px;
  background: var(--surface); border-bottom: 1px solid var(--line);
  padding: 0 16px; z-index: 20;
}
/* Actions sit together at the end, behind a rule, so the boundary between
   "this reports" and "this does something" is visible rather than implied.
   One container with one gap: seven items each carrying their own margin is
   how a bar ends up looking crowded in one place and gappy in another. */
#topbar .health, #topbar .build { flex: none; }
.bar-tools {
  display: flex; align-items: center; gap: 10px;
  margin-left: auto; flex: none; align-self: stretch;
  padding-left: 16px; position: relative;
}
.bar-tools > * { flex: none; }
.brand { display: flex; align-items: center; gap: 10px; min-width: 200px; }
.mark {
  width: 30px; height: 30px; border-radius: 7px; background: var(--ink);
  color: var(--on-ink); display: grid; place-items: center;
  font-weight: 650; font-size: 12px; letter-spacing: .04em;
}
/* Height-constrained, width free: a wordmark is wider than it is tall and
   must not be squashed into the badge's square. */
.logo { height: 26px; width: auto; max-width: 132px; object-fit: contain;
        display: block; flex: none; }
/* The name as type, for as long as the mark itself is not in the build. The
   navy is the company's; the letterforms are not pretending to be. */
.wordmark {
  font-size: 22px; font-weight: 700; letter-spacing: -.02em;
  color: #0d2464; line-height: 1; flex: none; user-select: none;
}
.wordmark::after {
  content: "®"; font-size: 8px; vertical-align: super; font-weight: 600;
  margin-left: 1px;
}
.project-name { font-weight: 600; font-size: 13px; line-height: 1.2; }
.project-sub { color: var(--ink-3); font-size: 11px; }

.headline {
  display: flex; gap: 0; flex: 1; overflow-x: auto;
  /* The figures are one reading, so they are one block with hairlines
     between them rather than five islands drifting in whitespace. */
  scrollbar-width: thin;
}
.stat {
  white-space: nowrap; padding: 0 18px;
  border-left: 1px solid var(--line);
}
.stat:first-child { padding-left: 0; border-left: 0; }
.stat .v {
  font-variant-numeric: tabular-nums; font-weight: 620;
  font-size: 14px; letter-spacing: -.01em;
}
.stat .k { color: var(--ink-3); font-size: 10.5px; text-transform: uppercase;
           letter-spacing: .05em; }
.stat.changed .v { animation: flash .9s ease-out; }
@keyframes flash {
  0%   { background: var(--flash-warn); box-shadow: 0 0 0 4px var(--flash-warn); }
  100% { background: transparent; box-shadow: 0 0 0 4px transparent; }
}

.health { display: flex; align-items: center; gap: 8px; }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 550;
  border: 1px solid transparent; white-space: nowrap;
}
.chip.ok   { background: var(--ok-soft);   color: var(--ok);   border-color: var(--ok-line); }
.chip.warn { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-line); }
.chip.bad  { background: var(--bad-soft);  color: var(--bad);  border-color: var(--bad-line); }
.chip.mute { background: var(--derived-bg); color: var(--ink-2); border-color: var(--line); }

/* A chip that *does* something says so: a pointer, a hover, and a focus ring
   a keyboard can find. A chip that merely reports gets none of it. */
.chip.act { text-decoration: none; cursor: pointer; transition: background .12s ease,
            border-color .12s ease, box-shadow .12s ease; }
.chip.act:hover  { filter: brightness(.97); border-color: var(--ink-3); }
.chip.act:active { transform: translateY(.5px); }
.chip.act:focus-visible {
  outline: none; box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--ink);
}
/* Signing out ends the session, so it is not dressed as a peer of People. */
.chip.act.quiet {
  background: transparent; border-color: transparent; color: var(--ink-3);
}
.chip.act.quiet:hover { color: var(--bad); border-color: var(--bad-line); }

/* The mark carries the meaning alongside the colour: a red pill says nothing
   to somebody who cannot see red, and a bare count reads as "2". */
.chip-mark { font-size: 12px; line-height: 1; }
.chip-text { font-variant-numeric: tabular-nums; }

/* Your own name is a label, not a control. */
.who {
  font-size: 12px; color: var(--ink-2); font-weight: 550;
  padding-right: 4px; white-space: nowrap;
}

/* Available to a screen reader, invisible on screen -- so a status can read
   as a full phrase without the header carrying a sentence. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- sidebar ---------- */
#sidebar {
  grid-area: side; background: var(--surface); border-right: 1px solid var(--line);
  padding: 12px var(--rail-inset); display: flex; flex-direction: column; gap: 1px;
  overflow-y: auto;
}
#sidebar a {
  display: block; padding: 7px 10px; border-radius: 6px;
  color: var(--ink-2); text-decoration: none; font-size: 12.5px;
}
#sidebar a:hover { color: var(--ink); }
/* The sheet you are on: its name in the accent, heavier, and larger -- no
   box behind it ("not a tab: when I click on the sheet it gets colour and
   is bigger than the others"). Larger by `transform`, from its left edge,
   so the links below it do not move: a font-size change would re-lay the
   column and nudge every link under it by two pixels on each navigation.
   Colour is not the only signal -- the size, the weight and the tick drawn
   in `theme.css` say it too. */
#sidebar a.active {
  color: var(--accent); font-weight: 680;
  transform: scale(1.14); transform-origin: 0 50%;
}
/* A group of screens, foldable. The heading is a button; the body is a grid
   whose single row goes from 1fr to 0fr, which is how a list of unknown
   height collapses without measuring it in JavaScript. */
.nav-sec { display: block; }
.nav-sec-head {
  display: flex; align-items: center; gap: 6px; width: 100%;
  margin: 14px 0 2px; padding: 4px 10px 4px 6px;
  background: none; border: 0; border-radius: 6px; cursor: pointer;
  color: var(--ink-3); font: inherit;
  font-size: 10px; text-transform: uppercase; letter-spacing: .07em; font-weight: 600;
}
.nav-sec-name { flex: 1; text-align: left; }
/* The group the screen you are on lives in, lit in the same accent -- open
   or folded, so the rail says where you are with its links away. */
.nav-sec.has-active > .nav-sec-head { color: var(--accent); }
.nav-chev { width: 13px; height: 13px; flex: none; }
.nav-sec-body { display: grid; grid-template-rows: 0fr; }
.nav-sec.is-open .nav-sec-body { grid-template-rows: 1fr; }
.nav-sec-list { overflow: hidden; min-height: 0; display: flex;
                flex-direction: column; gap: 1px; }

/* Which project kind's rail is on screen. Both sets of sections are in the
   markup; this is the only thing that decides which of them is offered, so a
   QS project can never reach a TDD screen through the nav and a TDD project
   is never asked for a rate library it does not have. */
#app.kind-tdd #sidebar [data-kind="qs"],
#app:not(.kind-tdd) #sidebar [data-kind="tdd"] { display: none; }

/* The way out of the platform, at the foot of the list. */
.nav-out {
  display: flex; align-items: center; gap: 8px;
  margin-top: 10px; padding: 7px 10px; border-radius: 6px;
  color: var(--ink-3); text-decoration: none; font-size: 12.5px;
}
.nav-out svg { width: 14px; height: 14px; flex: none; }
.nav-out:hover { background: var(--surface-2); color: var(--ink); }
/* The bottom room lives in the legend's own padding rather than in the rail's,
   because the rail scrolls: a scroll container's bottom padding is honoured by
   some engines and dropped by others, and the last line of the last item is
   not the place to find out which. Inside the item it is content, and content
   always scrolls. */
.legend {
  margin-top: auto; padding: 12px 10px 14px; border-top: 1px solid var(--line);
  color: var(--ink-3); font-size: 11px; display: grid; gap: 6px;
}
.legend div { display: flex; align-items: center; gap: 7px; }
.legend-note { display: block !important; line-height: 1.4; margin-top: 2px; }
/* A sample of the real cell, not a colour chip. Same mono face, same tabular
   figures, same two backgrounds as `td.cell` and `td.derived` above, so the
   key and the thing it describes cannot drift apart. Inert: no pointer, no
   hover, nothing focusable. */
.legend-cell {
  flex: none; min-width: 52px; padding: 2px 6px; border-radius: 3px;
  font-family: var(--mono); font-size: 10.5px; font-variant-numeric: tabular-nums;
  text-align: right; border: 1px solid var(--line);
}
.legend-cell.input { background: var(--surface); border-color: var(--line-strong); color: var(--ink); }
.legend-cell.derived { background: var(--derived-bg); color: var(--derived-ink); }

/* ---------- main ---------- */
#main { grid-area: main; overflow: auto; padding: 18px 20px 60px; }
.loading { color: var(--ink-3); padding: 40px; text-align: center; }
.screen-head { margin-bottom: 14px; }
.screen-head h1 { font-size: 17px; margin: 0 0 3px; font-weight: 640; letter-spacing: -.01em; }
.screen-head p { margin: 0; color: var(--ink-2); max-width: 78ch; font-size: 12.5px; }
.toolbar { display: flex; gap: 8px; align-items: center; margin: 12px 0; flex-wrap: wrap; }

.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 9px; box-shadow: var(--shadow); overflow: hidden;
  margin-bottom: 16px;
}
/* A table's title, in the workbook's own navy. `Summary!A2` is white on
   #002060 and so is the top row of twenty-one other tabs; a QS finds the
   heading of a block by its colour before reading it, and the platform does
   not get to have a different convention from the sheet it replaces. */
.card > h2 {
  margin: 0; padding: 11px 14px; font-size: 12.5px; font-weight: 600;
  border-bottom: 1px solid var(--wb-title-line);
  background: var(--wb-title); color: var(--wb-title-ink);
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
}
/* Everything inside the title reads on the navy: the subtitle, the links the
   header carries (rename · remarks · remove) and the band picker beside them. */
.card > h2 .sub { font-weight: 400; font-size: 11.5px; color: var(--wb-title-ink);
  opacity: .72; }
.card > h2 a { color: var(--wb-title-ink); text-decoration-color: rgba(255,255,255,.45); }
.card > h2 a:hover { text-decoration-color: currentColor; }
.card > h2 code { background: rgba(255,255,255,.12); color: inherit; }
.card > h2 .ht-pick {
  /* The app's chevron, in the title's own white: the shorthand would
     otherwise put the grey one back, which barely shows on the navy. */
  background: rgba(255,255,255,.10) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23ffffff' stroke-opacity='.85' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat right 7px center;
  color: var(--wb-title-ink);
  border-color: rgba(255,255,255,.28);
}
.card > h2 .ht-pick option { color: var(--ink); background: var(--surface); }
/* A button in a title is on the navy like everything else in it. `.btn` is
   white-on-paper and `button` inherits its colour, so the title's white ink
   landed on a white fill: the box was not empty, its label was invisible. The
   shadow goes with it -- a raised white chip on a dark heading reads as a
   smudge rather than as a control. */
.card > h2 button.btn {
  background: rgba(255,255,255,.10); color: var(--wb-title-ink);
  border-color: rgba(255,255,255,.28); box-shadow: none;
}
.card > h2 button.btn:hover {
  background: rgba(255,255,255,.18); border-color: rgba(255,255,255,.45);
}
/* And the ring a keyboard leaves, in the title's own ink: `--accent` measures
   2.26:1 on #002060, under the 3:1 a focus indicator needs, so the one control
   in a heading was the one place tabbing went quiet. */
.card > h2 :focus-visible { outline-color: var(--wb-title-ink); }
.card-body { padding: 14px; }

/* ---------- tables & grid ---------- */
/* Long tables keep their header: scrolling a 1,400-row take-off and losing
   which column is which is how a figure gets read against the wrong label. */
/* Opaque, so the browser can scroll it on the GPU. A see-through scroller
   has to be repainted on the main thread for every frame of a scroll on a
   1x screen -- Chrome will not hand it to the compositor, because the text
   inside could then not be antialiased against a known background. Measured
   on Work Status: 377 paints per 40 wheel ticks see-through, 73 opaque. The
   cells paint their own backgrounds over it, so nothing on screen changes. */
.grid-wrap { overflow: auto; max-height: 78vh; overscroll-behavior: contain; position: relative;
  background: var(--surface); }
table.grid { border-collapse: separate; border-spacing: 0; width: 100%; font-size: 12.5px; }
table.grid th, table.grid td {
  border-bottom: 1px solid var(--line); border-right: 1px solid var(--line);
  padding: 0; height: 28px; text-align: right; white-space: nowrap;
}
/* Positioned always, not only while selected or focused. Switching a cell's
   `position` as the range moved over it made the browser lay the whole table
   out again on every mouse move -- 30 ms a frame on the 1,480-cell Room
   Configuration grid, which is what made a drag wade. A tint and an inset
   shadow are paint; a position change is layout. */
table.grid td { position: relative; }
/* The column header, in the workbook's pale blue -- `Sr.No`, `Description`,
   `UOM` on #DEEBF7 across every BOQ sheet it has. Bold for the same reason the
   fill is that blue: `Summary!A3` is bold, and a header that only half matches
   is a header that has started drifting.

   Top-aligned, so every column's name sits on one line across the header and
   its unit lines stack beneath it. Bottom-aligned, a column with two lines
   under its name and one with one line put their names a line apart: on the
   Flat Matrix, "Sub Station" and "Meter Room" (no toilets line) sat a line
   below "Shops" and "Flat", level with those columns' "shops" and "1BHK".
   ("Make this in alignment.") */
table.grid th {
  position: sticky; top: 0; z-index: 5; background: var(--wb-header);
  font-weight: 700; color: var(--wb-header-ink); font-size: 11px; padding: 5px 8px;
  text-align: right; vertical-align: top; border-bottom: 1px solid var(--line-strong);
}
table.grid th.left, table.grid td.left { text-align: left; }
table.grid td.left, table.grid th.left { padding-left: 10px; }
/* The last column has nothing to its right, so a right-aligned figure in it
   sits flush against the table's own edge and its final glyph reads as
   clipped -- `₹66.60 L` losing its L. Every grid that happens to end in a
   delete column has been fine by accident; one that ends in an Amount, like
   the lift schedule and the priced opening schedule, has not. The delete
   column keeps its own spacing (`td.act`), which is centred rather than
   right-aligned. */
table.grid th:last-child:not(.left), table.grid td:last-child:not(.left):not(.act) {
  padding-right: 8px;
}
/* The MEP Summary's band tables are written by hand rather than built by
   `createGrid`, so their cells carry neither `.cell` nor `.derived` and
   inherit the `padding: 0` above -- which put `₹1.39 Cr` hard against its own
   right border and read as a figure with its last glyph cut off. The inline
   `padding-left` on the Remarks column still wins, as it should. */
table.grid[data-band] th, table.grid[data-band] td,
table.grid#mepTotals td, table.grid#uplifts td { padding-left: 8px; padding-right: 8px; }
/* The Cost summary's last steps. The Amount column is the width of the one in
   the section table above, so the figures stand in one column down the page;
   the label takes the rest. */
table.grid#uplifts td.amt {
  width: 150px; font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: 12px;
}
table.grid#uplifts tr.total-row td.amt, table.grid#uplifts tr.subtotal-row td.amt {
  font-weight: 700;
}
/* Row hover has to be visible to do its job. #fafbfd on white was a 1.02:1
   difference -- present in the stylesheet, absent on the screen -- and losing
   which row your eye is on is how a figure gets read against the wrong label. */
table.grid tbody tr:hover td { background: var(--row-hover); }
table.grid tbody tr:hover td.derived { background: var(--row-hover-derived); }

/* the two states that carry the whole design argument */
td.cell {
  background: var(--surface); font-variant-numeric: tabular-nums; font-family: var(--mono);
  font-size: 12px; cursor: cell; padding: 0 8px; color: var(--ink);
}
td.cell:focus { outline: 2px solid var(--accent); outline-offset: -2px; background: var(--surface); }
td.cell.zero { color: var(--ink-3); }
td.derived {
  background: var(--derived-bg); color: var(--derived-ink);
  font-variant-numeric: tabular-nums; font-family: var(--mono); font-size: 12px;
  padding: 0 8px; cursor: help;
}
td.derived:hover { background: var(--derived-hover); color: var(--ink); }

/* Work Status: the building as a picture, on one ramp from red through
   yellow to green.

   The workbook says this in three conditional formats on
   `Work Status!J1:AG30` -- amber at nought, yellow between, green at one.
   Ten steps and a colour of its own at 100 is that idea read as a gradient:
   a column of cells shows where a trade has got to before anybody reads a
   number off it, and 99 and 100 stay different answers, because one of them
   means somebody can sign.

   Light on purpose. These sit behind thirty rows of figures, not on a chart,
   and a screen of full-strength red is a screen nobody can read the numbers
   on. Every step carries the same dark ink and the tightest pair measures
   9.9:1.

   A gradient's neighbours are *meant* to be close, so tone is never the only
   channel: every cell prints its number and an NA cell says NA.

   Which step a cell is in is not decided here. The edges live in
   `qs_engine.rules.work_status.PROGRESS_BANDS` and arrive with the figure;
   a stylesheet is a place nothing can test. This file only says what each
   step looks like -- one rule per step, and a test checks none is missing.

   The same fills serve both themes: a data colour that changes with the
   theme is a different answer to the same question. */
:root {
  --ws-p1:  #ffc9c9;   /* 0-10   red */
  --ws-p2:  #ffd3b3;
  --ws-p3:  #ffdda6;
  --ws-p4:  #ffe6a3;
  --ws-p5:  #fff0a8;   /* 41-50  yellow, the middle of the ramp */
  --ws-p6:  #f6f3a6;
  --ws-p7:  #e4efa4;
  --ws-p8:  #cfe9a4;
  --ws-p9:  #b6e2a6;
  --ws-p10: #9bd9a4;   /* 91-99  green */
  --ws-done: #6bcf86;  /* 100    finished, and told apart from 99 */
  --ws-ink:  #101010;
}
td.cell.ws-p1,  td.derived.ws-p1  { background: var(--ws-p1); }
td.cell.ws-p2,  td.derived.ws-p2  { background: var(--ws-p2); }
td.cell.ws-p3,  td.derived.ws-p3  { background: var(--ws-p3); }
td.cell.ws-p4,  td.derived.ws-p4  { background: var(--ws-p4); }
td.cell.ws-p5,  td.derived.ws-p5  { background: var(--ws-p5); }
td.cell.ws-p6,  td.derived.ws-p6  { background: var(--ws-p6); }
td.cell.ws-p7,  td.derived.ws-p7  { background: var(--ws-p7); }
td.cell.ws-p8,  td.derived.ws-p8  { background: var(--ws-p8); }
td.cell.ws-p9,  td.derived.ws-p9  { background: var(--ws-p9); }
td.cell.ws-p10, td.derived.ws-p10 { background: var(--ws-p10); }
td.cell.ws-done, td.derived.ws-done { background: var(--ws-done); }
td.cell[class*="ws-p"], td.cell.ws-done,
td.derived[class*="ws-p"], td.derived.ws-done {
  color: var(--ws-ink); font-weight: 600;
}
td.cell.ws-na, td.derived.ws-na {
  background: var(--surface-2); color: var(--ink-3);
  font-size: 10.5px; letter-spacing: .06em;
}
/* No band at all: a trade that reaches no floor, so there is no figure to
   put in one. */
td.cell.ws-none, td.derived.ws-none { background: var(--surface); }
/* Work Status's totals bar: each trade's % completion under its column,
   `Work Status!A34`. The figure is a button so it can be reached from the
   keyboard and opens its working like every grey figure does; it reads as
   the figure, in the cell's own ink, not as a control sitting on it. The
   hit area is the whole cell. */
tr.total-row td.derived[class*="ws-"] { padding: 0; }
.ws-foot-pct {
  display: block; width: 100%; min-height: 28px; padding: 4px 8px;
  border: 0; background: none; color: inherit; font: inherit;
  font-variant-numeric: tabular-nums; text-align: right; cursor: pointer;
  text-decoration: underline dotted; text-underline-offset: 3px;
}
.ws-foot-pct:hover { text-decoration-style: solid; }
.ws-foot-pct:focus-visible {
  outline: 2px solid var(--ink); outline-offset: -2px;
  box-shadow: inset 0 0 0 4px rgba(255,255,255,.85);
}
/* Focus survives the tone: the outline is what says where you are, and a
   filled background must not be what a keyboard user reads instead. White
   inside the dark outline, so the ring reads on the red and on the green
   alike. */
td.cell[class*="ws-"]:focus {
  outline: 2px solid var(--ink); outline-offset: -2px;
  box-shadow: inset 0 0 0 4px rgba(255,255,255,.85);
}
/* A row hover must not repaint a step. `table.grid tbody tr:hover td.cell`
   sets the hover ground for every cell, which would flatten the whole ramp
   to one colour as the mouse passed. Each step holds its own; the hover is
   read off the row's first column instead. */
table.grid tbody tr:hover td.cell.ws-p1   { background: var(--ws-p1); }
table.grid tbody tr:hover td.cell.ws-p2   { background: var(--ws-p2); }
table.grid tbody tr:hover td.cell.ws-p3   { background: var(--ws-p3); }
table.grid tbody tr:hover td.cell.ws-p4   { background: var(--ws-p4); }
table.grid tbody tr:hover td.cell.ws-p5   { background: var(--ws-p5); }
table.grid tbody tr:hover td.cell.ws-p6   { background: var(--ws-p6); }
table.grid tbody tr:hover td.cell.ws-p7   { background: var(--ws-p7); }
table.grid tbody tr:hover td.cell.ws-p8   { background: var(--ws-p8); }
table.grid tbody tr:hover td.cell.ws-p9   { background: var(--ws-p9); }
table.grid tbody tr:hover td.cell.ws-p10  { background: var(--ws-p10); }
table.grid tbody tr:hover td.cell.ws-done { background: var(--ws-done); }
table.grid tbody tr:hover td.cell.ws-na   { background: var(--surface-2); }
/* The Schedule's two kinds of row, told apart in the two cells that carry
   the difference.

   A row that is a floor is named by the building -- rename it on Work Status
   and this follows -- so its name reads as a label rather than as something
   to type over, and the endpoint refuses the rename anyway. A start date
   that was typed is the row saying it does not follow the one above, which
   is how Finishing overlaps the frame; it wears the accent because it is the
   one thing on the row somebody decided. */
td.cell.from-floor { color: var(--ink-2); }
td.cell.own-start { color: var(--accent); font-weight: 600; }
/* A calculated cell opens its working when clicked, and says so. A `note`
   cell is grey for the same reason -- you cannot type in it -- but there is
   nothing behind it to open, so it must not invite the click. */
td.derived.clickable { cursor: pointer; }
td.derived.clickable:hover {
  background: var(--accent-soft); color: var(--ink);
  box-shadow: inset 0 -1px 0 var(--accent);
}
td.derived.note { cursor: default; color: var(--ink-3); }
/* A headline tile whose figure can be questioned. */
.tile.clickable { cursor: pointer; transition: border-color .12s, box-shadow .12s; }
.tile.clickable:hover {
  border-color: var(--accent); box-shadow: 0 1px 6px rgba(60, 90, 180, .12);
}
td.derived.note:hover { background: var(--derived-bg); color: var(--ink-3); }
td.label { background: var(--surface); padding: 0 10px; color: var(--ink); }
td.total, th.total { font-weight: 640; background: var(--total-bg); }
/* The row every table ends on, in the amber `Summary!B15` uses for the figure
   the meeting is actually about -- and bold across, label and figure, because
   `Summary!B15` and its `C15` both are.

   Bold here rather than in each screen's `<strong>`: half of them wrapped
   their figures and half did not, so "Total" printed Bold beside a figure at
   640, which no system font has a face for and Segoe rounds down to Semibold.
   A rule cannot be forgotten by the next table. */
tr.total-row td {
  font-weight: 700; background: var(--wb-total); color: var(--wb-total-ink);
  border-top: 1px solid var(--line-strong);
}
tr.total-row td.total, tr.total-row th.total { background: var(--wb-total); }
/* A subtotal inside a table -- a heading folding the rows under it -- in the
   green the workbook puts on `Excavation Total` and `RCC Total`. It is a
   different answer from the grand total and the workbook colours it
   differently, so this does too -- and bolds it just the same: `Civil BOQ (LD
   Patel R1)!B18` and its figure at `F18` are both bold. */
tr.subtotal-row td {
  font-weight: 700; background: var(--wb-subtotal); color: var(--wb-subtotal-ink);
}
tr.subtotal-row td.cell, tr.subtotal-row td.derived {
  background: var(--wb-subtotal); color: var(--wb-subtotal-ink);
}
/* Under the pointer a coloured row stays its colour, a shade deeper. Written
   at the grid's own weight -- `table.grid tbody tr:hover td` -- because the
   plain `tr.subtotal-row:hover td` this was lost to it, and the amber and the
   green went grey the moment they were pointed at. */
table.grid tbody tr.subtotal-row:hover td,
table.grid tbody tr.subtotal-row:hover td.derived {
  background: var(--wb-subtotal); filter: brightness(.97);
}
table.grid tbody tr.total-row:hover td,
table.grid tbody tr.total-row:hover td.derived {
  background: var(--wb-total); filter: brightness(.97);
}
/* Inside a coloured row every cell wears that row's ink, the quiet ones too.
   `--ink-3` is a grey chosen against white: it measures 4.3:1 on the amber
   and 3.7:1 on the green, under the 4.5:1 this platform holds itself to, so a
   `0`, a note or a muted pill in one of these rows takes the row's own deep
   version of its hue instead of staying grey. */
tr.total-row td.cell.zero, tr.total-row td.derived.note, tr.total-row .muted,
tr.total-row .cell-sub, tr.total-row button.row-del {
  color: var(--wb-total-ink);
}
tr.total-row td.derived.note:hover {
  background: var(--wb-total); color: var(--wb-total-ink);
}
tr.subtotal-row td.cell.zero, tr.subtotal-row td.derived.note,
tr.subtotal-row .muted, tr.subtotal-row .cell-sub, tr.subtotal-row button.row-del {
  color: var(--wb-subtotal-ink);
}
tr.subtotal-row td.derived.note:hover {
  background: var(--wb-subtotal); color: var(--wb-subtotal-ink);
}
td.missing { color: var(--bad); font-weight: 600; }
td.editing { padding: 0; }
td.editing input {
  width: 100%; height: 100%; border: 0; outline: 2px solid var(--accent);
  outline-offset: -2px; padding: 0 7px; text-align: right;
  font-family: var(--mono); font-size: 12px; background: var(--surface);
}
/* A text cell edits from the left, where its text is read from. */
td.editing input.left { text-align: left; }
.sel { background: var(--accent-soft) !important; }

.tag {
  display: inline-block; padding: 1px 6px; border-radius: 4px; font-size: 10.5px;
  background: var(--derived-bg); color: var(--ink-2); font-weight: 500;
}
.tag.bad  { background: var(--bad-soft);  color: var(--bad); }
.tag.warn { background: var(--warn-soft); color: var(--warn); }
.tag.ok   { background: var(--ok-soft);   color: var(--ok); }

/* --- Opening a row ----------------------------------------------------------
   The name of a unit type is the way in to its rooms. It was a bare blue
   underlined link in a monospaced cell, which reads as a URL rather than a
   name. It is the name now, in the table's own ink and weight, with a
   chevron that says "this opens"; hover and focus turn it the accent
   colour so it still declares itself as the way in. */
a.open-row {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--ink); font-weight: 600; text-decoration: none;
  font-family: var(--sans); padding: 2px 4px; margin: -2px -4px;
  border-radius: 5px;
}
a.open-row .chev {
  color: var(--ink-3); font-weight: 400; font-size: 15px; line-height: 1;
  transition: transform .12s ease, color .12s ease;
}
a.open-row:hover, a.open-row:focus-visible {
  color: var(--accent); background: var(--accent-soft); outline: none;
}
a.open-row:focus-visible .chev { color: var(--accent); transform: translateX(2px); }
/* The nudge under a mouse only: a tap on a phone left the chevron pushed
   over until the next tap somewhere else. */
@media (hover: hover) and (pointer: fine) {
  a.open-row:hover .chev { color: var(--accent); transform: translateX(2px); }
}
a.open-row:hover .chev {
  color: var(--accent);
}

/* --- Incomplete data --------------------------------------------------------
   A unit type short of the room data the take-off needs -- an area, a
   perimeter, a door, a kitchen's counters -- is tinted, faintly, across the
   row, and says in words what is missing. Pale on purpose: it marks where
   the work is, it does not raise an alarm. Meaning never rides on the
   colour alone -- the Data cell carries the text. */
.grid-wrap tr.incomplete td {
  background: color-mix(in srgb, var(--bad) 5%, var(--surface));
}
.grid-wrap tr.incomplete td.sticky-1 {
  box-shadow: inset 3px 0 0 color-mix(in srgb, var(--bad) 35%, var(--surface));
}
.status { display: inline-flex; align-items: center; gap: 6px; font-family: var(--sans); }
.status .dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
}
.status.bad { color: var(--bad); font-weight: 600; }
.status.bad .dot { background: color-mix(in srgb, var(--bad) 55%, var(--surface)); }
.status.ok { color: var(--ok); }
.status.ok .dot { background: color-mix(in srgb, var(--ok) 55%, var(--surface)); }
.status .why { color: var(--ink-2); font-weight: 400; }

/* ---------- buttons & inputs ---------- */
button.btn {
  padding: 5px 11px; border-radius: 6px; border: 1px solid var(--line-strong);
  background: var(--surface); cursor: pointer; font-size: 12.5px;
}
button.btn:hover { background: var(--surface-2); border-color: var(--ink-3); }
button.btn.primary { background: var(--ink); color: var(--on-ink); border-color: var(--ink); }
button.btn.primary:hover { background: var(--ink-hover); }
select.sel-input, select.text-input, input.text-input {
  padding: 5px 8px; border-radius: 6px; border: 1px solid var(--line-strong);
  background: var(--surface); color: var(--ink); font: inherit;
}

/* A link on a screen is the app's own: the accent, underlined a little clear
   of the letters, and the same colour visited or not -- a figure you have
   opened before is not a different kind of link. Eight screens were showing
   the browser's #0000ee and its :visited purple, because nothing said
   otherwise. Every link with a class of its own still says its own. */
a { color: var(--accent); text-underline-offset: 2px; text-decoration-thickness: 1px; }

/* The parts of a form the browser draws -- a checkbox's tick, a radio's dot,
   the caret in a box -- in the app's accent rather than the machine's blue. */
:root { accent-color: var(--accent); }
input, textarea { caret-color: var(--accent); }

/* ---------- derivation panel ---------- */
#panel {
  position: fixed; right: 0; top: 56px; bottom: 0; width: 400px;
  background: var(--surface); border-left: 1px solid var(--line);
  box-shadow: -8px 0 28px rgba(20,30,50,.09); overflow-y: auto; z-index: 30;
}
.panel-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 12px 14px; border-bottom: 1px solid var(--line);
  position: sticky; top: 0; background: var(--surface);
}
#panelClose {
  border: 0; background: none; font-size: 20px; cursor: pointer;
  color: var(--ink-3); line-height: 1; padding: 0 4px;
}
#panelBody { padding: 14px; font-size: 12.5px; }
.deriv-value {
  font-family: var(--mono); font-size: 21px; font-weight: 600;
  letter-spacing: -.02em; margin-bottom: 2px;
}
.deriv-expr {
  font-family: var(--mono); background: var(--derived-bg); padding: 9px 10px;
  border-radius: 6px; margin: 10px 0; font-size: 12px; line-height: 1.6;
  word-break: break-word;
}
.deriv-inputs { display: grid; gap: 6px; margin: 10px 0; }
.deriv-input {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 6px 8px; border-radius: 5px; background: var(--surface-2);
  border: 1px solid var(--line);
}
.deriv-input .n { color: var(--ink-2); }
.deriv-input .v { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.deriv-src { color: var(--ink-3); font-size: 11px; }
.deriv-note {
  background: var(--warn-soft); border: 1px solid var(--warn-line); color: var(--notice-ink);
  padding: 9px 10px; border-radius: 6px; margin-top: 10px; line-height: 1.5;
}
.deriv-excel {
  font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); margin-top: 10px;
}
/* Which floor height the rooms are being read at. Inline with the sentence
   that explains it, because the choice and its consequence belong together. */
.height-pick { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.height-pick select {
  font: inherit; font-size: 12px; padding: 2px 6px; border-radius: 5px;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  max-width: 320px;
}

/* A small select that sits in a card heading -- which is a flex row, so it is
   pushed to the end rather than left sitting where the title should be. */
.ht-pick {
  font: inherit; font-size: 12px; padding: 2px 22px 2px 6px; border-radius: 5px;
  border: 1px solid var(--line); color: var(--ink);
  /* One chevron, the app's -- the same one every other select wears. */
  appearance: none; -webkit-appearance: none; cursor: pointer;
  background: var(--surface) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23636c79' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat right 7px center;
  max-width: 260px; margin-left: auto;
}

/* Internal finishes: a unit type the screen has not been scrolled to is not
   laid out or painted until it is near. AVS draws every room of all 34 types
   at once -- 33,000 elements -- and laying them all out held the page for
   2.8 s on a desktop and 13 s on a phone; with this, 1.0 s and 6 s. Nothing
   is left out: every block is in the page, so find-in-page, copying and a
   link to one row all reach it. `auto` in the size makes the browser
   remember a block's real height once it has drawn it, so the scrollbar
   settles instead of jumping. A browser that does not know the property
   ignores it and draws everything, as before. */
#sheet > .card { content-visibility: auto; contain-intrinsic-size: auto 1500px; }

/* MEP's tab strip -- the workbook's own sheet tabs, as the screen's.
   ===========================================================================
   Links, not buttons, because each one is a place: the trade rides in the hash
   so a refresh after an edit comes back to the sheet being worked on, and the
   browser's own Back goes to the sheet before it. Which is exactly what a
   person expects of tabs that are named after sheets. */
.mep-tabs {
  display: flex; flex-wrap: wrap; gap: 2px; margin: 18px 0 0;
  border-bottom: 1px solid var(--line); padding: 0 2px;
}
.mep-tab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; font-size: 12.5px; line-height: 1.2;
  color: var(--ink-3); text-decoration: none; white-space: nowrap;
  border: 1px solid transparent; border-bottom: none;
  border-radius: 6px 6px 0 0; margin-bottom: -1px;
  background: none; cursor: pointer;
}
.mep-tab:hover { color: var(--ink); background: var(--surface-2); }
.mep-tab.on {
  color: var(--ink); background: var(--surface);
  border-color: var(--line); border-bottom-color: var(--surface);
  font-weight: 600;
}
.mep-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.mep-tab-add { font-family: inherit; color: var(--ink-3); }
.mep-tab-add:hover { color: var(--accent); }

/* An electrical sheet: its head, as the budget lays out rows 3-19, and the
   schedules under it. The engine's split of the sheet between bands -- one
   sheet, two bands of the Summary -- is the foot of the head, under the
   total it divides, rather than a row of boxes inside the card above it. */
table.el-sheet th.el-sr, table.el-sheet td.el-sr { width: 44px; text-align: center; }
table.el-sheet th.el-amt { width: 9.5em; }
/* A line's name wraps before the amount beside it is pushed off a phone. */
table.el-sheet td.left { white-space: normal; padding-block: 5px; line-height: 1.35; }
table.el-sheet tr.el-share-row td {
  height: 26px; font-size: 12px; color: var(--ink-2);
  background: var(--surface); font-variant-numeric: tabular-nums;
}
table.el-sheet tr.el-share-row td.left { padding-left: 22px; }

/* A line in the head moves you to its schedule. It is a control, drawn as
   the quiet link a sheet's index is -- the row's own ink, underlined in the
   line colour -- not the browser's #0000ee, and not a :visited purple that
   every line wore once `#` had been visited. */
.el-jump {
  font: inherit; color: var(--ink); background: none; border: 0; padding: 0;
  text-align: left; cursor: pointer;
  text-decoration: underline; text-decoration-color: var(--line-strong);
  text-underline-offset: 3px; text-decoration-thickness: 1px;
  transition: color 120ms ease, text-decoration-color 120ms ease;
}
@media (hover: hover) and (pointer: fine) {
  .el-jump:hover { color: var(--accent); text-decoration-color: currentColor; }
}
.el-jump:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px;
                         border-radius: 2px; }
.el-form select.sel-input { width: 100%; font: inherit; }

/* A trade's remark: one quiet line under the title, not a padded paragraph
   with a paragraph's margins -- on its own it read as a gap with a word in. */
.trade-remarks {
  margin: 0; padding: 10px 16px; font-size: 13px; color: var(--ink-3);
}
.trade-remarks span {
  font-size: 11px; font-weight: 650; letter-spacing: .04em;
  text-transform: uppercase; margin-right: 6px;
}
/* The first heading of a trade's body sits close under the title. */
#tradeGrid > .el-sub:first-child { margin-top: 14px; }
/* Under a remark, which already leaves its own room below it. */
.trade-remarks + #tradeGrid > .el-sub:first-child { margin-top: 4px; }

/* Plumbing: the PHE matrix, a block per tower above the rate rows. */
.pl-tower { margin: 2px 0 16px; }
.pl-tower-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 4px 16px 8px; font-size: 13px;
}
/* A tower of eleven flats is eleven columns: a flat's name ("Wing B Flat 1")
   wraps onto two lines rather than pushing the tower's total off the card. */
.pl-tower table.grid thead th { white-space: normal; line-height: 1.25; }
/* The MEP Summary on a project of towers: a column group per tower, as
   Shell Colony's Summary heads E:G "Tower 01" and H:J "Tower 2". */
table.tw-summary thead th.tw-group {
  text-align: center; border-bottom: 1px solid var(--line);
}
table.tw-summary thead th { white-space: normal; line-height: 1.25; }
/* A heading spanning both rows sits on the row the figures hang from. */
table.tw-summary thead th[rowspan] { vertical-align: bottom; }
table.el-sheet tr.el-group td { background: var(--surface-2); padding-top: 10px; }
table.el-sheet tr.el-group strong,
.el-top { text-transform: uppercase; letter-spacing: .04em; font-size: 12px; }
.el-band-chip { margin-left: 8px; padding: 1px 8px; font-size: 10.5px;
                font-weight: 600; vertical-align: 1px; }
.el-sub { margin: 20px 16px 8px; font-size: 13px; font-weight: 650; }
.el-sub .muted { font-weight: 400; }
.el-what { border: 0; padding: 0; margin: 0 0 12px; display: grid; gap: 6px; }
.el-what legend { padding: 0; margin-bottom: 4px; }
.el-what label { display: flex; align-items: center; gap: 8px; font-weight: 400; }
.el-ok { color: var(--ok); font-weight: 600; }
.el-flags { margin: 0; padding-left: 18px; font-size: 12.5px; line-height: 1.5;
            color: var(--ink-2); }

/* A height cluster in the panel, typed into rather than only read. */
.cluster-edit { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.cluster-edit label { font-size: 12px; color: var(--ink-3); }
.cluster-edit input {
  width: 88px; padding: 5px 7px; font: inherit; font-family: var(--mono);
  text-align: right; border: 1px solid var(--line); border-radius: 5px;
  background: var(--surface); color: var(--ink);
}
.cluster-edit .btn { width: auto; padding: 5px 10px; font-size: 12px; }

h4.deriv-h { margin: 16px 0 6px; font-size: 11px; text-transform: uppercase;
             letter-spacing: .06em; color: var(--ink-3); }

/* ---------- findings ---------- */
.finding {
  display: grid; grid-template-columns: 72px 168px 1fr; gap: 10px;
  padding: 9px 12px; border-bottom: 1px solid var(--line); align-items: baseline;
}
.finding:last-child { border-bottom: 0; }
.finding .rule { font-family: var(--mono); font-size: 11px; color: var(--ink-2); }
.finding .msg { color: var(--ink); line-height: 1.5; }

/* ---------- misc ---------- */
.kv { display: grid; grid-template-columns: auto 1fr; gap: 4px 16px; align-items: baseline; }
.kv dt { color: var(--ink-2); }
.kv dd { margin: 0; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.muted { color: var(--ink-3); }
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.right { text-align: right; }
.explain { color: var(--ink-2); font-size: 11.5px; line-height: 1.5;
           padding: 2px 12px 9px 12px; max-width: 100ch; }

#toast {
  position: fixed; bottom: 18px; left: 50%; transform: translateX(-50%);
  background: var(--ink); color: var(--on-ink); padding: 9px 15px; border-radius: 7px;
  font-size: 12.5px; z-index: 60; box-shadow: var(--shadow); max-width: 70ch;
}
#toast.bad { background: var(--bad); }
/* The toast's one action -- Undo, after a delete. On the dark toast it is a
   quiet button in the toast's own ink, not a second pill competing with the
   message it belongs to. */
.toast-act {
  margin-left: 10px; padding: 4px 9px; border: 0; border-radius: 5px;
  background: color-mix(in srgb, var(--on-ink) 16%, transparent);
  color: var(--on-ink); font: inherit; font-weight: 650; cursor: pointer;
}
.toast-act:hover { background: color-mix(in srgb, var(--on-ink) 26%, transparent); }
.toast-act:focus-visible { outline: 2px solid var(--on-ink); outline-offset: 1px; }

.tile-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); gap: 12px; }
.tile {
  background: var(--surface); border: 1px solid var(--line); border-radius: 9px;
  padding: 12px 14px; box-shadow: var(--shadow);
}
.tile .k { color: var(--ink-3); font-size: 10.5px; text-transform: uppercase;
           letter-spacing: .05em; margin-bottom: 3px; }
.tile .v { font-size: 21px; font-weight: 640; font-variant-numeric: tabular-nums;
           letter-spacing: -.02em; }
.tile .s { color: var(--ink-3); font-size: 11.5px; margin-top: 2px; }
/* A tile whose value is words rather than a figure: a date, "Not provided",
   "18 days late". 21px tabular digits are right for 913 and wrong for
   1 Sep 2026, which wraps the tile. */
.tile .v.sm { font-size: 15px; font-weight: 600; letter-spacing: 0; }

/* ---------- a notice with something to do about it ---------- */
/* Not a toast: this describes a state of the data that will still be true
   tomorrow, and it carries the action that ends it. */
.notice {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  margin: 10px 0 0; padding: 10px 13px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--surface-2);
  font-size: 12.5px; line-height: 1.5;
}
.notice.warn {
  border-color: color-mix(in srgb, var(--warn) 45%, var(--line));
  background: color-mix(in srgb, var(--warn) 9%, var(--surface));
}
.notice strong { font-weight: 620; }
.notice .btn { margin-left: auto; width: auto; flex: none; }
/* A standing state of the Cost Sheet's data, said beside its table. */
.cs-lost { padding-bottom: 0; }
.cs-lost .notice { margin-top: 0; }

/* ---------- rename in place ---------- */
/* The heading is the control. Underlined on hover rather than styled as a
   permanent input, so the page still reads as a heading. */
.renameable { cursor: text; border-radius: 4px; padding: 0 3px; margin: 0 -3px; }
.renameable:hover { background: var(--surface-2);
                    box-shadow: inset 0 -1px 0 0 var(--ink-3); }
.renameable:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.rename-input { font: inherit; padding: 0 4px; min-width: 8ch; width: 14ch; }

/* ---------- unit mix, and what it is made of ---------- */
/* The class is the heading and the types under it are the answer. A tile with
   one number could not tell "one type on every floor" from "four types sharing
   a label", which are different buildings to price. */
.mix-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
           gap: 12px; align-items: start; }
.mix-group {
  background: var(--surface); border: 1px solid var(--line); border-radius: 9px;
  box-shadow: var(--shadow); overflow: hidden;
}
.mix-head { padding: 11px 14px 9px; border-bottom: 1px solid var(--line); }
.mix-head .k { color: var(--ink-3); font-size: 10.5px; text-transform: uppercase;
               letter-spacing: .05em; display: block; }
.mix-head .v { font-size: 21px; font-weight: 640; font-variant-numeric: tabular-nums;
               letter-spacing: -.02em; }
.mix-head .s { color: var(--ink-3); font-size: 11.5px; margin-left: 7px; }
.mix-list { list-style: none; margin: 0; padding: 4px 0; }
.mix-list li {
  display: grid; grid-template-columns: 1fr auto auto; gap: 8px;
  align-items: baseline; padding: 4px 14px; font-size: 12.5px;
}
.mix-list li + li { border-top: 1px dashed var(--line); }
.mix-list li:hover { background: var(--surface-2); }
.mix-list .mono { font-variant-numeric: tabular-nums; font-weight: 600; }
.mix-list .muted { font-size: 11px; }

/* ---------- generate floors ---------- */
/* The Generate floors form. Sized for the 400px panel it lives in: four
   tight columns that never wrap -- the unit sits inside the height box, the
   control keeps to one word -- and a rhythm of 34px rows so nine kinds of
   floor read as one list rather than nine cards. */
.stack-form { width: 100%; border-collapse: collapse; margin: 10px 0 14px; table-layout: fixed; }
.stack-form th, .stack-form td {
  padding: 3px 4px; text-align: left; white-space: nowrap; vertical-align: middle;
}
.stack-form thead th {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-3); font-weight: 600; border-bottom: 1px solid var(--line);
  padding-bottom: 6px;
}
.stack-form tbody th { font-weight: 500; font-size: 13px; width: 84px; }
.stack-form tbody tr { height: 34px; }
/* A second height for the same kind of floor sits under the first, marked
   "and" where the type would be, with the type's name kept to one row. */
.stack-form tr.extra th { font-weight: 400; padding-left: 16px; }
.stack-form tr.extra th .muted { font-size: 12px; }
.stack-form td.act { width: 72px; text-align: right; padding-right: 0; }
.stack-form input.num {
  width: 64px; height: 28px; padding: 0 7px; text-align: right;
  font-family: var(--mono); font-size: 12.5px; font-variant-numeric: tabular-nums;
}
.stack-form input.text-input.num { width: 64px; }
/* An input with its unit inside the box, so "3.1 m" is one thing. */
.stack-form .with-unit {
  display: inline-flex; align-items: center; height: 28px; width: 86px;
  border: 1px solid var(--line-strong); border-radius: 6px; background: var(--surface);
}
.stack-form .with-unit input.num {
  border: 0; outline: 0; background: transparent; flex: 1; min-width: 0; width: 100%;
  padding-right: 4px; height: 26px;
}
.stack-form .with-unit:focus-within {
  border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft);
}
.stack-form .with-unit .unit {
  color: var(--ink-3); font-size: 11.5px; padding-right: 7px; flex: none;
}
.link-btn {
  border: 0; background: none; color: var(--accent); font: inherit;
  font-size: 12px; cursor: pointer; padding: 3px 6px; border-radius: 4px;
  white-space: nowrap;
}
.link-btn:hover, .link-btn:focus-visible { background: var(--accent-soft); outline: none; }
.stack-form .row-del { margin-right: 4px; }

/* ---------- row actions ---------- */
.grid-actions {
  padding: 9px 12px; border-top: 1px solid var(--line); background: var(--surface-2);
  display: flex; gap: 8px; align-items: center;
}
td.act { background: var(--surface); text-align: center; width: 34px; padding: 0; }
button.row-del {
  border: 0; background: none; color: var(--ink-3); cursor: pointer;
  font-size: 17px; line-height: 1; padding: 2px 6px; border-radius: 4px;
}
button.row-del:hover { background: var(--bad-soft); color: var(--bad); }
/* The same way out, one axis over. On the Flat Matrix a unit type *is* a
   column, so a screen that can add one and not remove it is a screen you
   have to leave to undo a mistake made on it. It sits beside the column's
   name, quiet until the header is hovered -- twenty-five trades with a red
   cross each would read as a row of warnings. */
table.grid thead button.col-del {
  border: 0; background: none; color: inherit; cursor: pointer;
  /* Top-aligned in the line, so its 15px glyph does not push the column's
     name 2px below the names of the columns without one. */
  vertical-align: top;
  font-size: 15px; line-height: 1; padding: 0 4px; margin-left: 4px;
  border-radius: 4px; opacity: 0; transition: opacity var(--t-quick, .12s);
}
table.grid thead th:hover button.col-del { opacity: .65; }
table.grid thead button.col-del:hover,
table.grid thead button.col-del:focus-visible {
  opacity: 1; background: var(--bad); color: #fff;
}
/* Never invisible to a keyboard: it fades in on hover for the mouse, and
   tabbing to it shows it whether or not the header is hovered. */
table.grid thead button.col-del:focus-visible {
  outline: 2px solid var(--wb-header-ink); outline-offset: 1px;
}
td.sel-cell { background: var(--surface); cursor: pointer; padding: 0 8px; }
td.sel-cell:focus { outline: 2px solid var(--accent); outline-offset: -2px; }
td.sel-cell::after { content: " ⌄"; color: var(--ink-3); font-size: 11px; }
.add-form {
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  padding: 10px 12px; background: var(--surface-2); border-top: 1px solid var(--line);
}
.add-form input, .add-form select {
  padding: 5px 8px; border-radius: 6px; border: 1px solid var(--line-strong);
}

/* An in-table warning: red text, not the app's 30px logo badge. */
.warn-text { color: var(--bad); font-weight: 600; }

/* No margin of its own: inside `.bar-tools` the gap is the group's, and an
   extra 8px here is what made two of the seven spacings wider than the
   others for no reason anybody chose. */
.build { display: flex; align-items: center; }

/* Placeholder for content that is genuinely slow to fetch -- reserves the
   space so nothing jumps when it lands (CLS), and reads as progress rather
   than as an empty card. */
.skeleton-row {
  height: 22px; max-width: 280px; border-radius: 5px; margin-bottom: 10px;
  background: linear-gradient(90deg, var(--line) 25%, var(--surface) 37%, var(--line) 63%);
  background-size: 400% 100%; animation: shimmer 1.4s ease-in-out infinite;
}
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
@media (prefers-reduced-motion: reduce) { .skeleton-row { animation: none; } }

/* A screen that asks the user to decide something explains itself first.
   It is set apart by a faint wash of the accent, not a coloured stripe down
   one side -- the stripe was the one element on the page that looked like it
   came from a template. */
.explainer { background: color-mix(in srgb, var(--accent) 3%, var(--surface)); }
.explainer .card-body p { max-width: 78ch; line-height: 1.55; }

/* --- Range selection ----------------------------------------------------
   A QS works in blocks, so the grid shows one. Selected cells get a tint, the
   block gets a border, and the focused cell keeps its own outline -- so "where
   am I", "what is selected" and "where does the selection stop" stay three
   different questions with three different answers. */
.grid td.in-range { background: var(--accent-soft); }

/* The fill handle: the corner of the block, dragged to copy it down or across.
   Sized for a pointer -- a 4px square is a target nobody can hit -- and given
   a hit area larger than the mark itself. */
.grid-wrap .fill-handle {
  position: absolute; left: 0; top: 0; width: 8px; height: 8px;
  background: var(--accent); border: 1px solid var(--surface); border-radius: 2px;
  cursor: crosshair; z-index: 4;
  /* Moved with a transform, never re-parented: taking a node out of one
     cell and putting it in the next was a layout of the table per row. */
  will-change: transform;
}
.grid-wrap .fill-handle::before {
  content: ""; position: absolute; inset: -7px;      /* the part you can grab */
}
.grid td.fill-target {
  background: color-mix(in srgb, var(--accent-soft) 55%, transparent);
  outline: 1px dashed var(--accent); outline-offset: -1px;
}
.grid td.in-range.derived { background: color-mix(in srgb, var(--accent-soft) 70%, transparent); }
.grid td:focus { outline: 2px solid var(--accent); outline-offset: -2px; z-index: 3; }

/* The border round the block, drawn with inset shadows so it costs no layout
   and cannot shift a single pixel of the table. Four edges compose, so a
   one-cell selection gets all four and a tall column gets two. */
.grid td.range-t { box-shadow: inset 0  2px 0 0 var(--accent); }
.grid td.range-b { box-shadow: inset 0 -2px 0 0 var(--accent); }
.grid td.range-l { box-shadow: inset  2px 0 0 0 var(--accent); }
.grid td.range-r { box-shadow: inset -2px 0 0 0 var(--accent); }
.grid td.range-t.range-b { box-shadow: inset 0 2px 0 0 var(--accent), inset 0 -2px 0 0 var(--accent); }
.grid td.range-l.range-r { box-shadow: inset 2px 0 0 0 var(--accent), inset -2px 0 0 0 var(--accent); }
.grid td.range-t.range-l { box-shadow: inset 0 2px 0 0 var(--accent), inset 2px 0 0 0 var(--accent); }
.grid td.range-t.range-r { box-shadow: inset 0 2px 0 0 var(--accent), inset -2px 0 0 0 var(--accent); }
.grid td.range-b.range-l { box-shadow: inset 0 -2px 0 0 var(--accent), inset 2px 0 0 0 var(--accent); }
.grid td.range-b.range-r { box-shadow: inset 0 -2px 0 0 var(--accent), inset -2px 0 0 0 var(--accent); }
.grid td.range-t.range-b.range-l { box-shadow: inset 0 2px 0 0 var(--accent), inset 0 -2px 0 0 var(--accent), inset 2px 0 0 0 var(--accent); }
.grid td.range-t.range-b.range-r { box-shadow: inset 0 2px 0 0 var(--accent), inset 0 -2px 0 0 var(--accent), inset -2px 0 0 0 var(--accent); }
.grid td.range-t.range-l.range-r { box-shadow: inset 0 2px 0 0 var(--accent), inset 2px 0 0 0 var(--accent), inset -2px 0 0 0 var(--accent); }
.grid td.range-b.range-l.range-r { box-shadow: inset 0 -2px 0 0 var(--accent), inset 2px 0 0 0 var(--accent), inset -2px 0 0 0 var(--accent); }
.grid td.range-t.range-b.range-l.range-r {
  box-shadow: inset 0 2px 0 0 var(--accent), inset 0 -2px 0 0 var(--accent),
              inset 2px 0 0 0 var(--accent), inset -2px 0 0 0 var(--accent);
}

/* --- The grid is cells, so a drag over it selects cells -------------------
   Dragging down a column used to paint the browser's own text selection over
   the finish names while the grid painted its range underneath: two
   selections at once. Copy still works -- Ctrl+C copies the range, which is
   what a QS wanted from the drag in the first place. */
table.grid { user-select: none; -webkit-user-select: none; }
td.editing input { user-select: text; -webkit-user-select: text; }

/* --- The first column stays put -----------------------------------------
   Sticky positioning used to be written inline on every first cell, together
   with a hardcoded background. An inline background beats any class, so the
   first column never tinted with a selection, never lit up on row hover, and
   never took the totals row's shading. As a class the background comes from
   whatever the cell actually is. */
.grid td.sticky-1, .grid th.sticky-1 { position: sticky; left: 0; }
.grid td.sticky-1 { z-index: 2; }
.grid th.sticky-1 { z-index: 6; }          /* above both the header and the column */
.grid tfoot td.sticky-1 { background: var(--wb-total); color: var(--wb-total-ink); }
/* The edge, so a scrolled-away column reads as covered rather than as clipped. */
.grid-wrap.scrolled-x .sticky-1::after {
  content: ""; position: absolute; top: 0; right: 0; bottom: 0; width: 6px;
  transform: translateX(100%); pointer-events: none;
  background: linear-gradient(to right, rgba(20,30,50,.10), transparent);
}

/* A column heading may carry more than a name: what the column counts, and
   what one of them is worth. */
.col-sub { font-weight: 400; color: var(--ink-3); font-size: 10px; line-height: 1.35; }
/* The quieter half of a cell -- the source cell beside a reconciliation line,
   the material beside a finish, the floors a height line covers -- on the
   same line as the half it qualifies. A row is one line tall: as a `<div>`
   under the label, each of these made its row 34px in a table of 28s, and a
   column that is ragged by six pixels every third row reads as a fault before
   it reads as information. */
.cell-sub { margin-left: 8px; font-size: 11px; font-weight: 400; color: var(--ink-3); }
.cell-sub.mono { font-size: 10.5px; }
/* The unit under a column's name sits on the workbook's pale blue, where the
   grey reads 4.4:1 -- just under the 4.5:1 minimum. It takes the header's own
   ink, held back so it still reads as the quieter half of the heading. */
table.grid th .col-sub { color: var(--wb-header-ink); opacity: .75; }

.grid-status {
  padding: 5px 10px; font-size: 11px; color: var(--ink-3);
  border-top: 1px solid var(--line); background: var(--surface);
}

/* Adding a deduction from the panel that shows it: another opening in this
   room's walls, or the other side of a door already in the flat. */
.deriv-add { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); }
.deriv-add-row { display: flex; gap: 8px; align-items: center; margin-top: 6px; }
.deriv-add-row select { flex: 1; min-width: 0; }
.deriv-add .deriv-input { align-items: center; gap: 10px; }

/* The shortcuts, where they are needed rather than in a manual. */
.grid-help {
  display: flex; flex-wrap: wrap; gap: 4px 14px;
  padding: 7px 10px; font-size: 11px; color: var(--ink-3);
  border-top: 1px solid var(--line);
}
.grid-help kbd {
  font-family: var(--mono); font-size: 10.5px; background: var(--surface);
  border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 4px;
  padding: 1px 4px; color: var(--ink-2);
}

.link-quiet { color: var(--accent); text-decoration: none; font-size: 11.5px; }
.link-quiet:hover { text-decoration: underline; }

/* --- Polish pass --------------------------------------------------------
   Applying the checks that matter for a dense professional tool: a visible
   focus ring on everything reachable, one motion token, and motion that stops
   when the reader asks it to. */

.btn, .chip, a[data-nav], .link-quiet, select, input {
  transition: background var(--tempo) var(--ease),
              border-color var(--tempo) var(--ease),
              color var(--tempo) var(--ease);
}

/* Focus must be visible on every interactive element, not only grid cells.
   :focus-visible so a mouse click does not leave a ring behind. */
.btn:focus-visible,
a:focus-visible,
select:focus-visible,
input:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Every number in a table lines up, so a column can be scanned rather than
   read. Digits are the same width; a stray proportional figure breaks it. */
.grid td.cell, .grid td.derived, .grid td.total, .grid tfoot td {
  font-variant-numeric: tabular-nums;
}

/* The one exception to "never rely on colour": these carry an icon or a word
   as well, so the colour is reinforcement rather than the message. */
.warn-text::before { content: ""; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* Sign-in: the page is drawn by `theme.css` ("Sign in"); the error line
   below is shared with it. */
.field { display: block; margin-bottom: var(--space-3); }
.field > span { display: block; font-size: 12px; color: var(--ink-2);
                margin-bottom: var(--space-1); font-weight: 550; }
.field .text-input { width: 100%; }
/* What the field is for, under it rather than in a placeholder that vanishes
   the moment somebody types. */
.field-note, .field > .field-note {
  display: block; margin-top: var(--space-1); margin-bottom: 0; font-size: 11.5px;
  color: var(--ink-3); line-height: 1.45; font-weight: 400;
}
/* A button that takes something away. Coloured so the act reads before it is
   pressed, and disabled until a typed confirmation matches. */
.btn.danger { background: var(--bad); border-color: var(--bad); color: var(--on-ink); }
.btn.danger:hover:not(:disabled) { filter: brightness(.94); }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.field textarea.text-input { width: 100%; font: inherit; resize: vertical; }

/* Which kind of project this is: the first question the create form asks and
   the one that cannot be changed afterwards, so it is two labelled choices
   with their consequences beside them rather than a dropdown of two words. A
   real `<fieldset>` and real radios, so the arrow keys move between them and
   a screen reader reads the legend before either. */
.kind-pick {
  border: 1px solid var(--line); border-radius: 8px;
  padding: var(--space-2); margin-bottom: var(--space-3);
}
.kind-pick > legend {
  font-size: 12px; color: var(--ink-2); font-weight: 550; padding: 0 4px;
}
.kind-opt {
  display: flex; gap: 8px; align-items: flex-start; cursor: pointer;
  padding: 6px; border-radius: 6px;
}
.kind-opt:hover { background: var(--surface-2); }
.kind-opt input { margin-top: 2px; flex: none; accent-color: var(--accent); }
.kind-opt > span { display: block; font-size: 12.5px; }
.kind-opt strong { display: block; font-weight: 570; color: var(--ink); }
.kind-opt:has(input:checked) { background: var(--accent-soft); }
.kind-opt:focus-within { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Two answers side by side, the consequential one first. */
.choice-row { display: flex; gap: var(--space-2); }
.choice-row .btn { width: auto; flex: 1; }
/* One of two ways a finish can land: on the room in front of you, or on
   every room finished like it. The consequence is spelled out beside each,
   because the difference is money on rooms nobody is looking at. */
.choice { display: flex; gap: var(--space-2); align-items: flex-start;
          margin-bottom: var(--space-2); cursor: pointer; }
.choice input { margin-top: 3px; flex: none; }
.choice > span { font-size: 12.5px; color: var(--ink-2); line-height: 1.45; }
.choice strong { color: var(--ink); font-weight: 600; }
/* What the choice will actually touch, named room by room. Not an aside --
   it is the difference between one kitchen and every flat's kitchen. */
.choice .choice-what { font-style: normal; }
.choice:hover { color: var(--ink); }
.choice:has(input:checked) strong { color: var(--accent, var(--ink)); }

.signin-error {
  background: var(--bad-soft); color: var(--bad); border-radius: 5px;
  padding: var(--space-2) var(--space-3); font-size: 12.5px; margin: 0 0 var(--space-3);
}
.btn.primary { width: 100%; justify-content: center; }
/* Full width is right for the one button that submits a form, and wrong for a
   toolbar, where the primary action is one control among several and a
   full-bleed dark bar reads as a heading rather than a button. */
.toolbar .btn.primary { width: auto; }

/* --- Signed out ----------------------------------------------------------
   There is one thing to do on this screen, so there is one thing on it. The
   sidebar, the headline figures and the health chip all describe a project,
   and signed out there is no project to describe -- showing an empty shell of
   them is a worse answer than not showing them. */
/* Outside a project -- the login page and the project list -- there is no
   project for any of these to be about. `no-project` covers both; `signed-out`
   goes further and takes the name and the sign-out control with it. */
#app.no-project #sidebar,
#app.no-project #headline,
#app.no-project #healthChip,
#app.no-project .project-name,
#app.no-project .project-sub { display: none; }
#app.no-project { grid-template-columns: 1fr; grid-template-areas: "top" "main"; }
#app.signed-out #whoami { display: none; }
/* With nothing beside it the mark is the whole brand, so it carries the
   product's name rather than sitting alone. */
#app.no-project .brand::after {
  content: "QS Platform"; font-weight: 600; font-size: 13px; color: var(--ink-2);
  padding-left: 12px; border-left: 1px solid var(--line);
}
/* The column is centred; the scroll is not. `#main` *is* the scrolling box,
   so narrowing it to a centred 1180px drew its scrollbar at that column's
   right edge -- a bar hanging in open space nine-tenths of the way across a
   wide window, instead of at the window's own edge where a scrollbar belongs.

   The box stays full width and the content is centred with padding instead.
   One rule rather than anything keyed to children, because My Projects renders
   four siblings straight into `main` and a child rule would have to be kept in
   step with the screen. `max()` keeps the 20px gutter `#main` already has when
   the window is narrower than the column, and the sign-in page is untouched:
   `#app.signed-out #main { padding: 0 }` is two ids to this one's one. */
#app.no-project main {
  width: 100%;
  padding-left: max(20px, calc((100% - 1180px) / 2));
  padding-right: max(20px, calc((100% - 1180px) / 2));
}
/* Until app.js has asked who is signed in, everything that describes a
   project stays out of sight -- so a refresh on a project's screen after the
   session has ended shows the sign-in page, not a beat of somebody's
   building first. Visibility rather than display, so nothing shifts when it
   appears; `#main` says "Loading…" meanwhile, as it always did. */
#app.booting #sidebar,
#app.booting #headline,
#app.booting #healthChip,
#app.booting #whoami,
#app.booting .project-name,
#app.booting .project-sub { visibility: hidden; }

/* A link that destroys something reads as one -- but quietly, so a table of
   projects is not a wall of red. It commits to the colour on hover, at the
   moment the pointer is actually on it. */
.link-danger {
  color: var(--ink-3); text-decoration: none; margin-left: 10px;
  padding-left: 10px; border-left: 1px solid var(--line);
}
.link-danger:hover { color: var(--bad); text-decoration: underline; }
.link-danger:focus-visible {
  outline: 2px solid var(--bad); outline-offset: 2px; border-radius: 3px;
}

/* One primary action, sized to itself. A full-bleed slab across the page was
   louder than the table it introduces, and the table is what the screen is
   for. */
.head-actions { display: flex; gap: 10px; margin-top: 4px; }

/* Two controls per finish row: one that keeps the row and records a reason,
   one that removes it. They must not look alike -- that is the distinction. */
.row-act {
  border: 1px solid var(--line); background: var(--surface); color: var(--ink-3);
  border-radius: 5px; font-size: 10.5px; font-weight: 600; padding: 1px 5px;
  cursor: pointer; letter-spacing: .02em;
}
.row-act:hover { color: var(--warn); border-color: var(--warn-line); background: var(--warn-soft); }
.row-act:focus-visible { outline: 2px solid var(--ink); outline-offset: 1px; }
td.act { white-space: nowrap; text-align: right; padding-right: 6px; }
td.act .row-del { margin-left: 4px; }

/* A finish decided against: still there, still readable, plainly not counted. */
tr.na-row td { opacity: .55; }
tr.na-row td.total { text-decoration: line-through; }
.row-act.on { color: var(--warn); border-color: var(--warn-line); background: var(--warn-soft); }

/* --------------------------------------------------------------------------
   My Projects
   --------------------------------------------------------------------------
   A list, not a spreadsheet. Nothing here is editable, so it reads as rows of
   information with one menu of actions each, rather than cells with borders.
*/
.list-toolbar {
  display: flex; align-items: center; gap: var(--space-2);
  flex-wrap: wrap;                     /* wrap, never clip a control */
  margin-bottom: var(--space-3);
}
.toolbar-gap { flex: 1 1 auto; min-width: 0; }

.search-field { position: relative; flex: 1 1 260px; min-width: 200px; }
.search-field .text-input { width: 100%; padding-left: 34px; }
.search-icon {
  position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; color: var(--ink-3); pointer-events: none;
}
.list-toolbar .sel-input { width: auto; min-width: 140px; }

/* The layout switch. Two buttons that behave as one control. */
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 7px;
       overflow: hidden; background: var(--surface); }
.seg button {
  display: grid; place-items: center; width: 34px; height: 32px;
  border: 0; background: none; color: var(--ink-3); cursor: pointer;
}
.seg button + button { border-left: 1px solid var(--line); }
.seg button svg { width: 16px; height: 16px; }
.seg button:hover { color: var(--ink); background: var(--surface-2); }
.seg button.on { background: var(--accent-soft); color: var(--accent); }
/* The same switch with words in it rather than icons: Work Status's
   foundation share, "10% | 15%". Wide enough for the figure and a finger. */
.seg.seg-text button {
  width: auto; min-width: 52px; padding: 0 12px; font: inherit;
  font-weight: 600; font-variant-numeric: tabular-nums;
}
.seg.seg-text button.on { color: var(--accent); }
/* Work Status: the building's own two choices, on one line under the floor
   controls -- what it is for, and the share its foundation is taken at. */
.bld-settings {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-2) var(--space-4); margin: 0 0 var(--space-3);
}
.bld-use { display: inline-flex; align-items: center; gap: var(--space-2); }
.bld-use .sel-input { width: auto; min-width: 220px; }
.bld-k { font-weight: 600; color: var(--ink-2); }
.bld-note { font-size: 12.5px; }
.found-share {
  display: inline-flex; align-items: center; gap: var(--space-2);
  flex-wrap: wrap; margin-left: auto;
}
/* Basic Rate: which of the four lists this project started from, and three
   of each one's rates, so "Tier 3" is never a guess. */
.tier-row {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-2) var(--space-4);
}
.tier-pick { display: inline-flex; align-items: center; gap: var(--space-2); }
.tier-note { margin: 0; font-size: 12.5px; flex: 1 1 320px; }
.tier-samples {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  border-top: 1px solid var(--line);
}
.tier-sample {
  display: flex; flex-direction: column; gap: 3px; padding: 10px 14px;
  font-size: 12.5px; color: var(--ink-2);
}
.tier-sample + .tier-sample { border-left: 1px solid var(--line); }
.tier-sample strong { color: var(--ink); font-weight: 600; margin-bottom: 2px; }
.tier-sample b {
  color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums;
}
.tier-sample em { font-style: normal; color: var(--ink-3); }
.tier-sample.on { background: var(--accent-soft); }
/* The tier's own rate, struck through where this project has moved off it:
   the two figures side by side are the change, readable without colour. */
.tier-was { text-decoration: line-through; color: var(--ink-3); }
tr.br-changed td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
/* A constant moved off its tier's, beside the rate that may not have. */
td.cell.br-own { color: var(--accent); font-weight: 600; }
/* Column groups: a heading over a run of columns that belong together --
   the Cost Summary's "As per Developer" and "As per DBOT" (`Cost
   Summary!C2:G2`, `H2:L2`), in the workbook's header blue like the column
   headings under them. Both rows stick; the column headings sit below the
   group headings by however tall those are drawn (`--grid-group-h`). */
table.grid tr.grid-groups th {
  text-align: center; font-size: 10.5px; letter-spacing: .05em;
  text-transform: uppercase; padding: 4px 8px;
}
table.grid tr.grid-groups th.grp { border-bottom: 2px solid var(--wb-header-ink); }
table.grid.has-groups tr.grid-cols th { top: var(--grid-group-h, 0px); }
/* And the filter boxes under both, on every grid (`stickHeads`). */
table.grid tr.filter-row th { top: var(--grid-head-h, 0px); }
/* A developer total that adds only some of its heads (`cost_summary`
   counts them): italic, so it is not read as the developer's whole. */
table.grid td.csm-part { font-style: italic; }
/* A head's letter, A to K, as the Summary's Sr. No column prints it. */
.csm-letter {
  display: inline-block; min-width: 18px; color: var(--ink-3); font-weight: 600;
}
/* The Cost Summary's tiles open the working of the figure they show: a
   button, so a keyboard reaches it, drawn as the figure and not as a
   button. */
.tile button.csm-tile {
  display: block; background: none; border: 0; padding: 0; margin: 0;
  text-align: left; cursor: pointer; border-radius: 4px;
}
.tile button.csm-tile:hover { color: var(--accent); }
.tile button.csm-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The Cost Sheet's "From": which Work Status columns a line reads. Clipped in
   the span, because a width on the cell is only a minimum under the grid's
   automatic layout -- "Flooring, Dado, Kitchen platform, ..." widened the
   column to 423px and pushed Total Cost off a 1440px screen. The full list is
   the span's title. */
.cs-basis {
  display: block; max-width: 130px; overflow: hidden;
  white-space: nowrap; text-overflow: ellipsis;
}
.br-remark {
  display: block; max-width: 280px; overflow: hidden;
  white-space: nowrap; text-overflow: ellipsis;
}
/* The Assumptions sheet: the line's number in front of its name, in the
   quiet ink and tabular so the names start in one column; and a long remark
   or workbook formula cut with an ellipsis, the whole of it on hover. */
.as-sr {
  display: inline-block; min-width: 2.2em; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.as-clip {
  display: block; overflow: hidden; white-space: nowrap;
  text-overflow: ellipsis;
}
/* The rate list on the new-project form: one choice per line, with three of
   its rates in the line under it. */
.tier-pick-form .field-note { margin-top: 2px; }

/* --- The Cost Analysis -------------------------------------------------------
   What the take-off is worked out from, as a row of figures each of which
   opens its working: a button, so a keyboard reaches it and a screen reader
   says it does something. The value is the loudest thing in it. */
.ra-basis { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; }
.ra-basis-k {
  font-size: 10.5px; color: var(--ink-3); text-transform: uppercase;
  letter-spacing: .05em; margin-right: 4px;
}
.fact-chip {
  display: inline-flex; align-items: baseline; gap: 6px; min-height: 28px;
  padding: 4px 10px; border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface); color: var(--ink-2); font: inherit;
  font-size: 12px; cursor: pointer;
  transition: border-color .12s ease, background .12s ease;
}
.fact-chip b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.fact-chip .fact-unit { color: var(--ink-3); font-size: 11px; }
.fact-chip:hover { border-color: var(--accent); background: var(--accent-soft); }
.fact-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --- How a building is built, on its Cost Sheet ------------------------------
   Four settings side by side, each a labelled strip of choices, and under
   them the figures the civil lines are worked out from, each opening its
   working. */
.cs-settings { display: flex; flex-wrap: wrap; gap: var(--space-3) var(--space-5); }
.cs-setting { display: inline-flex; flex-direction: column; gap: 6px; }
.cs-facts {
  display: flex; flex-wrap: wrap; gap: 6px 8px; margin-top: var(--space-4);
  padding-top: var(--space-3); border-top: 1px solid var(--line);
}
td .tag.cs-unpriced { margin-left: 8px; }
/* The same four on the new-project form: one row each, its choices inline. */
/* The name above its choices on every row. Side by side, a short name
   ("Façade") let its choices ride up beside it while a long pair
   ("Labour contractor", "Labour with material") wrapped under its own, and
   four rows of one form read as two layouts. */
.build-pick .build-row {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 2px; padding: 4px 0;
}
.build-opts { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); }
.build-opt { display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  font-size: 13px; min-height: 28px; }
.ra-list-note { margin: 12px 0 0; font-size: 12.5px; color: var(--ink-2); }
.ra-tiles .tile .s .tag { margin-right: 4px; }
.ra-tiles .tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A typed figure says so in words as well as in colour, and its row carries
   the same left rule a changed Basic Rate line does. */
td .tag.ra-typed {
  margin-right: 6px; background: var(--accent-soft); color: var(--accent);
  font-weight: 600;
}
tr.ra-typed-row td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
/* A line the sheet takes off by hand, not taken off yet: the cell asks for a
   figure rather than showing a nought it does not have. */
td.cell.ra-by-hand { outline: 1px dashed var(--line-strong); outline-offset: -4px; }
.ra-blank { color: var(--ink-3); font-style: italic; }
/* Words about a figure, not a figure: the sans face, so a rule and its
   source read as a sentence and stay narrow enough to leave the amount on
   screen. */
.ra-how, .ra-blank { font-family: var(--sans); font-size: 12px; }
.ra-how { color: var(--ink-2); }
tr.ra-unpriced td.label { color: var(--ink-2); }
/* The section's two closing figures open their workings like any other. */
.ra-foot {
  background: none; border: 0; padding: 0; font: inherit; font-weight: 700;
  color: inherit; cursor: pointer; font-variant-numeric: tabular-nums;
  border-bottom: 1px dotted currentColor;
}
.ra-foot:hover { color: var(--accent); }
.ra-foot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }

/* The six building types, two to a row: short labels, and every one on
   screen at once rather than behind a dropdown. */
.use-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2px var(--space-2);
}
.seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.list-card { padding: 0; overflow: hidden; }
.table-scroll { overflow-x: auto; }     /* the table scrolls, the page never */

.list-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.list-table th {
  text-align: left; font-weight: 600; font-size: 11.5px; color: var(--ink-2);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--line); white-space: nowrap;
  background: var(--surface-2);
}
.list-table td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}
.list-table tbody tr:last-child td { border-bottom: 0; }
.list-table tbody tr:hover { background: var(--surface-2); }
.list-table tr.is-open { background: var(--accent-soft); }
.list-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.list-table .actions { width: 44px; text-align: right; }

/* The Schedule Summary's comparison, in the sheet's own shape: two groups of
   three columns under one heading each, Developer on the left and DBOT on
   the right. `Sr No` and `Particulars` span both header rows, as they do in
   the file. The rule between the groups is what stops a reader carrying a
   developer's date across into this assessment's column. */
.compare-schedule th.group {
  text-align: center; background: var(--wb-header);
  color: var(--wb-header-ink); font-weight: 700;
}
/* Marked in the markup, never counted. A row where the developer gave
   nothing says so in one cell spanning their three, so it has six cells and
   not eight -- and `nth-child(6)` drew the rule down the wrong column on
   exactly those rows, which is the one place a reader most needs to see
   which side a date is on. */
.compare-schedule th.group + th.group,
.compare-schedule .ours-first {
  border-left: 1px solid var(--line-strong);
}
.compare-schedule .centre { text-align: center; }
/* A building's own row, and the project's. The subtotal and total colours
   are the workbook's, already measured. */
.compare-schedule tbody tr.subtotal-row td { background: var(--wb-subtotal);
  color: var(--wb-subtotal-ink); font-weight: 700; }
.compare-schedule tbody tr.subtotal-row:hover td { background: var(--wb-subtotal); }
.compare-schedule tfoot tr.total-row td { background: var(--wb-total);
  color: var(--wb-total-ink); font-weight: 700; }

.proj { display: flex; align-items: center; gap: var(--space-2); }
.proj-mark {
  display: grid; place-items: center; width: 30px; height: 30px; flex: none;
  border-radius: 7px; background: var(--accent-soft); color: var(--accent);
}
.proj-mark svg { width: 17px; height: 17px; }
.proj-text { display: flex; flex-direction: column; min-width: 0; }
.proj-name {
  font-weight: 600; color: var(--ink); text-decoration: none;
  overflow-wrap: anywhere;
}
.proj-name:hover { color: var(--accent); text-decoration: underline; }
.proj-sub { font-size: 11px; color: var(--ink-3); }

/* The caret that folds a project's towers out under its row.
   A fixed 18px slot on every row, taken by a spacer where the project has one
   building, so the project names line up down the column whether the list
   holds a multi-tower project or not -- a caret indenting only some rows reads
   as two lists. */
.proj-fold {
  width: 18px; height: 18px; flex: none; padding: 0;
  display: grid; place-items: center;
  background: none; border: 0; border-radius: 4px;
  color: var(--ink-3); cursor: pointer;
}
.proj-fold.spacer { cursor: default; }
.proj-fold svg { width: 13px; height: 13px; }
.proj-fold.open svg { transform: rotate(90deg); }
.proj-fold:hover { background: var(--surface-2); color: var(--ink); }

/* The towers themselves, in the row the caret opens and on a card. */
.list-table tr.tower-rows td { padding: 0 var(--space-3) var(--space-2) 56px;
                               background: var(--surface-2); }
.tower-list { display: flex; flex-direction: column; gap: 2px; }
.tower-line {
  display: flex; align-items: baseline; gap: var(--space-3);
  font-size: 11.5px; padding: 3px 0;
}
.tower-line .tower-name { font-weight: 600; color: var(--ink);
                          min-width: 110px; }
.tower-line.warn .tower-name { color: var(--warn); }
.proj-card .tower-list { margin: 0 0 var(--space-2); }
.proj-card .tower-line { justify-content: space-between; }

/* The tower tabs above Room Configuration's matrix.
   A tab strip rather than a select: with two or five towers every one is worth
   a click of its own, and which tower is being looked at is the single most
   important thing on the screen -- it decides what every figure below means. */
.tower-tabs {
  display: flex; gap: 2px; flex-wrap: wrap;
  margin: 0 0 var(--space-3);
  border-bottom: 1px solid var(--line);
}
.tower-tab {
  padding: 7px var(--space-3) 6px;
  font-size: 12.5px; font-weight: 600; color: var(--ink-2);
  text-decoration: none; border-radius: 6px 6px 0 0;
  border: 1px solid transparent; border-bottom: 0;
  margin-bottom: -1px;
}
.tower-tab:hover { background: var(--surface-2); color: var(--ink); }
/* The accent and not `--wb-title`, although the strip reads as a sheet's tabs:
   `--wb-title` is a background colour, and its dark-mode value (#0b1d3f) is
   unreadable as text on a dark surface. The accent has a counterpart made for
   ink in both modes, and it is what "where you are" is drawn in everywhere
   else in this interface. */
.tower-tab.on {
  background: var(--surface); color: var(--accent);
  border-color: var(--line); box-shadow: inset 0 2px 0 var(--accent);
}
.tower-tab.warn { color: var(--warn); }
.tower-tab .pill { margin-left: 4px; background: var(--warn-soft);
                   color: var(--warn); }

.stamp { white-space: nowrap; }
.stamp .by { display: block; font-size: 11px; color: var(--ink-3); }

.pill {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: 11px; font-weight: 600; white-space: nowrap;
}
.pill.ok { background: var(--ok-soft); color: var(--ok); }
.pill.neutral { background: var(--surface-2); color: var(--ink-2);
                box-shadow: inset 0 0 0 1px var(--line); }
.pill.muted { background: var(--surface-2); color: var(--ink-3); }
.pill.bad { background: var(--bad-soft); color: var(--bad); }

/* One menu per row. `<details>` gives keyboard and click for nothing. */
.row-menu { position: relative; display: inline-block; }
.row-menu > summary {
  list-style: none; display: grid; place-items: center;
  width: 28px; height: 28px; border-radius: 6px; cursor: pointer;
  color: var(--ink-3);
}
.row-menu > summary::-webkit-details-marker { display: none; }
.row-menu > summary:hover { background: var(--surface-2); color: var(--ink); }
.row-menu > summary:focus-visible { outline: 2px solid var(--accent); }
.row-menu > summary svg { width: 15px; height: 15px; }
/* `fixed`, not `absolute`. The card clips its corners and the table scrolls
   sideways inside it, and both of those clip an absolutely-positioned popup:
   the menu opened, and everything past the second item was cut off by the
   card's edge -- so `Restore to Live` on an archived row was there and could
   not be seen. Fixed to the viewport escapes both; `placeMenu` puts it under
   its own button. */
.row-menu-body {
  position: fixed; z-index: 60; min-width: 208px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 8px 24px rgba(15, 23, 42, .12); padding: 4px;
  display: flex; flex-direction: column;
}
.row-menu-body button {
  border: 0; background: none; text-align: left; font: inherit;
  padding: 7px 10px; border-radius: 6px; cursor: pointer; color: var(--ink);
  white-space: nowrap;
}
.row-menu-body button:hover { background: var(--surface-2); }
.row-menu-body button:focus-visible { outline: 2px solid var(--accent); }
.row-menu-body button.danger { color: var(--bad); }
.row-menu-body button.danger:hover { background: var(--bad-soft); }

.proj-cards {
  display: grid; gap: var(--space-3); padding: var(--space-3);
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
.proj-card {
  border: 1px solid var(--line); border-radius: 9px; padding: var(--space-3);
  display: flex; flex-direction: column; gap: 6px; background: var(--surface);
}
.proj-card header { display: flex; align-items: start; justify-content: space-between;
                    gap: var(--space-2); }
.proj-card p { margin: 0; font-size: 12px; }
.proj-cost { font-size: 15px; font-weight: 600;
             font-variant-numeric: tabular-nums; }
.proj-card footer { display: flex; align-items: center; justify-content: space-between;
                    gap: var(--space-2); font-size: 11px; margin-top: auto; }

.empty-state { padding: var(--space-6) var(--space-3); text-align: center; }
.empty-title { font-weight: 600; font-size: 14px; margin: 0 0 4px; }
.empty-state .btn { margin-top: var(--space-3); display: inline-flex; }

.pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-2); flex-wrap: wrap;
  margin-top: var(--space-3); font-size: 12px;
}
.pager-buttons { display: flex; gap: 4px; }
.pager-buttons .btn { width: auto; min-width: 32px; padding: 5px 9px; }
.pager-buttons .btn.on { background: var(--accent); border-color: var(--accent);
                         color: var(--on-ink); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

@media (max-width: 720px) {
  .list-toolbar .sel-input { flex: 1 1 140px; }
  .toolbar-gap { display: none; }
}

/* The developer's own dates, typed on the comparison's stage rows. A bare
   date input in a table cell: no border until it is hovered or focused, so
   the table reads as a table and not as a form, and the calendar icon is
   there the moment somebody looks for it. */
.compare-schedule td.dev-cell { padding: 0 var(--space-2); position: relative; }
.compare-schedule td.dev-cell input {
  width: 100%; border: 1px solid transparent; background: none;
  font: inherit; color: inherit; padding: 3px 4px; border-radius: 4px;
}
.compare-schedule td.dev-cell input:hover { border-color: var(--line); }
/* The cell reads in the sheet's own `Mon-YY`, as every other date on this
   comparison does. A `type="date"` input draws the browser's format instead
   -- `04-09-2026` here, `9/4/2026` on the next machine -- and one table
   printing `04-09-2026` beside `Sep-26` is one table in two date formats.
   So the input's own text is turned off and the `Mon-YY` is drawn under it;
   focus puts the editable format back, because that is what has to be there
   to type into. The calendar button is the input's and stays visible
   throughout, which is what says the cell can be typed at all. */
.compare-schedule td.dev-cell input { color: transparent; }
.compare-schedule td.dev-cell .as-shown {
  position: absolute; left: calc(var(--space-2) + 5px); top: 0; bottom: 0;
  display: flex; align-items: center; pointer-events: none;
}
.compare-schedule td.dev-cell input:focus { color: inherit; }
.compare-schedule td.dev-cell input:focus + .as-shown { display: none; }
/* The ring the rest of the app draws, not a 1px border doing the job of one:
   a cell with no border until it is touched is the one place a focus that
   only changes a border colour would be missed entirely. */
.compare-schedule td.dev-cell input:focus-visible,
.compare-schedule td.dev-cell input:focus {
  outline: 2px solid var(--accent); outline-offset: -1px;
  border-color: var(--accent); background: var(--surface);
}

/* A cell that is not there: a trade reaching only the bottom of the stack has
   no cell on the floors above it. Drawn as a gap in the matrix -- faintly
   hatched, no fill, no cursor -- so it reads as "there is nothing here" and
   not as "nobody has filled this in yet", which are different findings. */
table.grid td.cell.locked, table.grid td.ws-out {
  background: repeating-linear-gradient(135deg,
    transparent 0 6px, var(--line) 6px 7px);
  cursor: default;
}
table.grid tbody tr:hover td.cell.locked,
table.grid td.cell.locked:hover {
  background: repeating-linear-gradient(135deg,
    transparent 0 6px, var(--line) 6px 7px);
}
/* Except on a heading row. A heading's Unit is not a gap in a matrix, it is
   part of the band that says "the rows under me" -- so it wears the row's
   colour like every other cell on it, and no hatch. */
table.grid tr.subtotal-row td.cell.locked,
table.grid tbody tr.subtotal-row:hover td.cell.locked {
  background: var(--wb-subtotal);
}
