/* Thermii design system — palette + type scale
   ------------------------------------------------------------------
   Presentation only. Load AFTER journey.css and thermii-spark.css:

     <link rel="stylesheet" href="css/thermii-system.css">

   Two jobs, both additive:
     1. Retires the pre-Thermii accents that are still driving parts of the UI
     2. Replaces ad-hoc font sizes with one scale

   No calculator hooks, values, visibility rules or handlers live here. */

/* ══ 1. PALETTE ═══════════════════════════════════════════════════════════
   tokens.css still carries the palette from before Thermii — #ff5b00 orange
   and #8cc63f green — alongside the teal→magenta gradient. The orange drives
   the summary Edit pills, which means the only orange in the journey sits on
   the review screen and reads as a warning. Both are remapped here rather
   than deleted, so anything still referencing them lands on brand. */
:root {
  /* One green — THE BRAND GREEN, #8cc63f, everywhere (Loz, 2026-08-27). This
     token shipped as a cooled #17a05c "to sit beside the gradient's teal", which
     left the app running three green systems at once: #17a05c wherever the vars
     reach, the skin's hard-coded #7ac043/#5d9c46, and the locked engine's
     literal #8cc63f on the kit add-on cards. Loz picked the engine's green as
     the one; everything var-driven lands on it from here. */
  --thermii-recommend: #74b700;

  /* A secondary-action ink for Edit and other quiet controls. */
  --thermii-quiet: #5b6480;

  /* Legacy tokens, remapped. */
  --navy: #101735;
  --accent: #7138ed;
  --accent2: #149de8;
  --green: var(--thermii-recommend);
  --sel-bg: #f4f1ff;
  --body-bg: #f4f6fb;

  /* journey.css layer. */
  --journey-green: var(--thermii-recommend);
  --journey-orange: var(--thermii-quiet);
  --journey-selected: #5117f7;

  --thermii-success: var(--thermii-recommend);
}

@media screen {
  /* Edit is a quiet secondary action, not an alert. */
  body .eq-ans-edit {
    border-color: #d3d8e6;
    background: #fff;
    color: var(--thermii-quiet);
  }

  body .eq-ans-edit:hover {
    border-color: var(--journey-selected);
    color: var(--journey-selected);
  }

  /* The kit total is the most important number in the product — put it on the
     brand purple rather than a one-off #4e20f5. */
  body #wKitTotal,
  body #kitTotal {
    color: var(--thermii-purple) !important;
  }

  /* Recommended state, harmonised. */
  body .filter-option.is-recommended {
    border-color: var(--thermii-recommend);
    background: linear-gradient(135deg, #fff 0%, #f1faf5 100%);
  }

  body .fo-rec-badge { color: #128049; }

  /* ── Question-tile icons: retire the orange rollover ──
     thermii.css:594 tints these on hover with a hue-rotate filter chain that
     lands on orange. Each icon is an <img>, so a filter can only ever produce a
     flat tint — there is no filter that paints a gradient.

     Instead the icon is knocked down to a solid black silhouette and a gradient
     panel is laid over it with mix-blend-mode:lighten. Against the card's white
     hover background (journey.css:214-216) lighten resolves per channel: over
     the black strokes the gradient is lighter and shows through, over the white
     card white stays white. The gradient therefore lands on the artwork only,
     with no mask and no change to the markup or the image sources.

     Selected tiles are excluded: green still means "confirmed" there, and only
     the rollover was orange. */
  body .filter-option:not(.is-selected):hover .fo-icon {
    position: relative;
  }

  body .filter-option:not(.is-selected):hover .fo-icon img {
    filter: brightness(0);
  }

  body .filter-option:not(.is-selected):hover .fo-icon::after {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--thermii-gradient);
    mix-blend-mode: lighten;
    pointer-events: none;
  }

  /* The same rollover drives the System tiles, which swap to an authored
     "-selected" artwork on hover instead of being filtered — those are already
     on brand, so they are deliberately left alone. */

  @media (prefers-reduced-motion: reduce) {
    body .filter-option:not(.is-selected):hover .fo-icon img {
      transition: none;
    }
  }
}

/* ══ 2. TYPE SCALE ════════════════════════════════════════════════════════
   Question headlines were clamp(34–42px) in one file, 30px in another and
   24px in a third; body copy ran 13 / 13.5 / 14 / 15px across four files.
   Six steps, mapped onto the classes that already exist.

   Tracking is tied to size rather than applied flat: −.035em is right on a
   42px Manrope headline and cramped at 24px. */
:root {
  --t-display: clamp(30px, 3.1vw, 42px);
  --t-h1: 30px;
  --t-h2: 24px;
  --t-h3: 19px;
  --t-h4: 16px;
  --t-body: 15px;
  --t-small: 13px;
  --t-micro: 11px;

  --tt-display: -.035em;
  --tt-h2: -.02em;
  --tt-h3: -.012em;
  --tt-body: 0;

  --lh-display: 1.12;
  --lh-heading: 1.25;
  --lh-body: 1.6;
}

@media screen {
  /* ── Display: the question itself ── */
  body .thermii-version-body h1,
  body .kb-section-title,
  body .eq-summary-title,
  body .eq-title {
    font-family: var(--thermii-font-display);
    font-size: var(--t-display);
    font-weight: var(--thermii-font-weight-display);
    letter-spacing: var(--tt-display);
    line-height: var(--lh-display);
    text-wrap: pretty;
  }

  /* ── Stage title ──
     Sized to its column rather than to the scale, which is why this is a
     literal and not var(--t-h2).

     The title sits in the stage header's first grid column,
     var(--journey-stage-col) — clamp(150px, …, 180px) — and since 2026-08-07
     that column ends in a vertical rule. "Measurements" is the longest stage
     name and is a single unbreakable word: at the old 24px it measured 177px
     against a 150px column at narrow widths and ran straight through the rule.

     Measured at 600 weight with the -0.02em tracking: 24px→177, 22px→162,
     21px→155, 20px→148, 19px→140. 20px technically fits 150 but with 2px to
     spare, which is inside font-rendering variance between platforms. 19px
     clears it by 10px and still fits the widest case with room, so one size
     serves every breakpoint — which is the point, every screen's title should
     be the same.

     --t-h2 is deliberately left alone: it is 24px and --t-h3 is already 19px,
     so retuning the token would collapse two steps of the scale into one to
     solve a layout problem in a single component. */
  body .thermii-stage-title {
    font-size: 19px;
    letter-spacing: var(--tt-h2);
    line-height: var(--lh-heading);
  }

  /* ── Tile labels ── */
  body #systemTypeRow .so-label,
  body .thermii-version-card h2 {
    font-size: var(--t-h3);
    letter-spacing: var(--tt-h3);
    line-height: var(--lh-heading);
  }

  body .fo-label,
  body .thermostat-name {
    font-size: var(--t-h4);
    letter-spacing: var(--tt-h3);
    line-height: 1.3;
  }

  /* ── Body copy ── */
  /* `.eq-subtext` was the one body style never wired into the scale. It took
     15px/1.55 from journey.css:317 while its sibling .thermii-screen-intro
     took var(--t-body)/var(--lh-body) from here — measured 23.25px against
     24px leading on screens sitting side by side in the same journey. Adding
     it to the group is the whole fix; the `body ` prefix these already carry
     puts them at (0,1,1), which clears journey.css's (0,1,0) without needing
     !important. It also closes a 701-900px hole where journey's hard-coded
     15px held on while the token had already stepped down to 14px. */
  body .thermii-version-subtitle,
  body .thermii-screen-intro,
  body .eq-subtext,
  body .eq-note,
  body .thermii-option-copy,
  body .thermii-version-card p {
    font-size: var(--t-body);
    letter-spacing: var(--tt-body);
    line-height: var(--lh-body);
  }

  /* ── Small: dense rows and tables ── */
  body .eq-sum-row .eq-ans-q,
  body .eq-sum-row .eq-ans-v,
  body .kit-name,
  body .thermii-side-copy,
  body .thermii-guide-status {
    font-size: var(--t-small);
    line-height: 1.5;
  }

  body .kit-name {
    font-size: var(--t-h4);
    letter-spacing: var(--tt-h3);
  }

  /* ── Micro: uppercase labels and metadata ── */
  /* `.kit-sku` AND `.kit-detail` LEFT THIS GROUP ON 2026-09-08, and the
     grouping was the defect Loz spotted: "I want the subtext and SKU text in
     the product kits to match the subtext in 'Need more tools'... in font
     style, sizing, letter spacing."

     Everything else about them already matched — measured, both are Sora
     12px/400, line-height 16.8px, #6b7280 — because the 2026-09-07 rule below
     unified size, line-height and colour with `.wopt-note`. The one property
     it did not cover was TRACKING, and these two were sitting here at
     **0.84px against the note's `normal`**, because they had been grouped with
     the uppercase eyebrows. A SKU and a product description are neither
     uppercase nor labels: 0.07em is right for `.fo-rec-badge` and
     `.thermii-stage-kicker`, which are set in caps, and wrong for a sentence.

     Their size and line-height come from that later rule, so nothing else
     moves by leaving here. */
  body .fo-rec-badge,
  body .thermii-project-label,
  body .thermii-stage-kicker {
    font-size: var(--t-micro);
    letter-spacing: .07em;
    line-height: 1.4;
  }

  /* Prices stop shuffling as quantities change. */
  body .kit-unit-price,
  body .kit-line-price,
  body #wKitTotal,
  body #kitTotal,
  body .eq-input {
    font-variant-numeric: tabular-nums;
  }
}

@media screen and (max-width: 900px) {
  :root {
    --t-display: clamp(24px, 5.4vw, 30px);
    --t-h2: 20px;
    --t-h3: 17px;
    --t-h4: 15px;
    --t-body: 14px;
  }

  /* --t-h4 previously had no mobile step, which left tile labels at the desktop
     16px while h2, h3 and body all stepped down. journey.css tried to correct
     that with `.fo-label{font-size:15px}` at 700px, but at (0,0,1,0) it was
     outranked everywhere and never applied. The step belongs here, in the scale,
     so .fo-label, .thermostat-name and .kit-name all move together. */
  body .electric-covering-row:not(.room-tiles-grid) .fo-label {
    /* thermii.css hard-codes 16px on these eleven rows at (0,0,3,0). It carries
       no !important, so matching its specificity and adding `body` is enough to
       hand the decision back to the token. The two ID-scoped exceptions in
       journey.css — room tiles at 15px, the seven-tile floor-covering row at
       14px — still outrank this and are left as the deliberate density choices
       they are. */
    font-size: var(--t-h4);
  }
}

/* ── Reclaiming the display scale below 700px ─────────────────────────────
   thermii.css pins two of the four display classes:

     @media(max-width:700px){ .kb-section-title{font-size:20px!important}
                              .eq-title{font-size:20px!important} }

   so below 700px the scale above was silently discarded and question headlines
   dropped to 20px while .eq-summary-title still followed the scale at 24px —
   the summary ended up larger than the question it summarised.

   thermii.css is byte-locked to baseline/thermii.monolith.html by the parity
   harness (tests/verify-parity.mjs asserts the two are equal), so those
   declarations cannot be deleted at source. An !important declaration cannot be
   beaten by specificity or by load order — only by another !important. This
   block is therefore deliberate, and scoped as tightly as it can be: the two
   selectors that are actually pinned, at the one breakpoint where the pin
   applies.

   Not included, on purpose: .eq-summary-title and .thermii-version-body h1 are
   not pinned and already follow the scale, so they are left without !important;
   and between 700px and 900px nothing is pinned, so no override is needed there.

   Delete this block if thermii.css is ever unlocked and the two declarations are
   removed at source. That is the real fix; this is the one that is available. */
@media screen and (max-width: 700px) {
  body .eq-title,
  body .kb-section-title {
    font-size: var(--t-display) !important;
  }
}

/* ---------------------------------------------------------------------------
   Kit screen: sticky pricing sidebar
   ---------------------------------------------------------------------------
   The kit screen is a long scroll — hero, room summary, heat requirement, then
   a 12-item list — with the total and every call to action stranded at the
   bottom. Moving them into a column that stays put means the price and the Add
   to Cart button are on screen the whole way down.

   The obstacle is that #kitPanelHeading / #kitPanel / #kitFooterCard /
   #kitPrintRow are siblings of the WHOLE journey: their shared parent is
   #electricPanel, which also holds every question step, the progress bar, the
   summary and its CTA. Gridding that parent unconditionally would restructure
   the entire journey. So the grid is scoped to the kit screen via the body
   marker navigation.js already sets. On that screen every other child is
   display:none, and display:none children are removed from grid flow entirely,
   so only the four kit blocks get placed.

   THE PDF IS NOT AFFECTED, which is worth stating because it is the usual trap
   on this screen. legacy-core.js clones #kitPanel and #kitFooterCard THEMSELVES
   into a fresh 760px #thermiiPdfRoot — not their parent — so no grid container
   exists there and the grid-column declarations are inert. Only `position:
   sticky` survives cloning, and it is explicitly reset at the end of this
   block. Verified against the cloneInto() list in legacy-core.js:6034-6078.

   WIDTH — the guard was 1181px and that was too low. Re-measured 2026-08-07:

     viewport   list, measured
     1180        784   single column, full width
     1181        391   sidebar appears — 393px lost to one pixel of viewport
     1336        546
     1920        650   the old 1400px shell cap, so this was the ceiling

   The sidebar costs ~320px the moment it appears, and the old 1400px shell cap
   meant the viewport could never hand that back: the kit list was NARROWER at
   every desktop size than it was at 1180px, and worst of all in the 1181-1400
   band where most laptops sit. Introducing a sidebar at a breakpoint always
   costs its own width — that part is unavoidable — so the fix is to spend it
   only where the list can still afford it.

   At 1500 the list lands at ~714px and grows to ~974 against the new 1720px
   shell cap, so it is never worse than the old ceiling and usually far better.
   Below 1500 the layout simply stacks full width, which is what every screen
   under 1181 already did.

   A default .kit-row needs ~530px (78+220+74+86 plus gaps). The list now
   clears that everywhere the sidebar exists, so the compressed 4-column
   variant below is no longer strictly required — it is kept because its 1fr
   name column absorbs the extra width harmlessly, and swapping row styles is
   a visual change that belongs in its own commit, not this one. */
@media screen and (min-width: 1500px) {
  /* `grid-template-rows` is not decoration. The sidebar has to span every left
     column row to have anything to stick against, and `grid-row: 1 / -1` only
     spans when rows are EXPLICIT — against an implicit grid, `-1` resolves back
     to line 1, so the card silently occupies row 1 alone. Measured symptom:
     its 1060px height inflated row 1 and pushed the kit list 1000px down the
     page. Six rows is comfortably more than the four blocks this screen ever
     shows; unused rows collapse to zero.

     Which is also why row-gap is 0 — with a gap, every empty row would add one.
     The blocks carry their own vertical margins already. */
  body[data-thermii-screen="electric-kit"] #electricPanel,
  body[data-thermii-screen="water-kit"] #waterPanel {
    display: grid;
    grid-template-rows: repeat(6, auto);
    /* 360 SINCE 2026-09-04 (Loz), and the history matters because the number
       has moved three times.

       300 gave the summary's trust pairs 119px columns and wrapped them to
       three lines. 330 still wrapped them to two. 500 was measured off the
       design — its sidebar spans 1207-1711 of a 1738px page, ~30% of the
       content width — and is the NARROWEST width at which the paired trust
       block does not wrap at all. Loz then found 500 too wide on the live
       site.

       So the pairing was the thing forcing the width, not the width being
       wrong: `.tks-trust` now STACKS (see journey.css) and nothing wraps at
       360 either. The card grows ~90px taller, which is the trade he chose.
       The list keeps ~1340px at his 1900, far past the ~530px a kit row
       needs. */
    grid-template-columns: minmax(0, 1fr) minmax(280px, 360px);
    align-items: start;
    column-gap: 20px;
    row-gap: 0;
  }

  body[data-thermii-screen="electric-kit"] #kitNav,
  body[data-thermii-screen="electric-kit"] #kitPanelHeading,
  body[data-thermii-screen="electric-kit"] #kitPanel,
  body[data-thermii-screen="electric-kit"] #kitPrintRow,
  body[data-thermii-screen="water-kit"] #wKitNav,
  body[data-thermii-screen="water-kit"] #wKitPanelHeading,
  body[data-thermii-screen="water-kit"] #wKitPanel,
  body[data-thermii-screen="water-kit"] #wKitPrintRow {
    grid-column: 1;
  }

  /* max-height keeps a tall card usable: without it a sidebar taller than the
     viewport pins its top and buries the CTAs off the bottom edge.

     `grid-template-columns: 1fr` because #kitFooterCard is ITSELF a two-column
     grid — the green total block beside the "what next" panel, which is right
     for a full-width footer and impossible at 300px. Left alone it overflowed
     362px into 300 and crushed the panel to 46px wide. */
  body[data-thermii-screen="electric-kit"] #kitFooterCard,
  body[data-thermii-screen="water-kit"] #wKitFooterCard {
    position: sticky;
    /* CLEAR THE STICKY HEADER (Loz, 2026-09-23). `.kb-logo` is
       `position: sticky; top: 0; z-index: 40` (thermii-skin.css) and measures a
       stable 78px across the whole sidebar range (1500-1850px). At `top: 20px`
       the card pinned 58px UNDER it, so scrolling down cropped the card's top —
       the "Your Kit Summary" band was buried behind the header. 78 + a 20px gap
       (the rhythm the card already had from the viewport top) = 98px, so the
       card now stops just below the header's gradient line. If the header height
       ever changes, this 98 and the max-height below move with it. */
    top: 98px;
    grid-row: 1 / -1;
    grid-column: 2;
    grid-template-columns: 1fr;
    /* the header eats the top 78px and a 20px foot keeps the CTAs off the edge */
    max-height: calc(100vh - 118px);
    margin-top: 0;
    overflow-y: auto;
  }

  /* FIVE COLUMNS, NOT FOUR — CORRECTED 2026-09-03. This reused the
     narrow-screen variant on the premise that "the list column is narrow for
     the same reason a phone is", and that premise does not hold: a .kit-row
     has FIVE children (thumbnail, info, unit price, qty controls, line total)
     and the four-column template gave the line total nowhere to go, so it
     WRAPPED onto a second grid row underneath the thumbnail.

     MEASURED at 1850px, which is the width Loz actually works at: columns
     resolved to 68 / 1004 / 52 / 76 and the total sat at the row's left edge
     on a second line. The info column alone had 1004px — there was never a
     shortage of room, only a missing column. The comment three blocks up
     already records that a row needs ~530px and that the list clears that
     everywhere the sidebar exists; the narrow variant was simply the wrong
     one to reach for.

     Column widths follow the design's own header order — ITEM, QTY, UNIT
     PRICE, TOTAL — with the thumbnail ahead of the item. `auto` for the
     controls because the stepper and bin size themselves. */
  body[data-thermii-screen="electric-kit"] .kit-row,
  body[data-thermii-screen="water-kit"] .kit-row,
  body[data-thermii-screen="electric-kit"] .thermii-kit-colhdr,
  body[data-thermii-screen="water-kit"] .thermii-kit-colhdr {
    /* A DEFINITE qty track, not `auto`: the row and the injected column
       header are separate grids sharing a template, so `auto` resolved
       to 126px in the row and 24px in the header and every label sat
       right of its column. See the note in journey.css. */
    grid-template-columns: 68px minmax(180px, 1fr) 76px 132px 84px;
  }

  /* Five CTA pills across a 300px column is unreadable; stack them. */
  body[data-thermii-screen="electric-kit"] .kit-cta-grid,
  body[data-thermii-screen="water-kit"] .kit-cta-grid {
    grid-template-columns: 1fr;
  }

}

/* The re-skin hid two things this layout wants back: the gradient hero
   (journey.css:1344) and the kit list's category headers — Pipe, Manifold
   Assembly, Controls & Temperature (journey.css:1364). Both with
   `display: none !important`, so both need !important to undo.

   Restoring the category headers also fixes a standing bug: they were absent
   from the DOWNLOADED QUOTE too, because html2canvas rasterises screen CSS, so
   customers received an undifferentiated wall of line items.

   Scoped to the kit-screen body marker rather than applied globally, and that
   is load-bearing. legacy-core.js writes `style.display = 'none'` on the hero
   when you leave the kit screen (:1524, :1705); a global `display: block
   !important` would beat that inline style and strand the banner across the
   whole journey. Bounded by the marker, the rule simply stops applying and the
   engine's own hiding works untouched. */
body[data-thermii-screen="electric-kit"] #kitPanelHeading,
body[data-thermii-screen="water-kit"] #wKitPanelHeading {
  display: block !important;
}

/* FLEX, NOT BLOCK, ON SCREEN (corrected 2026-09-03). `block` was only ever
   the value picked to UNDO the skin's `display: none` — it was not a choice of
   block over flex, and it quietly broke the row the engine designed:
   legacy-core.js writes `display:flex; align-items:center;
   justify-content:space-between` inline (:1174 and :4578) so the label and
   count sit left and the collapse chevron sits right. Restored as a block, the
   children stacked and THE CHEVRON DROPPED ONTO ITS OWN LINE under the label —
   visible in every screenshot of this screen and mistaken for the design's
   own until the cascade was traced.

   THE PDF CLONE KEEPS `block`, deliberately. Chrome cannot fragment flex
   containers across printed pages — css/thermii.css's print block already
   makes this screen's tall flex columns block-level for exactly that reason —
   and while a one-line header is unlikely to be split, the printed quote is
   not the place to find out. Screen gains the fix; paper stays byte-identical.

   `.kit-category-header` also stays block: it is different markup and nothing
   here has established that it was built as a flex row. */
body[data-thermii-screen="electric-kit"] .wkit-cat-hdr,
body[data-thermii-screen="water-kit"] .wkit-cat-hdr {
  display: flex !important;
}

body[data-thermii-screen="electric-kit"] .kit-category-header,
body[data-thermii-screen="water-kit"] .kit-category-header,
#thermiiPdfRoot .wkit-cat-hdr,
#thermiiPdfRoot .kit-category-header {
  display: block !important;
}

/* ---------------------------------------------------------------------------
   Kit screen: bring the four panels onto the current palette
   ---------------------------------------------------------------------------
   The kit screen still carried the pre-reskin styling — a peach room card with
   an orange border, a green Add to Cart, an orange Save button and a red-
   outlined Guides button. Everything here moves to the gradient/navy/neutral
   set used by the rest of the journey.

   !important is unavoidable on most of it, and it is worth being precise about
   why rather than reaching for it by habit: the room card, the heat panel and
   the CTA buttons are BUILT BY legacy-core.js with their colours written as
   inline style attributes on the generated markup. That file is byte-locked,
   and unlike index.html — where the parity contract ignores `style`, so the
   hero's inline sizing was simply deleted — a locked script's output cannot be
   edited at source. Inline beats every stylesheet, so !important is the only
   instrument left. The hero rules below carry no !important precisely because
   that markup lives in index.html and was stripped instead.

   Scoped to the kit-screen marker throughout, so none of it can reach the
   summary screens or leak into the rest of the journey. */

/* Hero ---------------------------------------------------------------------- */
/* !important here, unlike the title and subtitle below, because legacy-core.js
   writes `background: transparent; border-radius: 0; padding: 28px 36px`
   straight onto this element. The banner's colour was never CSS at all — it
   came from the .banner-wave-bg SVG behind it, so hiding that wave without
   putting a real gradient on the element leaves the hero completely blank.
   The two must move together. */
body[data-thermii-screen$="-kit"] #kitPanelHeading,
body[data-thermii-screen$="-kit"] #wKitPanelHeading {
  /* 26px, DOWN FROM 34 (Loz, 2026-09-08): "reduce the height of the overall
     panel". The band's content is 80px tall — the 30px title line, its 2px
     margin and the subtitle's two 24px lines — so 26px each side puts the
     panel at 132px against the 171 he was looking at, a 39px cut WHILE the
     subtitle gains a line. It is also shorter than the 124 it measured with a
     one-line subtitle and no reference badge at all.

     26 is this screen's own inner padding — `.thermii-heat-main` takes the
     same — rather than a number picked to hit a height. The avatar is 52px and
     the badge 34, so neither sets the band; the text does, which is why the
     arithmetic above is the whole story. */
  padding: 26px 38px !important;
  border-radius: 16px !important;
  background: var(--thermii-gradient) !important;
}

/* The wave was drawn for a full-bleed banner. In a rounded card at this width
   it reads as an artefact rather than a flourish — and a flat gradient is what
   the design calls for. */
body[data-thermii-screen$="-kit"] .banner-wave-bg {
  display: none !important;
}

body[data-thermii-screen$="-kit"] .kit-hero-title {
  font-size: 26px;
  font-weight: var(--thermii-font-weight-display);
  line-height: 1.25;
  letter-spacing: -.01em;
}

body[data-thermii-screen$="-kit"] .kit-hero-sub {
  font-size: 15px;
  line-height: 1.55;
  opacity: .94;
}

/* WHITE ON THE GRADIENT, BOTH JOURNEYS (Loz, 2026-09-24). The water hero is
   rebuilt by the engine with its text white inline; electric's is static
   markup (index.html:873) with no colour, so it read navy on the gradient.
   The hero is hidden in print (css/thermii.css), so this never reaches paper. */
body[data-thermii-screen$="-kit"] .kit-hero-title,
body[data-thermii-screen$="-kit"] .kit-hero-sub {
  color: #fff;
}

/* ── AND THE QUOTE REFERENCE READS A SIZE UP (Loz, 2026-09-08) ──────────────
   "Increase the size of the Quote Ref box/text slightly."

   15px IS THE SUBTITLE'S OWN SIZE, so the reference now reads at the same
   weight of voice as the line beside it rather than being the smallest thing
   on the panel — a relationship rather than a step picked by eye. The label
   keeps its rank a size below at 13px (it was 12 against 13). The padding and
   radius grow with the text: 8/16 and 10px against 7/14 and 9px.

   HIS DESIGN GOES FURTHER and this deliberately does not follow it all the
   way: attach4.jpg sets the reference as a small uppercase label ABOVE a
   rounded capsule whose text solves to roughly 16-18px with tracking. He asked
   for slightly bigger, not for the two-part treatment, so the box keeps its
   shape. Restructuring it is a separate ask. */
body[data-thermii-screen$="-kit"] .trm-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 16px;
  padding: 8px 16px;
  border-radius: 10px;
  background: rgba(255, 255, 255, .18);
  font-size: 15px;
  font-weight: 500;
}

body[data-thermii-screen$="-kit"] .trm-badge .trm-badge-label {
  font-size: 13px;
}

/* ── THE QUOTE REFERENCE GOES TO THE FAR RIGHT (Loz, 2026-09-08) ────────────
   "Move the Quote/Ref box over to the far right." His design has it there:
   attach4.jpg puts the reference at the right-hand end of the band, vertically
   centred against the text, where the app had it under the subtitle. Measured
   on the design at 1:1 — its "QUOTE REF:" ink runs x 969..1061 of a band
   ending at 1171, i.e. hard right.

   js/thermii-kit-screen.js moves the node out of the text stack into the flex
   row the engine emits, and marks that row — the badge cannot be pushed right
   from inside a sibling. `margin-left: auto` then does it, with the row's own
   `align-items: center` handling the vertical centring, so nothing needs
   positioning and the badge can never overlap the text. Measured after: 38px
   from the panel's right edge (its own padding) and 0px off the row's centre.

   THE `margin-top` MUST GO WITH IT, or the 16px that spaced it from the
   subtitle now offsets it from the row's centre line.

   IT ALSO TAKES A ROW OUT OF THE STACK, which is most of the height Loz asked
   to lose: the badge was 16px of margin plus a 34px box below two lines of
   text, and it now costs nothing at all. */
body[data-thermii-screen$="-kit"] .thermii-kit-hero-row > .trm-badge {
  flex-shrink: 0;
  margin-top: 0;
  margin-left: auto;
}

/* NARROW SCREENS WRAP RATHER THAN CRUSH. The row is `nowrap` as the engine
   writes it, so at a phone width the badge would squeeze the text stack
   instead of dropping below it; `min-width: 0` lets the stack shrink at all
   (a flex item's floor is its content otherwise) and the wrap gives the badge
   its own line. Measured: the badge stays on the row at 900px and drops below
   it at 700, with no overflow at any width down to 375. */
body[data-thermii-screen$="-kit"] .thermii-kit-hero-row {
  flex-wrap: wrap;
  /* ── 36px, AND IT IS THE MASCOT'S HALO THAT SETS IT (Loz, 2026-09-08) ────
     "Move the headline and subtext further away from the animated
     avatar/mascot on this panel." The flex gap said 18px and the text still
     looked crowded, because the gap is measured from the 52px avatar while
     what a reader SEES is `.thermii-glow-ring` behind it: css/thermii.css:379
     makes that an 80px disc — 14px proud of the avatar on each side — with
     `filter: blur(10px)` on top, so the lit halo reaches about 24px past the
     mark and was landing ON the headline. Measured before: the ring's box ran
     to x=185 against a title starting at 189, i.e. 4px, with the blur over it.

     36 = the ring's 14px overhang + its 10px blur + 12px of actual air. So the
     visible mark now gets the clearance the old 18 appeared to promise.

     THE ROW-GAP WAS INERT UNTIL TODAY and this is the correction: the engine's
     inline `gap: 18px` sets BOTH axes and beat the 12px declared here for the
     wrapped phone case. js/thermii-kit-screen.js strips that one property per
     render now, so both values below are the ones that apply. Yesterday's note
     claiming the phone gained a 12px row-gap was wrong on that detail; the
     panel heights it quoted were measured live and stand. */
  column-gap: 36px;
  row-gap: 12px;
}

body[data-thermii-screen$="-kit"] .thermii-kit-hero-row > div:not(.trm-badge) {
  min-width: 0;
}

/* ── AND THE FORCED BREAK STANDS DOWN ON A PHONE ────────────────────────────
   Loz asked for the sentence to run onto a second line, which is a typographic
   choice about the width he works at. At 375px the sentence already wraps
   three times, so the forced break buys nothing there and costs a whole line:
   MEASURED, the panel went 291px -> 295 at 375 and 251 -> 256 at 500, i.e. the
   extra line outweighed the padding this change saved. Suppressing it hands
   the phone the saving instead: measured 277px at 375 against 291 before
   today, 14px shorter. Every one of this change's three parts then reduces or
   holds the height at that width — 16px less padding, no forced line, and a
   12px row-gap where the badge had a 16px margin.

   `display: none` on a `<br>` really does suppress the break; nothing else
   about the line changes, and the copy is identical either way. */
@media screen and (max-width: 700px) {
  body[data-thermii-screen$="-kit"] .kit-hero-sub br {
    display: none;
  }
}

/* ── THE DOCUMENTS GROUP SITS IN THE BULLETS' OWN BAND (Loz, 2026-09-08) ────
   "Let's have the Documents & its 4 buttons in the same background/section
   style/colour as the 4 bullets in the sidebar (#fafbfd). Keep the buttons
   white, but have them go purple on mouseover."

   #fafbfd IS THE SAME TINT AS THE TRUST BULLETS' BAND — the hex he gave on
   2026-09-07 — so this screen still has ONE separator tint and the gate ties
   the two rules together rather than letting a second value drift in.

   FULL-BLEED THE WAY EVERY OTHER BAND ON THIS CARD DOES IT: a negative margin
   against the CTA card's own inline padding (`12px 28px 16px`, index.html), so
   -28px reaches its sides and -16px its foot. `.tks-head` and `.tks-trust`
   both work exactly this way.

   THE BOTTOM CORNERS ARE ROUNDED TO 11px, WHICH IS THE CARD'S OWN. The band
   ends flush at the foot of a card that does NOT clip (no overflow), so square
   corners would poke outside its radius — the mirror of `.tks-head`, which
   rounds its TOP corners for the same reason at the other end. 11px is read
   off the card's inline `border-radius:11px`, not invented.

   THE LABEL'S TOP MARGIN GOES: it carries 20px for the white layout, which
   would sit on top of the band's own padding and read as 38. */
/* The separator above the band, now that js/thermii-kit-screen.js marks it and
   strips the engine's inline margin: this restates index.html's own value so
   nothing moves at these widths, and the sidebar block below takes both it and
   the band 2px further out. */
body[data-thermii-screen$="-kit"] .thermii-kit-docs-rule {
  margin: 20px -28px 0;
}

body[data-thermii-screen$="-kit"] .thermii-kit-docs-band {
  margin: 0 -28px -16px;
  padding: 18px 28px 20px;
  border-radius: 0 0 11px 11px;
  background: #fafbfd;
}

body[data-thermii-screen$="-kit"] .thermii-kit-docs-band
  .thermii-kit-docs-lbl {
  margin-top: 0;
}

/* A MEDIA QUERY ADDS NO SPECIFICITY — so this block has to sit AFTER the base
   rules above, not in the min-width:1500 block 300 lines earlier where it was
   first written and where the base rule quietly beat it on source order.
   Measured then: insets still 3/3/7 with the override in the CSSOM. This
   project has shipped that defect once before, on the small print. */
@media screen and (min-width: 1500px) {
  /* ── FLUSH TO THE SIDEBAR CARD (Loz, 2026-09-08) ─────────────────────────
     "The new colour #fafbfd and horizontal line is not butt-up to the sides
     and bottom of the sidebar container — there is white showing."

     HE WAS SEEING THREE BOXES, NOT ONE. Measured at 1850px, the white frame
     was 3px at each side and 7px at the foot, and it decomposes exactly:

         sides   1px  #wKitFooterCard's own border
                 2px  .thermii-whatnext's inline `padding:2px`
         foot    1px  that border, again
                 4px  the card's own `padding: 0 0 4px`
                 2px  the wrapper's padding

     The band's first bleed escaped only the INNER card's 28px/16px, which is
     one level up; `.tks-head` and `.tks-trust` look flush with a single -24px
     because `.kit-total-block` is a DIRECT child of the card with no wrapper
     in its chain. This band sits two levels deeper, which is the whole
     discrepancy. So: 28 + 2 = **30** at the sides, 16 + 2 + 4 = **22** at the
     foot, landing the band's border box on the card's padding box to the
     pixel. Measured after: 1px, 1px, 1px — the card's own border, exactly
     what the trust band above it shows.

     THE WRAPPER'S 2px COSTS NOTHING TO COVER, and that was checked rather
     than assumed: `.thermii-whatnext` computes background-image `none`,
     background-color transparent, radius 0 and no shadow — the "ONE CARD, NOT
     TWO" work neutralised its gradient — and it holds exactly ONE child. So
     those 2px were showing the card's white, not a brand element.

     THE CARD'S 4px IS ESCAPED, NEVER EDITED. CLAUDE.md records that bottom
     4px as deliberately kept when the top one went, so the negative margin is
     the only legal route to it.

     ── THE PADDING GROWS WITH THE MARGIN, AND THIS IS THE PART THAT WOULD
     HAVE SHIPPED WRONG. The band's `padding: 18px 28px 20px` is the exact
     COUNTERWEIGHT to its -28px: it re-insets the Documents label and the 2x2
     grid to the same 298px column the "Add Kit Items to Cart" and "Save"
     buttons occupy, and those two are direct children of the CTA column, not
     of the band. Moving the margin alone widens the band's content box to
     302px, shifting the label 2px left and growing each button from 144 to
     146 — a visible misalignment with the buttons directly above. 30 and 26
     keep the content EXACTLY where it was: measured after, the grid is still
     1440..1738, its buttons still 144px, still 0px off the primary's edges,
     and still 39px from the card's foot. Only the tint and the line moved.

     ── 13px, NOT 14, AND THE .tks-head PRECEDENT IS BACKWARDS FOR A CLIPPED
     BOX. An overflow clip runs at the PADDING edge with each radius reduced
     by the border width, so on this 14px/1px card the clip curve is 13px. At
     14 the band's own arc would sit outside that curve and the card's white
     would show between the two; at 13 they coincide. (Below 1500 there is no
     clip at all — `overflow: auto` lives in this very block — and 13 is also
     the card's inner curve there, so one number serves both.)

     ── SCOPED TO THE SIDEBAR, DELIBERATELY. Between 701 and 1499px the footer
     card is a two-column grid and the CTA column is capped at `max-width:
     900px` and CENTRED, so a fixed -30px lands ~50px short of the card's edge
     while the -22px would still carry the tint to its foot: flush on one axis
     only, with a rounded tint corner floating mid-card. Measured at 1200px:
     50px side insets against a 1px foot. Below 1500 the band therefore keeps
     the inset look it has today, which is consistent on all four sides.
     Loz's ask is the sidebar; the phone case is offered, not assumed. */
  body[data-thermii-screen$="-kit"] .thermii-kit-docs-band {
    margin: 0 -30px -22px;
    padding: 18px 30px 26px;
    border-radius: 0 0 13px 13px;
  }

  body[data-thermii-screen$="-kit"] .thermii-kit-docs-rule {
    margin: 20px -30px 0;
  }
}

/* Room card and heat panel -------------------------------------------------- */
body[data-thermii-screen$="-kit"] .wkit-room-card,
body[data-thermii-screen$="-kit"] .wkit-heat-panel {
  border: 1px solid var(--journey-border) !important;
  border-radius: 14px !important;
  background: #fff !important;
}

body[data-thermii-screen$="-kit"] .kit-room-name,
body[data-thermii-screen$="-kit"] .wkit-heat-title {
  color: var(--thermii-navy) !important;
  font-weight: var(--thermii-font-weight-display) !important;
}

/* The insulation pills are class-less buttons whose SELECTED state exists only
   as an inline `background:#ff5b00` written by legacy-core.js — there is no
   is-selected class to hook, so the attribute substring is the only handle.
   It is self-maintaining: the engine rewrites the same style attribute when
   the choice changes, so the rule follows the selection rather than a fixed
   position.

   The design shows this as a white card with a purple border and a tick. That
   is not reachable — the tick element does not exist in the generated markup
   and cannot be added to it — so this takes the nearest honest equivalent: the
   same filled-pill treatment on the journey's selected-purple instead of the
   old orange.

   Longhand `background-color`, not the `background` shorthand. Measured: the
   shorthand carrying a var() failed to beat the element's inline `background:
   #ff5b00` even with !important, while `border-color` from the same rule
   applied. The longhand wins cleanly. */
body[data-thermii-screen$="-kit"] .wkit-heat-pillwrap > button[style*="#ff5b00"] {
  border-color: var(--journey-selected) !important;
  background-color: var(--journey-selected) !important;
  background-image: none !important;
}

/* Room card interior: align the spec pairs -------------------------------- */
/* The pairs were laid out as `display:flex; flex-wrap:wrap` with each pair
   content-sized, so no two rows shared a column and the labels started at a
   different x on every line. That is what made the card read as jumbled — not
   the amount of information, the absence of a grid under it.

   They were also PILLS: `background:#fff; border-radius:20px`. On the original
   peach card those white pills were the visible structure. Turning the card
   white made them disappear and left the text floating with nothing holding
   it — so the earlier palette change is what exposed this. Since the design
   calls for no pills, they are dropped properly here and a real grid does the
   work the pill outlines used to.

   Everything is hooked by inline-style substring because none of this markup
   carries a class — it is generated by legacy-core.js as bare divs and spans.
   The substrings chosen are the structural ones (`flex-wrap:wrap` for a pair
   row, the 0.6px tracking for a group heading), not colours, so they survive a
   value change. */
/* `auto-fit` with a real minimum rather than a fixed column count, because the
   right count depends on whether the sticky sidebar is present and a fixed
   number gets it wrong on one side or the other. Measured the natural width of
   every spec pair at its actual type sizes: the widest is "Pressure Drop:
   ≈12.1 kPa per loop" at 211px. Beside the sidebar the card has 588px inside,
   where three columns would be 182px each and wrap every row; two give 284px.
   Full width it has ~722px and fits three. 220px is that 211px measurement
   plus a little headroom, and lets the browser pick. */
body[data-thermii-screen$="-kit"] .wkit-room-card div[style*="flex-wrap:wrap"] {
  display: grid !important;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 9px 20px !important;
  align-items: baseline;
}

/* Pairs: strip the pill, keep label and value on one line, let long values
   wrap instead of forcing the column wider. */
body[data-thermii-screen$="-kit"] .wkit-room-card div[style*="flex-wrap:wrap"] > span {
  display: flex !important;
  align-items: baseline;
  gap: 6px !important;
  padding: 0 !important;
  border-radius: 0 !important;
  background: none !important;
  white-space: normal !important;
}

body[data-thermii-screen$="-kit"] .wkit-room-card div[style*="flex-wrap:wrap"] > span > span:first-child {
  flex: 0 0 auto;
  color: var(--journey-muted, #596584) !important;
}

body[data-thermii-screen$="-kit"] .wkit-room-card div[style*="flex-wrap:wrap"] > span > span:last-child {
  min-width: 0;
}

/* Group headings become real section rules. The first one has no divider —
   nothing above it to separate from. */
body[data-thermii-screen$="-kit"] .wkit-room-card div[style*="letter-spacing:0.6px"] {
  margin: 18px 0 10px !important;
  padding-top: 14px;
  border-top: 1px solid var(--journey-border);
  color: var(--journey-muted, #596584) !important;
  font-size: 11px !important;
}

body[data-thermii-screen$="-kit"] .wkit-room-card div[style*="letter-spacing:0.6px"]:first-of-type {
  margin-top: 4px !important;
  padding-top: 0;
  border-top: 0;
}

/* Kit summary block (injected by js/thermii-kit-summary.js) ----------------- */
/* The generated .kit-total-block centres everything, which suits a lone total
   but not a summary with aligned figures — so the injected parts opt out
   rather than the block being re-centred, leaving the existing total markup
   untouched. */
body[data-thermii-screen$="-kit"] .tks-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: -28px -24px 18px;
  padding: 16px 22px;
  border-bottom: 1px solid var(--journey-border);
  background: #f6f8fd;
  /* 14px, NOT 12: the band now sits flush against the card's top, and the card
     is `overflow: auto` with a 14px radius, so it CLIPS this band to its own
     corners. A tighter radius here would leave two white slivers where the
     band's corner falls inside the card's. Larger-or-equal is safe; the clip
     decides the shape. */
  border-radius: 14px 14px 0 0;
  color: var(--thermii-navy);
  font-size: 17px;
  font-weight: var(--thermii-font-weight-display);
  text-align: left;
}

body[data-thermii-screen$="-kit"] .tks-head svg {
  width: 22px;
  height: 22px;
  flex: 0 0 22px;
  color: var(--journey-selected);
}

body[data-thermii-screen$="-kit"] .tks-lines {
  margin-bottom: 18px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--journey-border);
  text-align: left;
}

/* ── THE PRODUCT LINES' SUBTEXT MATCHES THE TOOLS' (Loz, 2026-09-07) ────────
   "Use the same style subtext that is on the 'Need More Tools' section for the
   descriptions and SKUs on the kit summary products. Also remove the grey
   border from the product thumbnails."

   The reference is `.wopt-note` in the optional-tools list, which the engine
   writes inline as **12px / #888 / line-height 1.4** — against these two lines
   at 11px / #6f6f6f. So they step UP a size and take the quieter, roomier
   setting.

   ONE DEPARTURE FROM "the same", AND IT IS DELIBERATE: the grey is #6b7280,
   not the tools' #888. #888 measures **3.54:1** on white, under WCAG 1.4.3
   AA's 4.5:1, and #6f6f6f — what these lines have today — is 5.3:1, so
   copying the colour literally would REGRESS the contrast of every product
   description and SKU on the screen. #6b7280 is 4.83:1 and is the grey this
   screen already uses for the room card's note, its column headings and the
   heat disclaimer. **`.wopt-note` is brought to the same value in the same
   breath**, so the two really are one style rather than two that merely look
   alike — and that fixes the tools list's own failing contrast while it is
   being used as the reference.

   The thumbnail's `1px solid #e7eaf1` goes; the 9px radius stays, so the
   product image keeps its shape without a box drawn round it. */
body[data-thermii-screen$="-kit"] .kit-detail,
body[data-thermii-screen$="-kit"] .kit-sku,
body[data-thermii-screen$="-kit"] .wopt-note {
  font-size: 12px !important;
  line-height: 1.4 !important;
  /* TRACKING IS PART OF THE STYLE (Loz, 2026-09-08). The three were one style
     in size, line-height and colour and still read differently, because
     `.kit-sku`/`.kit-detail` were tracked 0.84px by the uppercase-label group
     above while `.wopt-note` sat at `normal`. Declared here as well as
     removed there, so "one style" is literally true of every property a
     reader can see — and because letter-spacing INHERITS, which means an
     ancestor gaining tracking later would otherwise split them again. */
  letter-spacing: normal !important;
  color: #6b7280 !important;
  /* ONE WEIGHT, 400 (Loz, 2026-09-23). The manifold row's .kit-detail carries an
     inline font-weight:500 from the engine (legacy-core.js:4615) that the rest
     of this rule never overrode, so it read slightly bolder than every other
     product subtext. Forcing 400 here levels it; the others were already 400. */
  font-weight: 400 !important;
}

/* THE SKU IS A STEP LIGHTER THAN ITS DESCRIPTION (Loz, 2026-09-23). A product
   code is reference metadata most customers skim past, so it sits below the
   description grey (#6b7280, kept) rather than level with it: bold name ->
   mid-grey description -> lighter-grey SKU. #868da0 is 3.32:1 on white, under
   AA's 4.5 for 12px — accepted as incidental text, Loz's call after seeing the
   number. Same specificity as the shared rule above, later in the file, so it
   wins the SKU while leaving .wopt-note and .kit-detail at #6b7280. */
body[data-thermii-screen$="-kit"] .kit-sku {
  color: #868da0 !important;
  /* a touch more air above the SKU than the engine's inline 3px (Loz,
     2026-09-23). Paper keeps 3px via the print reset below, so the PDF's
     page-break model (which measures cloned kit-row heights) is unchanged. */
  margin-top: 6px !important;
}

/* Paper keeps the engine's 3px. Same selector and !important as the screen rule
   but LATER in this file, so it wins in native print; and legacy-core.js's
   re-emitter scopes it to #thermiiPdfRoot for the clone (which renders screen
   media), where the id makes it win there too. */
@media print {
  body[data-thermii-screen$="-kit"] .kit-sku {
    margin-top: 3px !important;
  }
}

/* MANIFOLD A / B EYEBROW IN THE THERMII PURPLE (Loz, 2026-09-23) — was an
   off-brand teal (#3eafd4, inline on manifest-locked legacy-core.js:4608), the
   one label in the kit not in the app's purple eyebrow language. It is the only
   bare <div> that leads a .kit-info (the product title is always an <a>/<span>,
   the note comes after it), so this selector reaches the manifold label and
   nothing else. `!important` because the engine writes the colour inline;
   colour only, so the PDF clone gets the same purple with no page-break impact.
   #6c4bd8 to match the insight titles (Loz, 2026-09-23), not the primary
   #7138ed. */
body[data-thermii-screen$="-kit"] .kit-info > div:first-child {
  color: #6c4bd8 !important;
}

/* PIPE COIL DETAIL STACKS ONE COIL PER LINE (Loz, 2026-09-23). The coil planner
   writes its per-coil breakdown newline-separated (js/thermii-coil-plan.js); this
   renders those newlines as line breaks. `pre-line` also collapses runs of
   whitespace to one, exactly like `normal`, so every other .kit-detail (which
   carries no newline — the manifold "Serves:" note, electric coverage text)
   renders identically. Reaches the PDF clone, which is the intent: the printed
   quote gets the same clear per-coil list. */
body[data-thermii-screen$="-kit"] .kit-detail {
  white-space: pre-line;
}

body[data-thermii-screen$="-kit"] .tks-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 5px 0;
  font-size: 14px;
}

body[data-thermii-screen$="-kit"] .tks-row-label {
  color: var(--journey-muted, #596584);
}

body[data-thermii-screen$="-kit"] .tks-row-value {
  color: var(--thermii-navy);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* ── THE TOTAL BLOCK RANGES LEFT (Loz, 2026-09-07) ──────────────────────────
   "The TOTAL KIT COST, price and subtext under the price need to be ranged
   left." The design has all three hard against the block's left edge; the
   engine writes `text-align:center` INLINE on `.kit-total-block`, and these
   three inherit it.

   NO `!important` AND NO STRIPPING NEEDED, which is worth knowing because both
   are this project's usual answers to an inline style: the centring is
   INHERITED, and any declaration on the element itself beats an inherited
   value whatever its weight. So the block stays centred for everything else it
   holds — the QR block among them — and only these three opt out. Same
   arrangement thermii-system.css already records for the injected parts.

   THE VAT NOTE HAS NO CLASS OF ITS OWN, so js/thermii-kit-summary.js marks it
   `.tks-vat-note`: it is the engine's own unclassed div, found as the total's
   next sibling. Paper is restored to centred in journey.css's print block —
   the PDF clones this card and renders in screen media. */
body[data-thermii-screen$="-kit"] .kit-total-lbl,
body[data-thermii-screen$="-kit"] #wKitTotal,
body[data-thermii-screen$="-kit"] #kitTotal,
body[data-thermii-screen$="-kit"] .tks-vat-note {
  text-align: left;
}

/* ── THE TRUST BLOCK IS A TINTED BAND (Loz, 2026-09-07) ─────────────────────
   "Can we make this section look like the attached — i.e. 2 columns, tint
   background to separate it from the Add to Kit button."

   THE TINT IS #fafbfd, MEASURED OFF HIS OWN SCREENSHOT — every pixel of the
   band clear of the text reads (250,251,253). That is the SAME hex he gave for
   the room card's footer band this morning, so it is this screen's one
   separator tint rather than a second value. Note `.tks-head` above is
   #f6f8fd, a shade deeper: the head is a heading, this is a footer. Flagged to
   him rather than harmonised.

   FULL-BLEED, THE WAY `.tks-head` ALREADY DOES IT. His band spans the card's
   whole width (measured x21..860 of an 840px card interior) with its text
   inset, so the box escapes `.kit-total-block`'s inline `padding: 28px 24px`
   with a -24px margin and carries its own instead — exactly the mirror of the
   head band's `margin: -28px -24px 18px`.

   AND THAT PADDING IS index.html's, NOT THE ENGINE'S — worth being exact
   about, because it means it CAN move. It is an inline style on the two
   `.kit-total-block` divs (index.html:894 electric, :1724 water), and inline
   styles are outside the DOM contract, so a later session stripping one — this
   screen has had six such strips — would take the -24px with it. The gate
   pins it as a premise in both journeys, which is the only thing keeping the
   two numbers together.

   THE BOTTOM PADDING IS NOT ESCAPED, deliberately: the block's own 28px is
   what puts white between the band and the Add-to-Cart button, which is the
   separation he asked the tint for. Measured after: 66px from the band's
   bottom rule to the top of the button.

   THE INSET IS 16px WHERE HIS BAND'S IS 27, AND THAT IS THE ONE PLACE THIS
   DEPARTS FROM HIS SCREENSHOT — because his card is 420px wide and this one is
   the 360 he chose. At 27px the pairs do not fit at all; at 20px they fit with
   **4.3px** to spare in both governing cells, which one missing webfont would
   swallow; at 16px the spare doubles to **8.3px**, and the fallback case was
   then measured rather than assumed — system-ui at these sizes is NARROWER
   here, not wider, leaving 16.7px. 16px is also a number this card already
   uses: the head band's vertical padding and the lines block's rule gap.

   NO RADIUS. The band sits mid-card, and the card is `overflow: auto`, so
   there are no corners for it to round. */
body[data-thermii-screen$="-kit"] .tks-trust {
  display: grid;
  gap: 14px;
  margin: 20px -24px 0;
  padding: 20px 16px;
  border-top: 1px solid var(--journey-border);
  border-bottom: 1px solid var(--journey-border);
  background: #fafbfd;
  text-align: left;
}

/* ── NO ICONS ON THE TRUST ROWS (Loz, 2026-09-07) ───────────────────────────
   "The icons and text below the price are not the same as the original
   uploaded design." Measured against attach4.jpg's sidebar: the design has NO
   icons on these four items at all — bold label over a grey sub-line, and
   nothing in front of them. The 32px lavender tiles were this app's own
   furniture. They are no longer BUILT (js/thermii-kit-summary.js), so there is
   no icon rule left here to go stale.

   THE PAIRING IS BACK TO 2x2 AS OF 2026-09-07, and it fits at Loz's 360px
   sidebar — see the measurements in css/journey.css beside the columns
   themselves. Two things changed since this note said it could not: the icons
   went (43px a row), and the columns are sized to their CONTENT rather than to
   equal halves. */
body[data-thermii-screen$="-kit"] .tks-trust-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

body[data-thermii-screen$="-kit"] .tks-trust-item strong {
  display: block;
  color: var(--thermii-navy);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.3;
}

body[data-thermii-screen$="-kit"] .tks-trust-item span {
  display: block;
  color: var(--journey-muted, #596584);
  font-size: 12px;
  line-height: 1.35;
}

/* The quote is a costing document, not a shopfront. The VAT breakdown belongs
   in it — a customer needs the tax line — but returns policy and secure
   checkout are conversion furniture and would only pad the PDF. */
#thermiiPdfRoot .tks-trust,
#thermiiPdfRoot .tks-head {
  display: none !important;
}

/* Sidebar buttons ----------------------------------------------------------- */
/* The primary action. Green #8cc63f is the last of the old palette on this
   screen and the one the eye goes to first.

   SOLID PURPLE SINCE 2026-09-03, not the three-stop sweep: Loz's Final Screen
   design has it flat, and the sweep also starts on the brand blue where white
   reaches only about 3:1 — under 4.5:1 for an 18px button label. The same
   reasoning already retired the sweep from the how-pill's hover. Changed HERE
   rather than overridden elsewhere because this rule is where the primary's
   colour lives; a second declaration in journey.css lost to it anyway, being
   (1,1,0) against this (1,1,1) with both !important. */
body[data-thermii-screen$="-kit"] #kitBasketBtn,
body[data-thermii-screen$="-kit"] #wKitBasketBtn {
  border: 0 !important;
  border-radius: 12px !important;
  background: var(--thermii-purple, #4e20f5) !important;
  color: #fff !important;
  font-weight: 600 !important;
}

/* Positional selectors because these buttons carry no classes and only one of
   the five has an id — the markup is generated, so there is nothing else to
   hook. Order is fixed by legacy-core.js: Save, Guides, Download, Email,
   Print. First is the secondary action and keeps brand colour; the rest are
   tertiary and go neutral so they stop competing with Add to Cart. */
/* DOCUMENTS RUNNING ORDER (Loz, 2026-09-23): Download Quote, Email Quote,
   Print Quote, then Install Guides — so the 2x2 reads
   Download | Email / Print | Install. In the markup Install Guides is the
   first of the four document buttons, so it is sent to the last grid cell with
   `order`. Reordering by CSS, not by moving the node: the buttons carry inline
   on* handlers held as an ordered list by verify-parity, so re-sequencing the
   markup would break the DOM contract. `order` is visual only, so the
   `:first-of-type` rule below (which keys off DOM source order) is unaffected;
   the guides panel is a sibling OUTSIDE the grid, so it is untouched. */
body[data-thermii-screen$="-kit"] .kit-cta-grid > button[id$="GuidesBtn"] {
  order: 1;
}

/* `:first-of-type` rather than `:first-child`: there are TWO .kit-cta-grid
   elements in the document, one per journey, and measured against the live
   DOM `:first-child` matched only one of them. Counting among button siblings
   is immune to whatever non-button node precedes them. */
/* NOT IN THE DOCUMENTS GRID (2026-09-03). This gave the FIRST of the five
   buttons the brand colour because that was Save to Account. The sidebar
   rebuild promotes Save out of the grid, so `:first-of-type` started
   landing on Install Guides — measured, and visibly wrong: one document
   button lit up blue while its three siblings stayed neutral. The design
   has all four neutral, and the promoted Save carries its own purple. */
body[data-thermii-screen$="-kit"] .kit-cta-grid:not(.thermii-kit-docs) > button:first-of-type {
  border: 1.5px solid var(--thermii-blue) !important;
  background: #fff !important;
  color: var(--thermii-blue) !important;
}

/* ALL FOUR of the documents buttons, not just the last three. This was
   `:not(:first-of-type)` because the first was Save to Account and had
   its own brand colour. With Save promoted out, exempting the first
   button left Install Guides wearing only its INLINE orange from
   index.html — it went blue, then orange, and neither is the design.
   Measured both times; the render is what showed it. */
body[data-thermii-screen$="-kit"] .kit-cta-grid > button:not(:first-of-type),
body[data-thermii-screen$="-kit"] .kit-cta-grid.thermii-kit-docs > button {
  border: 1.5px solid var(--journey-border) !important;
  background: #fff !important;
  color: var(--thermii-navy) !important;
}

body[data-thermii-screen$="-kit"] .kit-cta-grid > button {
  border-radius: 11px !important;
  font-weight: 600 !important;
}

/* ── THE FOUR BUTTONS PICK UP A HOVER (Loz, 2026-09-08) ─────────────────────
   They stay white at rest, as he asked. The first version of this filled them
   with solid `--thermii-purple` and put the label in white; he corrected it
   the same day to a lilac fill with purple ink — see the block below, which
   carries the values that ship. What survives from this one is the placement
   and the two reasons `!important` is unavoidable.

   IT SITS DIRECTLY AFTER THE RESTING RULE, AND HAS TO. Both selectors compute
   to (0,3,2) — an attribute, a class and either a second class or `:hover`,
   over `body` and `button` — so they TIE, and both need `!important` because
   the engine writes background, border and colour INLINE on every one of these
   buttons. A tie between two !important declarations is settled by SOURCE
   ORDER, so this rule was inert 300 lines earlier in the file. Measured: the
   rule was in the CSSOM and losing.

   `!important` IS UNAVOIDABLE FOR A SECOND REASON, and my first comment here
   had it wrong: removing the engine's `onmouseover`/`onmouseout` stops it
   writing NEW inline colours at hover time, but the STATIC inline
   `background:#fff` on each button stands whatever the handlers do. The strip
   is what makes the hover stick once it applies; it is not what makes it win.

   `border-color` MOVES WITH THE FILL, or a 1.5px #dfe4ee edge is left drawn
   around a purple button. `:focus-visible` comes along so the keyboard sees
   what the mouse sees. */
/* ── CORRECTED TO HIS DESIGN: LILAC FILL, PURPLE INK (Loz, 2026-09-08) ──────
   "Have the Documents 4 buttons on mouseover go lilac and purple text/icon
   (design example attached). Make the border lilac (my mouseover example is
   showing grey - sorry!)"

   THE FILL IS SAMPLED, NOT CHOSEN: #ded7ff is a flat 27,847-pixel region in
   his file, so it is exact. His screenshot is of the real app — its band reads
   #fafbfd and its resting border #e0e4ed, both ours to the digit — which also
   confirms the grey he apologised for: the hovered button's border in that
   file IS still #dfe4ee.

   THE INK IS THE ONE PLACE I DID NOT MATCH HIM, and the reason is a number.
   His purple samples at #6a41e4, which measures **4.38:1** on his own fill —
   under WCAG 1.4.3 AA's 4.5 for a 14px/600 label, and `--thermii-purple`
   (#7138ed) is no better at 4.37. `--journey-selected` is the same purple a
   shade deeper at **5.42:1**, and it is already this screen's own ink: the
   engine writes it on every category subtotal in the kit list. So the card
   still carries one purple, and it is a purple that can be read.

   THE BORDER IS DERIVED FROM THE FILL rather than picked by eye, because he
   named a colour family and not a value. #c5b8ff is the fill's own hue and
   saturation — hsl(250, 100%) — stepped down in lightness until the edge
   matches the strength the button's edge has AT REST: #dfe4ee on white is
   1.275:1, and #c5b8ff on #ded7ff is 1.308:1, the nearest step at or above it.
   So the button's outline reads exactly as firmly hovered as resting, which is
   what stops it looking like it changes weight under the cursor.

   IT SITS DIRECTLY AFTER THE RESTING RULE, AND HAS TO. Both selectors compute
   to (0,3,2) — an attribute, a class and either a second class or `:hover`,
   over `body` and `button` — so they TIE, and both need `!important` because
   the engine writes background, border and colour INLINE on every one of these
   buttons. A tie between two !important declarations is settled by SOURCE
   ORDER, so this rule was inert 300 lines earlier in the file. Measured: the
   rule was in the CSSOM and losing.

   `!important` IS UNAVOIDABLE FOR A SECOND REASON, and my first comment here
   had it wrong: removing the engine's `onmouseover`/`onmouseout` stops it
   writing NEW inline colours at hover time, but the STATIC inline
   `background:#fff` on each button stands whatever the handlers do. The strip
   is what makes the hover stick once it applies; it is not what makes it win.

   `:focus-visible` comes along so the keyboard sees what the mouse sees. THE
   ICONS FOLLOW THE LABEL because they are `stroke="currentColor"` SVGs — the
   engine's own markup — so `color` carries both, and that is why there is no
   separate stroke rule here. */
body[data-thermii-screen$="-kit"] .thermii-kit-docs > button:hover,
body[data-thermii-screen$="-kit"] .thermii-kit-docs > button:focus-visible {
  border-color: #c5b8ff !important;
  background: #ded7ff !important;
  color: var(--journey-selected) !important;
}


/* ── ONE CARD, NOT TWO (Loz's design, 2026-09-03) ───────────────────────────
   The design's sidebar is a single white card. The markup is two blocks — the
   total panel and the gradient-bordered "what next" card — which read as two
   cards stacked. In sidebar mode the CARD moves to the container and both
   children give theirs up.

   SCOPED TO THE SIDEBAR ONLY. Below 1500px those two blocks sit side by side
   as a two-column grid, where two cards is the right answer and a single
   outline around both would be wrong.

   The gradient wrapper keeps its 2px padding as plain spacing; killing the
   padding as well would close the gap between the buttons and the trust rows
   above them. */
body[data-thermii-screen="electric-kit"] #kitFooterCard,
body[data-thermii-screen="water-kit"] #wKitFooterCard {
  /* NO PADDING ON TOP (Loz, 2026-09-07): "the top of the panel is not butt-up,
     there is a white edge on the top." The 4px was on all four sides, and the
     tinted `.tks-head` band is the card's first content — so those 4px showed
     as a white strip above the tint, between it and the card's border. The
     bottom 4px stays: the comment above records that the gradient wrapper
     below leans on it for the gap to the trust rows. */
  padding: 0 0 4px;
  border: 1px solid var(--journey-border, #e4e7ef);
  border-radius: 14px;
  background: #fff;
  row-gap: 0;
}

body[data-thermii-screen="electric-kit"] #kitFooterCard .kit-total-block,
body[data-thermii-screen="water-kit"] #wKitFooterCard .kit-total-block,
body[data-thermii-screen="electric-kit"] #kitFooterCard .thermii-whatnext,
body[data-thermii-screen="water-kit"] #wKitFooterCard .thermii-whatnext {
  border: 0 !important;
  border-radius: 0 !important;
  background: transparent !important;
  box-shadow: none !important;
}

/* Sticky is the one property that survives into the cloned quote, where it
   would offset the total block against a container that never scrolls. */
#thermiiPdfRoot #kitFooterCard,
#thermiiPdfRoot #wKitFooterCard {
  position: static !important;
  max-height: none !important;
  overflow: visible !important;
}

/* ---------------------------------------------------------------------------
   Wider question tiles
   ---------------------------------------------------------------------------
   Several screens capped their tile row well below the space available, so the
   journey kept changing width screen to screen. Measured caps against content:

     Install method  #wq-step-4d   650px   the most constrained
     Heat source     #wq-step-2    720px   the user's reference for "generous"
     System          #systemTypeRow 760px
     Rooms / subfloor / spacing / floor covering   820-930px, already filling

   The caps are RAISED, not removed. Filling edge to edge was the first
   instinct and it is wrong: Insulation and Self-levelling are Yes/No screens,
   so a full-width row makes two ~380px slabs out of a tile containing one icon
   and the word "No". Ceilings that only bind at the wide end give the gain
   without the absurdity.

   BREAKPOINT — this guard is the whole ballgame, and the obvious choice is
   backwards. The workspace does not shrink monotonically: journey.css's
   `max-width: 1180px` block drops the sidebar 300 -> 250 and workspace padding
   36 -> 24, so measured content width is

     910px viewport -> 516    1180 -> 786    1181 -> 713    1440 -> 972

   i.e. the content area jumps 73px WIDER as the viewport gets 1px NARROWER.
   Guarding at `min-width: 1181px` therefore applies the widening to the
   NARROWEST case and skips the wider one. `min-width: 901px` is correct: it
   covers every band where content exceeds ~516px and leaves everything at or
   below 900px on the existing mobile rules, including the byte-locked
   flex-column stacking in thermii.css that the <=700 rules depend on.

   Raising a cap can never make anything narrower — a max-width only binds when
   the container would otherwise exceed it — so the width half of this block is
   safe at every width inside the guard. */
@media screen and (min-width: 901px) {
  /* Three-up rows follow the shared column. Counted, not guessed: at 1040 they
     land at ~347px per tile, and per-tile width is the only thing that decides
     whether a tile row reads as tiles or as slabs. */
  #systemTypeRow {
    max-width: var(--journey-content-max);
  }

  /* TWO-up, despite sitting next to three-up rows that go wider. Heat Source is
     Boiler | Heat Pump, so 820 is already ~410px a tile; on the shared column
     they would be 520px each. Same reason #eq-q7 / #eq-q8 stay at 620 below.
     The rule is per-tile width, so the caps differ where the tile counts do. */
  #wq-step-2 .electric-covering-row {
    max-width: 820px;
  }

  /* Three-up: Screed | Joist | Overlay. */
  #wq-step-4d .electric-covering-row {
    max-width: var(--journey-content-max);
  }

  /* The question steps' inner column. Rows that stayed centred (everything
     except eq-q7/q8) centre against THIS, not against their own cap, so while
     it ran the full card width a widened shell pushed them further from the
     left-pinned headline: Heat Source measured L=588 against a headline at
     402. Bounding it to the shared column pulls that back to ~512.

     Width only — margins are deliberately untouched. The area steps use a
     680px .eq-question-full with `margin: 0 auto`, and this cap cannot bind on
     those, so nothing that is centred today moves. Left-pinning the whole
     journey is still the bigger call it always was. */
  .eq-question-full {
    max-width: var(--journey-content-max);
  }

  /* The System screen's Continue is right-ranged inside a footer that spans the
     whole card, so widening the shell pushed it away from the content rather
     than with it: measured at 1680 the headline and tiles ended at 1302/1282
     while the button sat at 1354-1594. Bounding the footer to the shared column
     puts the button — and its divider — back on the content's right edge.
     Only this one needs it; every other CTA already sits inside the 680px
     question column. #thermiiHowItWorksBtn comes too, or its centred pill
     would centre on the card (998) instead of on the content (922). */
  .thermii-confirm-footer,
  #thermiiHowItWorksBtn {
    max-width: var(--journey-content-max);
    margin-right: auto;
  }

  /* Insulation and Self-levelling are the two-option screens. They are flex
     rows whose tiles are `flex: 0 1 auto`, so today they shrink to their text
     and sit marooned in the row. Let them share the row instead, bounded at
     620px so two tiles never become slabs. */
  /* Left-pinned as well as capped. Tile rows are centred by default, so the
     narrower the cap the further right the row drifts: at 1440 the 620px cap
     alone moved these from x=438 to x=568, against a headline at 402. Capping
     them without this would have bought width at the cost of alignment.
     Deliberately scoped to these two rows — the other screens' rows are still
     centred as they always were, and re-aligning the whole journey is a
     bigger call than this change should make on its own. */
  #eq-q7 .electric-covering-row,
  #eq-q8 .electric-covering-row {
    max-width: 620px;
    margin-right: auto;
    margin-left: 0;
  }

  #eq-q7 .electric-covering-row .filter-option,
  #eq-q8 .electric-covering-row .filter-option {
    flex: 1 1 0;
  }

  /* Thermostat cards are deliberately NOT widened. They are pinned by both
     `width: 218px` and `max-width: 218px`, so flex-grow alone is inert — the
     max-width wins — and lifting it would change the fixed card width that the
     water journey's thermostat CAROUSEL scrolls against. Growing a row of
     product photographs is also the least convincing case for "bigger": the
     imagery is fixed-aspect and would gain white space, not detail. Left as
     is, on purpose.

     `.filter-option` carries a fixed `height: 224px` (journey.css:346), which
     silently defeats every per-screen min-height written against it. Releasing
     it to auto is what makes those existing rules — and the two below — mean
     anything. Grid and flex both stretch items to the tallest in the row, so
     rows stay level. */
  .eq-step .filter-option {
    height: auto;
  }

  /* The two screens left visibly short of their band. Install method had no
     min-height at all and rendered at 200px against System's 286px for the
     same 3-up layout; Rooms sat at 176px, the smallest in the journey. */
  #wq-step-4d .filter-option {
    min-height: 286px;
  }

  #eq-q1 .room-tiles-grid .filter-option,
  #wq-step-4 .room-tiles-grid .filter-option {
    min-height: 210px;
  }
}

/* ---------------------------------------------------------------------------
   The nine "Next" buttons become the gradient Continue
   ---------------------------------------------------------------------------
   All nine were wearing `.neohub-info-btn` — the NeoHub *info* button: a
   transparent outlined pill at 13px, built as a tertiary affordance and then
   patched inline with `font-size:16px;padding:8px 22px` to bulk it up. Someone
   already felt it was underweight for the job.

   It was. Each of the nine is the only way forward on its screen — every
   onclick advances the journey (revealAndAnswer, wConfirmInstallMethod,
   wCommitRoom, wAddRoomFromInput …). None is a secondary confirm. Meanwhile
   the gradient already meant "primary advance" on Version, System and both kit
   CTAs, so these were the only holdouts. wOtherRoomNext was further adrift
   still: a fully inline-styled orange (#ff5b00) pill.

   The inline styles are gone from index.html rather than beaten with
   !important. The parity contract compares ids, names, values, data-* and
   inline handlers by exact list equality — `style` is not among them — so
   stripping presentation there is permitted, and it is the cheaper fix than
   nine more !important declarations. Verified 243/240/223 unchanged after.

   `display` declarations were deliberately preserved. wInstallConfirmBtn and
   wSpacingConfirmBtn carry `display:none` on the button; #eqTstatContinue and
   #wNeoHubConfirm carry it on the wrapper, and legacy-core.js toggles all four
   at runtime (`style.display=''` for the buttons, `'block'` for the wrappers).
   Stripping those would have made hidden steps appear. For the same reason
   nothing here sets `display` on a wrapper: `display:flex !important` would
   have overridden the `none` and pinned the NeoHub confirm permanently open.

   Right-alignment therefore comes from the button's own auto margin rather
   than a flex parent, which works whether the wrapper ends up block or flex.
   `width: fit-content` is what makes that margin bite — a block-level flex box
   would otherwise fill the row and have no free space to push against. */
#totalAreaNext,
#heatedAreaNext,
#wOtherRoomNext,
#wAreaNext,
#wManifoldDistNext,
#wInstallConfirmBtn,
#wSpacingConfirmBtn,
#eqTstatContinue > button,
#wNeoHubConfirm > button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  width: fit-content;
  min-width: 240px;
  min-height: 54px;
  margin-right: 0;
  margin-left: auto;
  padding: 14px 24px;
  border: 0;
  border-radius: 13px;
  background: var(--thermii-gradient);
  box-shadow: 0 10px 22px rgba(113, 56, 237, .2);
  color: #fff;
  font: 600 15px/1 'Poppins', sans-serif;
  cursor: pointer;
}

#totalAreaNext:hover,
#heatedAreaNext:hover,
#wOtherRoomNext:hover,
#wAreaNext:hover,
#wManifoldDistNext:hover,
#wInstallConfirmBtn:hover,
#wSpacingConfirmBtn:hover,
#eqTstatContinue > button:hover,
#wNeoHubConfirm > button:hover {
  filter: saturate(1.1) brightness(1.03);
}

/* Every one of these starts disabled and is enabled by the engine. .35 came
   from .neohub-info-btn; .45 matches the kit CTAs, which are the closest
   equivalent control. wOtherRoomNext is the exception — legacy-core.js writes
   `style.opacity` on it directly, so its inline value wins and is left to. */
#totalAreaNext:disabled,
#heatedAreaNext:disabled,
#wAreaNext:disabled,
#wManifoldDistNext:disabled,
#wInstallConfirmBtn:disabled,
#wSpacingConfirmBtn:disabled {
  opacity: .45;
  cursor: not-allowed;
}

/* The footer rule above each one, matching .thermii-confirm-footer on the
   System screen. #eqTstatContinue and #wNeoHubConfirm are safe unconditionally
   because the whole wrapper is hidden with them. The other two toggle the
   BUTTON, so their wrapper would paint a stray divider over empty space while
   hidden — hence the guard on the inline `display:none` the engine writes. */
/* The same treatment for the gate footers thermii-gate.js injects into the
   tile-choice steps, and for the carpet confirm now wearing the same class.
   Without it they shrink to their button inside .eq-question-full's centred
   column and the Continue lands mid-screen instead of on the content's right
   edge — measured 240px wide at x=390 against a 966px tile row. The System
   screen's footer is not inside .eq-step, so it is untouched. */
.eq-step .thermii-confirm-footer {
  align-self: stretch;
  width: 100%;
}

div:has(> #totalAreaNext),
div:has(> #heatedAreaNext),
/* SCOPED TO ITS OWN WRAPPER (2026-09-11). js/thermii-skin.js now lifts this
   button into the action bar, and the bar is a <div> — so the unscoped
   `div:has(> #wOtherRoomNext)` matched the BAR and drew a stray divider and
   34px of margin across it. Scoping keeps the divider on the inner wrapper it
   was written for, where it still applies if the button is ever not claimed. */
#wOtherRoomWrap div:has(> #wOtherRoomNext),
div:has(> #wAreaNext),
div:has(> #wManifoldDistNext),
div:has(> #wInstallConfirmBtn:not([style*="none"])),
div:has(> #wSpacingConfirmBtn:not([style*="none"])),
#eqTstatContinue,
#wNeoHubConfirm {
  /* .eq-question-full is `flex-direction: column; align-items: center`, so
     these wrappers shrink to their button and sit centred — leaving the
     button's `margin-left: auto` no free space to push against, and the
     divider only as wide as the button itself. Stretching the wrapper to the
     full column width is what gives both something to work with. `width` is
     belt-and-braces for the wrappers whose parent is an ordinary block. */
  align-self: stretch;
  width: 100%;
  margin: 34px 0 0;
  padding: 22px 0 0;
  border-top: 1px solid var(--journey-border);
}

@media screen and (max-width: 700px) {
  #totalAreaNext,
  #heatedAreaNext,
  #wOtherRoomNext,
  #wAreaNext,
  #wManifoldDistNext,
  #wInstallConfirmBtn,
  #wSpacingConfirmBtn,
  #eqTstatContinue > button,
  #wNeoHubConfirm > button {
    width: 100%;
    min-width: 0;
  }
}

/* ---------------------------------------------------------------------------
   Question headlines and subtext: one left margin across every screen
   ---------------------------------------------------------------------------
   Measured on the live journey, the headline's left edge moved between
   screens: Rooms and Heat Source at 402px, System at 450, the area steps at
   451. Rooms and Heat Source are the reference.

   Worth recording what it was NOT, because the headlines are the obvious
   suspect and they are innocent. Every one of them already computed
   `text-align: left`, and their max-width/margin-auto never bit, because the
   widths involved were at or above the space available. Rewriting those
   changed nothing at all.

   Every container starts at the same 392px. What differs is what sits between
   that edge and the text:

     System      #systemTypeCard pads 58px, question steps pad 10px   → +48
     Area steps  a 680px .eq-question-full with `margin: 0 auto`      → +49
     Thermostats text-align:center — the only genuinely centred text

   So this fixes containers and one text-align, and leaves the headlines to
   inherit the result. Widths are preserved throughout: the 680px wrapper and
   the 760px tile row keep their measure — both are load-bearing for how their
   contents lay out — and only stop being centred.

   Scoped to `.eq-step` and `#systemTypeCard` rather than to the panels,
   because #eq-summary and #kitPanel live INSIDE #electricPanel (and their
   water equivalents inside #waterPanel). Scoping to the panel would have
   dragged the last two screens along with it; scoping to .eq-step cannot,
   since neither the summary nor the kit carries that class. */

/* (1) Normalise the measure. The headlines carried three different max-widths
   — 900 / 760 / 680 — so even once they share a left edge they wrapped at
   different points, and normalising them was the fix.

   This said `none` until 2026-08-07, on the reasoning that 900 already
   exceeded the ~778px then available so the cap never bound. Raising the shell
   cap to 1720px made that false: the workspace reaches 1364 and unbounded
   headlines and body copy ran to 1272px, which is far past a readable measure.
   Back to a single shared bound — still left-pinned, still one measure for all
   four, but a measure that exists. The containers are what should go fluid on
   a wide screen; a line of text is not improved by being longer. */
.eq-step .eq-title,
.eq-step .eq-subtext,
#systemTypeCard .kb-section-title,
#systemTypeCard .eq-subtext {
  max-width: var(--journey-content-max);
  margin-left: 0;
  margin-right: auto;
  text-align: left;
}

/* The headline-to-subtext gap, which is the last thing that differs between
   the System screen and the rest. Both use the same 20px/11px margins, but
   they resolve differently: `.eq-question-full` is flex, where adjacent
   margins ADD (20 + 11 = 31), while #systemTypeCard is block, where they
   COLLAPSE to the larger of the two. journey.css:504 also writes the System
   headline's margin as the `margin: 0 auto` SHORTHAND, zeroing the bottom, so
   the collapse resolves to max(0, 11) = 11px against 31px everywhere else.

   Setting 20px here would NOT fix it — it would collapse to 20. 31px is the
   value that makes a collapsing context produce the same gap a flex one adds,
   and it is written as a plain number for exactly that reason: it is the sum
   of the other screens' 20 + 11, not a spacing token in its own right. Both
   measured on the live journey rather than computed from the stylesheets. */
#systemTypeCard .kb-section-title {
  margin-bottom: 31px;
}

/* (2) The System screen's 48px. Measured: every container starts at 392, but
   #systemTypeCard pads 58px against the question steps' 10px, so its content
   began at 450 where Rooms began at 402. Only the horizontal padding is
   touched — the 56px/44px vertical padding is what makes it read as a card.

   Held to the desktop breakpoint deliberately. The ≤900px and ≤430px rules
   already tighten this to 20px and 6px, and the mobile pass is a separate job
   we have parked. */
@media screen and (min-width: 901px) {
  .thermii-main-shell #systemTypeCard {
    padding-right: 10px;
    padding-left: 10px;
  }
}

/* (3) The area steps wrap their question in a 680px .eq-question-full with
   `margin: 0 auto`, centring it ~49px in. Keep the 680 — it sets the measure
   for the inputs underneath — and drop only the centring. Selectors mirror
   journey.css:683 exactly, because that rule is ID-scoped (1,1,0) and a
   class-based override cannot reach it however late it loads. */
#wq-step-4b .eq-question-full,
#wq-step-4bb .eq-question-full,
#eq-q4 .eq-question-full,
#eq-q5 .eq-question-full {
  margin-left: 0;
  margin-right: auto;
}

/* (4) The only screens whose TEXT was genuinely centred rather than merely
   inset. Also ID-scoped in journey.css, so this matches its specificity and
   wins on load order. */
#eq-q6 .eq-title,
#wq-step-4e .eq-title {
  text-align: left;
}

/* (5) The System tile row keeps its 760px width but stops floating in the
   middle, so it starts on the same margin as the headline above it rather
   than 9px in once the card padding is corrected. */
#systemTypeRow {
  margin-left: 0;
  margin-right: auto;
}

/* ---------------------------------------------------------------------------
   Dark sidebar
   ---------------------------------------------------------------------------
   The sidebar is persistent chrome — who Thermii is, how far you are, what the
   project looks like — while the panel beside it is the live task. Both being
   white left only a 1px border to say so. Dark navy makes that split structural
   instead of implied, and the brand gradients read better on it than on white.

   Lives here rather than in components.css because that file is prefix-locked
   (append-only), so its light values cannot be edited at source. Everything is
   scoped under `.thermii-sidebar`, which is also what makes it win: the stat
   rows are styled by thermii-progress.css, which loads AFTER this file, but its
   selectors are `body .thermii-pstat …` (0,1,2) against our (0,2,1) — so we
   take precedence on specificity rather than order.

   The tokens below are redefined locally rather than rule-by-rule. Any sidebar
   rule already written as var(--thermii-border) / --thermii-navy /
   --journey-muted / --thermii-purple flips automatically, including the stat
   rows thermii-progress.js injects at runtime and anything added later. Only
   hard-coded hex values need the explicit overrides that follow.

   Contrast measured against the *card* surface (the lightest background in
   here, #1f274e once the translucent panels composite over the navy), not the
   base — the base flatters every value and would hide a failure:

     ink    #f2f5ff  13.2:1     muted  #aab4d6  7.0:1
     link   #93a8ff   6.4:1     accent #b9a8ff  6.9:1

   All clear 4.5:1 for body text; the icon strokes clear 3:1 as graphics.

   Not touched: `.thermii-side-spark` (guide-alive.css pins those brand hues
   with !important, and they already read on dark) and `.thermii-guide-blink i`,
   which is the mascot's eye and is meant to be white. The `tav-g0/g1/g2`
   gradient defs live inside this subtree — nothing here may hide or reparent
   them or the mascot renders invisible. */
.thermii-sidebar {
  --thermii-border: rgba(255, 255, 255, .10);
  --thermii-navy: #f2f5ff;
  --journey-muted: #aab4d6;
  --thermii-purple: #b9a8ff;

  --tsb-ink: #f2f5ff;
  --tsb-muted: #aab4d6;
  --tsb-link: #93a8ff;
  --tsb-accent: #b9a8ff;
  --tsb-surface: rgba(255, 255, 255, .055);
  --tsb-line: rgba(255, 255, 255, .10);

  color: var(--tsb-ink);
  background:
    radial-gradient(circle at 55% 11%, rgba(147, 168, 255, .16), transparent 34%),
    linear-gradient(168deg, #0a0f2c 0%, #111a42 60%, #0d1436 100%);
}

/* Intro ------------------------------------------------------------------- */
.thermii-sidebar .thermii-side-copy {
  color: var(--tsb-muted);
}

.thermii-sidebar .thermii-side-avatar::before {
  background: radial-gradient(circle, rgba(20, 157, 232, .34), rgba(147, 168, 255, .16) 48%, transparent 72%);
}

/* Time estimate ----------------------------------------------------------- */
.thermii-sidebar .thermii-side-time {
  background: var(--tsb-surface);
}

.thermii-sidebar .thermii-side-time span {
  color: var(--tsb-muted);
}

.thermii-sidebar .thermii-side-time-icon {
  border-color: var(--tsb-link);
  box-shadow: 0 4px 12px rgba(0, 0, 0, .35);
}

/* Help card --------------------------------------------------------------- */
.thermii-sidebar .thermii-side-help-icon {
  background: rgba(147, 168, 255, .14);
}

.thermii-sidebar .thermii-side-help h3 {
  color: var(--tsb-accent);
}

.thermii-sidebar .thermii-side-help p {
  color: var(--tsb-muted);
}

.thermii-sidebar .thermii-side-help a {
  color: var(--tsb-link);
}

/* Insight ----------------------------------------------------------------- */
.thermii-sidebar .thermii-insight {
  border-color: var(--tsb-line);
  background: linear-gradient(145deg, rgba(255, 255, 255, .07), rgba(255, 255, 255, .035));
  box-shadow:
    0 1px 0 rgba(255, 255, 255, .06) inset,
    0 12px 30px rgba(0, 0, 0, .30);
  color: var(--tsb-ink);
}

.thermii-sidebar .thermii-insight-toggle:hover {
  background: rgba(255, 255, 255, .05);
}

.thermii-sidebar .thermii-insight-icon {
  border-color: var(--tsb-line);
  background: linear-gradient(135deg, rgba(20, 157, 232, .26), rgba(147, 168, 255, .22), rgba(212, 20, 196, .18));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .14),
    0 5px 14px rgba(0, 0, 0, .30);
}

body.thermii-spark-recommending .thermii-sidebar .thermii-insight-icon {
  border-color: rgba(140, 198, 63, .42);
  background: linear-gradient(135deg, rgba(140, 198, 63, .30), rgba(20, 157, 232, .20), rgba(147, 168, 255, .18));
}

.thermii-sidebar .thermii-insight-heading strong {
  color: var(--tsb-accent);
}

.thermii-sidebar .thermii-insight-heading span,
.thermii-sidebar .thermii-insight-panel {
  color: var(--tsb-muted);
}

.thermii-sidebar .thermii-insight-chevron,
.thermii-sidebar .thermii-insight-panel li::before,
.thermii-sidebar .thermii-insight-panel a {
  color: var(--tsb-link);
}

.thermii-sidebar .thermii-insight-panel strong {
  color: var(--tsb-ink);
}

.thermii-sidebar .thermii-insight-panel::before {
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .18), transparent);
}

.thermii-sidebar .thermii-insight-extra {
  border-top-color: rgba(255, 255, 255, .16);
}

/* Project card ------------------------------------------------------------ */
.thermii-sidebar .thermii-project-card {
  background: var(--tsb-surface);
}

.thermii-sidebar .thermii-project-icon {
  color: #7ff0e4;
  background: rgba(31, 219, 202, .16);
}

.thermii-sidebar .thermii-project-label,
.thermii-sidebar .thermii-project-next {
  color: var(--tsb-muted);
}

/* Stat rows (injected by thermii-progress.js) ------------------------------ */
.thermii-sidebar .thermii-pstat {
  border-bottom-color: var(--tsb-line);
}

.thermii-sidebar .thermii-pstat.is-total {
  background: linear-gradient(135deg, rgba(20, 157, 232, .20), rgba(147, 168, 255, .18));
}

.thermii-sidebar .thermii-pstat.is-total > span {
  color: var(--tsb-muted);
}

/* Circuit hydraulics flag (injected by js/thermii-hydraulics-warn.js) --------

   Fires when a circuit needs more pump head than a domestic circulator delivers
   (CIBSE DHDG 2026 s5.5.5.4, 7-8 m). Deliberately reuses the amber of the
   engine's own "N rooms short" badge rather than introducing a second warning
   colour: that badge sits in the heat panel on this same page, thermii.css is
   byte-locked so it cannot be brought along, and two visibly different ambers on
   one screen is the worse outcome.

   CONTRAST, measured against the real surfaces:
     #a06000 ink on #fffbea   4.86:1  passes AA for body text (needs 4.5)
     #c08000 icon on #fffbea  3.20:1  passes 1.4.11 for a non-text indicator
     #f0c000 border          1.65:1  DECORATIVE, and that is deliberate

   The border is exempt from 1.4.11's 3:1 because it is not "required to
   understand the content": the state is carried by the icon at 3.20:1 AND by the
   words "Circuit too long". Nothing here signals by colour alone, which is also
   what satisfies 1.4.1 Use of Colour. --thermii-warning (#ff5b18) is NOT used:
   it measures 3.11:1 on white and 2.99:1 on this amber, so it fails as body text
   on both and fails even as a non-text indicator on the amber. */

.thermii-hyd-flag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  /* 12px is the engine's own badge size. The type scale has no 12px stop, but a
     13px pill sitting 40px from a byte-identical 12px one is the inconsistency
     worth avoiding. */
  font-size: 12px;
  font-weight: var(--thermii-font-weight-semibold);
  color: #a06000;
  background: #fffbea;
  border: 1px solid #f0c000;
  border-radius: 20px;
  padding: 3px 11px;
  white-space: nowrap;
}

.thermii-hyd-strip {
  margin: 0 0 12px;
  padding: 12px 14px;
  background: #fffbea;
  border: 1px solid #f0c000;
  border-radius: 10px;
  color: #a06000;
  font-size: 13px;
  line-height: 1.45;
}

.thermii-hyd-strip p { margin: 0 0 6px; }
.thermii-hyd-strip p:last-child { margin-bottom: 0; }

.thermii-hyd-strip-lead {
  font-weight: var(--thermii-font-weight-semibold);
}

.thermii-hyd-strip-lead svg {
  vertical-align: -1px;
  margin-right: 4px;
}

.thermii-hyd-strip-why,
.thermii-hyd-strip-ask {
  font-weight: var(--thermii-font-weight-regular);
}

/* De-chromed so it reads as a link inside the prose, but it is a <button>:
   html2canvas drops anchor text, which is why the engine swaps anchors for PDF
   generation at legacy-core.js:5412. */
.thermii-hyd-fix {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-decoration: underline;
  cursor: pointer;
}

.thermii-hyd-fix:hover { text-decoration: none; }

.thermii-hyd-fix:focus-visible {
  outline: 2px solid #a06000;
  outline-offset: 2px;
}

/* The room header is a flex row that already carries a name, chips and a
   chevron. Scoped to flagged headers only, so no unflagged room's layout moves.
   order:9 with a full basis drops the pill to its own line while leaving the
   chevron on row one. */
@media (max-width: 700px) {
  .kit-room-hdr.is-hyd-flagged { flex-wrap: wrap; }
  .kit-room-hdr.is-hyd-flagged .thermii-hyd-flag { order: 9; flex-basis: 100%; }
}

/* thermii.css:2365-2387 owns the print rules and is byte-locked, so the print
   treatment for these nodes has to live here. The flag must survive onto the
   quote PDF — it is the one thing on the page a fitter needs to see. */
@media print {
  .thermii-hyd-flag,
  .thermii-hyd-strip {
    background: #fffbea !important;
    border-color: #f0c000 !important;
    color: #a06000 !important;
  }
  .thermii-hyd-fix { display: none; }
}

/* ── Mobile chrome, <=900px ────────────────────────────────────────────────
   Two unrelated fixes sharing the breakpoint at which the shell goes single
   column (journey.css:1672). This was 820px — the breakpoint components.css:453
   uses for the compact sidebar — until the band block at the end of this file
   extended the mobile layout up to meet journey's 900. THREE BLOCKS NOW SHIP
   AND REVERT TOGETHER: this one, the companion bar below it, and the band.

   Both are `screen and` on purpose: A4 portrait is 793.7 CSS px, i.e. inside
   this query, and components.css:520-527 must keep winning in @media print.
   No !important anywhere, so it still does. */
@media screen and (max-width: 900px) {

  /* (1) The sidebar greeting is an introduction, so show it once. It is
     identical on all ten steps while only .thermii-insight below it changes
     per screen.

     Keyed on .thermii-version-active, NOT [data-thermii-screen]: the class
     ships in the served markup (index.html:21) and flips synchronously in
     ui.js:55/69/86, whereas navigation.js:158 sets the attribute inside a
     requestAnimationFrame having already latched `queued = true`
     (navigation.js:155-159). Drop that frame — which the preview pane does,
     since it holds the document at visibilityState 'hidden' — and the
     attribute never arrives at all, so an attribute-based :not() would hide
     the greeting on screen 1, the one screen that needs it.

     Target is the greeting's own wrapper div. Never .thermii-side-intro or
     .thermii-side-avatar: the tav-g0/g1/g2 <defs> live at index.html:95-105
     inside the avatar, and svg.wIntroAvatar at index.html:245-248 paints from
     them with no defs of its own, so hiding that subtree silently unpaints it.
     (Only that one consumer — the electric and water intro avatars carry their
     own eav-g0..2 and wav-g0..2 defs at index.html:349 and :1026.)

     (0,3,2), so it beats components.css:476's (0,2,1) on specificity as well
     as order. Worth 27px at 375px and nothing at all by 700px, where the 58px
     avatar governs row 1 once .thermii-side-copy fits on one line. */
  body:not(.thermii-version-active) .thermii-side-intro > div:last-child {
    display: none;
  }

  /* (2) Stage header: unwrap the kicker, un-overlap the progress bar.

     journey.css:124-129 sits in an unconditional @media screen (opened at
     journey.css:7, closed at :1654), so its
     `grid-template-columns: var(--journey-stage-col) minmax(0,1fr)` repeals
     components.css:480's mobile `1fr` at every width. At 375px that boxes
     .thermii-stage-summary into the clamp's 150px floor rather than the 325px
     content box components.css:481's flex row was written for. Inside 150px
     the row goes negative (74.5px max-content kicker + 145px bar), the left
     child freezes at its 70px automatic minimum, and "Step 1 of 10" — 74.5px
     once :237 adds its .07em tracking — misses by 4.5px and wraps, while the
     bar lands flush and rides 9px up into the title's line box.

     Dropping the flex row is the smallest correct fix, and it makes mobile
     match desktop rather than deviate from it: above 821px the summary is
     already block with the bar stacked beneath the title. Restoring `1fr`
     instead was measured and is worse — the header becomes two rows
     (126 -> 160px) and align-items:flex-end keeps the overlap.

     Sizing .thermii-stage-progress, not #thermiiStageProgress: the inner span
     carries a JS-written inline width:% (ui.js:20, navigation.js:138) that no
     rule short of !important could beat. */
  .thermii-stage-header { min-height: 0; }
  .thermii-stage-summary { display: block; }

  .thermii-stage-progress {
    width: auto;
    margin: 10px 0 0;
  }
}

/* ── Companion bar, <=900px, screens 2-10 ──────────────────────────────────
   Collapses the sidebar to a single row: animated mascot left, Thermii Insight
   toggle beside it, chevron right. The greeting rule above frees row 1; this
   moves the insight into it. 168px -> 94px, or 195px -> 94px counting from
   before the greeting rule.

   THREE BLOCKS SHIP AND REVERT TOGETHER. Revert the greeting rule alone and
   the greeting auto-places into a new implicit row 2 and the bar grows back.
   Revert the band block at the end of this file alone and these rules land on
   a DESKTOP sidebar between 820 and 900: grid-column/grid-row are silently
   ignored on a flex item, so the insight would not move into row 1, and the
   greeting would be hidden from a 104px centred avatar leaving a headless
   mascot. The band block is what makes the sidebar a grid up there.

   `screen and` for the same reason as above: A4 portrait is 793.7 CSS px, and
   components.css:520-527's print `display:none!important` must keep winning.
   Nothing below uses !important, so it does. */
@media screen and (max-width: 900px) {

  /* (1) The insight joins the mascot in row 1 rather than stacking beneath it.
     `grid-column: 2` is the only load-bearing declaration — components.css:467's
     1fr track is already 245px and spark.css:654 already sets width:100%.
     `grid-row: 1` is belt-and-braces: auto-placement lands it there anyway, but
     stating it means a later change to the avatar's placement cannot silently
     push the insight back onto a second row.

     margin-top, NEVER the `margin` shorthand. This selector is (0,2,1), an
     exact TIE with spark.css:667-680's `body.thermii-spark-* .thermii-insight`,
     so we win only on sheet order (7th vs 5th). That rule floats the insight as
     a fixed bottom-sheet toast during reactions and centres it under a 430px cap
     with `margin: 0 auto`. A `margin: 0` here would out-order it and pin the
     toast flush left. Measured at 760px: toast x=165 centred with margin-top,
     x=14 with the shorthand. Invisible at 375px, where the 430px cap never
     binds — which is exactly why it is written down. Same caution applies to
     position, left/right/bottom, width and max-width: do not declare them here. */
  body:not(.thermii-version-active) .thermii-insight {
    grid-column: 2;
    grid-row: 1;
    margin-top: 0;
  }

  /* (2) The insight carries its own 34px Thermii avatar, which in the bar sits
     ~26px from the animated one — two marks side by side, and the static one is
     a remote SVG that degrades to an empty disc offline.

     Hiding it buys 0px of height but frees 44px of the 245px column: the prompt
     track goes 149px -> 193px, which stops four of the twenty-three insight
     screens wrapping their prompt onto a second line (water-rooms,
     water-install-method, electric-rooms, electric-floor-covering). That is what
     makes the bar a CONSTANT height on every screen instead of jittering by
     4.5px as the user navigates.

     :not(.is-reacting) is what keeps it honest. During the four spark states
     spark.css:667-680 floats the insight as a fixed toast, where that avatar is
     the whole point: it carries spark.css:350-364's reaction animations and, for
     `recommending`, thermii-system.css:1283's green tint. Note the tint never
     renders in the bar at this breakpoint — the body class that applies it is
     the same one that makes the insight position:fixed — so guarding here
     preserves it in full rather than trading it away.

     Keyed on .is-reacting rather than the four body classes because
     thermii-spark.js:401-402 adds `is-reacting` and the body class in one
     synchronous block, and clearReaction (:318-319) removes them together;
     spark.css:696/700 already relies on that lockstep. One class instead of
     four :not()s, and it cannot drift if a fifth reaction level is added.

     No accessible-name change: the icon is already aria-hidden="true" wrapping
     an alt="" img (index.html:129-131). Verified identical either way —
     "Thermii Insight Water, electric or unsure?" */
  body:not(.thermii-version-active) .thermii-insight:not(.is-reacting) .thermii-insight-icon {
    display: none;
  }

  /* (3) MANDATORY PARTNER TO (2) — not cosmetic. display:none removes the grid
     ITEM, not the TRACK. spark.css:93 and :706 template the toggle at
     `34px minmax(0,1fr) 18px`, so without this the heading auto-places into the
     34px icon track and wraps to four lines while the chevron takes the 149px
     track: toggle 58 -> 104px, sidebar 94 -> 140px. Worse than doing nothing.

     (0,4,1) clears both spark.css:93 and spark.css:706 (each (0,1,0)), so it
     holds below the 520px breakpoint too. visibility:hidden is the wrong fix —
     it keeps the 34px track and its 10px gap, wasting 44px of a 245px column. */
  body:not(.thermii-version-active) .thermii-insight:not(.is-reacting) .thermii-insight-toggle {
    grid-template-columns: minmax(0, 1fr) 18px;
  }
}

/* ── Compact band, 820-900px ───────────────────────────────────────────────
   THE BUG. journey.css:1672 (max-width:900px) drops .thermii-app-grid to a
   single column, but components.css:453 only switches the sidebar to its
   compact layout at max-width:820px. Between the two the sidebar stacks above
   the shell still wearing full desktop dress: 104px centred avatar, time,
   rule, project card, 42px top padding, and components.css:74's
   min-height:760px on the grid un-zeroed. Measured at 850 before this block:
   sidebar 772x672px, main shell 793px down the page, stepper viewport 562px
   (it scrolls), and a 96px vertical divider from journey.css:140 drawn beside
   a header the mobile rules would have flattened.

   THE FIX. Extend the whole mobile band up to 900 so it meets journey's
   breakpoint. components.css is prefix-locked, so its :453 and :574 blocks are
   replicated here rather than widened. Every declaration below was chosen by
   measurement, not by copying: a verbatim copy re-hosts each declaration from
   components.css (3rd) to this file (7th), where ~10 of them stop losing to
   journey.css and start winning. Those are omitted. The result is a computed-
   style diff between 820 and 821 that is empty apart from the 1px of viewport
   (verified across 27 selectors x 40 properties on the version screen, the
   system screen and the water heat-source screen).

   OMITTED ON PURPOSE - each measured identical on both sides of 820, so
   copying them would CHANGE the band rather than match it:
     .thermii-app-grid{grid-template-columns:1fr}  journey.css:100 (uncond.)
     .kb-wrapper{min-height:100dvh}                journey.css:94
     .thermii-stage-header{grid-template-columns;margin;gap}  journey.css:125-128
     .thermii-stepper{min-width:600px}             journey.css:168 -> 610px
     .thermii-step-label{font-size:8px}            journey.css:317 -> 9px
     .thermii-workspace{padding:0 22px}            journey.css:1678 -> 20px
     .thermii-version-body{padding-top/left/right} journey.css:428
     .thermii-version-body h1{font-size:27px}      system.css:142 -> 30px
     .thermii-side-copy{font-size:11px;line-height} system.css:219 -> 13px
     .thermii-side-avatar img{...}                 dead selector, no <img>
     .thermii-side-spark{display:none} + spark.css:636's reversal  net no-op
     .thermii-insight{border-color;background;box-shadow}  system.css:1262
     .thermii-guide-mark{width/height}   guide-alive.css:332 already gives 52px
       across the whole <=900 range. An 80px mark here overflows the 58px
       avatar box by 22px, is clipped flat by .thermii-sidebar{overflow:hidden}
       and collides with the insight card. Add nothing.

   LOWER BOUND IS 820, NOT 821. `(max-width:820px)` and `(min-width:821px)` are
   disjoint but NOT adjacent: measured in Chrome, a viewport of 820.25-820.6
   CSS px (reachable at 150% page zoom, or directly on a 2x display) matches
   NEITHER, and the layout snaps back to the desktop sidebar on a sub-pixel
   island. min-width:820px overlaps components.css:453 by ~0.1px instead; the
   overlap is a proven no-op (computed-style diff at exactly 820, band on vs
   off: empty).

   UPPER BOUND IS 900, byte-identical to journey.css:1672, so the two flip on
   the same sub-pixel and no third layout can appear between them.

   `screen and` is load-bearing, not decorative: components.css:520-527's print
   `display:none!important` must keep winning, and Chrome's print Scale setting
   lays A4 out at 793.7/scale - 882px at 90%, i.e. inside this band. Nothing
   below uses !important, so print is unaffected either way. */
@media screen and (min-width: 820px) and (max-width: 900px) {

  /* Mobile chrome. This is the half of the change that is a design decision
     rather than a bug fix: 821-900 loses the wrapper's border and 20px radius
     and drops to the 105px wordmark. It is also what buys the stepper its
     viewport back - 533px -> 623px at 821, so the stepper (min-width 610px)
     stops scrolling from 850 up instead of scrolling across the whole band. */
  body { padding: 0; }
  .kb-wrapper { padding: 12px; border: 0; border-radius: 0; }
  .kb-logo {
    min-height: 58px;
    padding: 0 4px 10px;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
  }
  .kb-logo-side:first-child,
  .kb-logo-side:last-child {
    display: flex;
    flex: 0 0 auto;
    flex-direction: row;
    align-items: center;
    gap: 0;
  }
  .kb-return-btn,
  .thermii-shell-restart { min-height: 38px; padding: 7px 10px; font-size: 11px; }
  .kb-return-btn svg,
  .thermii-shell-restart svg { width: 18px; height: 18px; }
  .kb-logo-screen { width: 105px; height: auto; }

  /* The sidebar box. min-height:0 is the single most load-bearing declaration
     here - it is the only thing that beats components.css:74's 760px floor on
     a grid that journey.css has already collapsed to one column. */
  .thermii-app-grid { min-height: 0; }

  .thermii-sidebar {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 14px;
    align-items: center;
    padding: 16px 18px;
  }
  /* spark.css:632's partner. Only observable on a screen with no insight. */
  body.thermii-has-insight .thermii-sidebar { align-items: start; }

  .thermii-side-intro { display: contents; text-align: left; }
  .thermii-side-avatar { grid-row: 1; width: 58px; height: 58px; margin: 0; }
  .thermii-side-intro > div:last-child { grid-column: 2; }
  .thermii-side-title { font-size: 18px; }
  .thermii-side-copy { max-width: none; margin-top: 4px; }
  .thermii-side-time,
  .thermii-side-rule,
  .thermii-side-help,
  .thermii-project-card { display: none; }

  /* Stage header. min-height:0, summary display and progress sizing come from
     the widened block below, which already owns them at <=820 - not repeated
     here. Only the padding is components-side. */
  .thermii-stage-header { padding: 18px 0 14px; }

  /* journey.css:140 guards the vertical divider with `min-width:821px` and its
     comment says "below 821px ... the whole mobile band is parked". That
     premise is what this change moves, so the divider has to be switched off
     with it. Suppressed here rather than by re-guarding journey.css:140 to
     901px: `max-width:900px` and `min-width:901px` would reopen exactly the
     sub-pixel dead zone described above, one breakpoint higher. Same
     specificity as journey.css:143, wins on sheet order (7th vs 4th). */
  .thermii-stage-summary::after { content: none; }

  .thermii-stepper-wrap { margin: 0 -18px; padding: 0 18px 5px; }

  /* Screen 1. min-height:0 is the single-column concern — the desktop stage
     reserves full height for the two-column shell, which this band no longer
     has. padding-bottom only: journey.css:428 owns the other three sides, and
     the `padding` shorthand from components.css:488 would out-order it here.

     DELIBERATELY NOT replicated from components.css:490-491:
       .thermii-version-options { grid-template-columns:1fr; max-width:450px }
       .thermii-version-card    { min-height:230px }
     Those force the cards 1-up, which is right on a phone and wrong here. The
     band has 796-876px of content, so the cards stay 2-up at 370px each and
     the stage is 641px instead of 895px — both visible at once, no scroll.
     Replicating them for seam-continuity with 820 would have made the layout
     non-monotonic in the other direction: 1-up at 900 but 2-up again at 901,
     on a WIDER viewport. Card count should only ever go up with width. */
  .thermii-version-stage { min-height: 0; }
  .thermii-version-body { padding-bottom: 28px; }

  /* Full-bleed action bar, as at <=820. -20px not components.css:492's -22px:
     that value pairs with the 22px workspace gutter of the <=820 block, but
     journey.css:1678 sets 20px across <=900, so -22px overshoots the workspace
     by 2px each side. Measured: -22px gives 11/839 against a 13/837 workspace;
     -20px lands exactly on it. Longhands, so journey.css:428's other sides
     survive. */
  .thermii-version-footer { margin-right: -20px; margin-left: -20px; }

  /* The live half of thermii-spark.css:631-703. */
  .thermii-insight {
    position: relative;
    grid-column: 1 / -1;
    width: 100%;
    margin: 2px 0 0;
    backdrop-filter: blur(18px) saturate(120%);
    -webkit-backdrop-filter: blur(18px) saturate(120%);
  }

  /* MANDATORY PARTNER to the two :not(.is-reacting) rules in the companion-bar
     block below. Those rules hide the insight's own avatar and re-track the
     toggle, and they are free ONLY because during a reaction spark.css:667-680
     floats the insight out of the bar as a fixed toast. That rule is
     max-width:820px, so without this copy the guards invert: measured at 850
     without it, a second static Thermii mark pops in 26px from the animated
     one and the heading jolts 48px sideways on every confirmed choice.
     This block is appended AFTER the two companion-bar blocks, so here the
     toast's `margin: 0 auto` out-orders their `margin-top: 0` instead of the
     other way round. Same computed result - top 0, sides auto - so the toast
     still centres: measured 430px wide, x=185/195/210/235 at 800/820/850/900,
     i.e. exactly centred at every width. The <=820 caution still applies to
     the companion block itself: keep `margin-top`, never the shorthand. */
  body.thermii-spark-responding .thermii-insight,
  body.thermii-spark-recommending .thermii-insight,
  body.thermii-spark-celebrating .thermii-insight,
  body.thermii-spark-triumphing .thermii-insight {
    display: block;
    position: fixed;
    z-index: 1200;
    right: max(14px, env(safe-area-inset-right));
    bottom: max(14px, env(safe-area-inset-bottom));
    left: max(14px, env(safe-area-inset-left));
    width: auto;
    max-width: 430px;
    margin: 0 auto;
  }

  .thermii-insight-toggle { min-height: 58px; padding: 9px 11px; }

  /* Without this an opened panel grows unbounded inside a 94px bar. */
  .thermii-insight-panel {
    max-height: min(52vh, 360px);
    padding-right: 13px;
    padding-left: 13px;
    padding-bottom: 14px;
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .thermii-insight.is-reacting .thermii-insight-toggle { pointer-events: none; }
  .thermii-insight.is-reacting .thermii-insight-chevron { visibility: hidden; }
}

/* ---------------------------------------------------------------------------
   Wider question tiles, part two — the dense screens from Rooms onward.

   The :812 block above widened the screens that were already 2-up and 3-up.
   This one is about column COUNT, which is the only lever that moves the
   screens carrying 5, 6 and 7 options: measured at 1440 the shared column is
   966px, so a five-up row can only ever be 180px a tile no matter what its cap
   says. Three-up on the same column is 311px — the identical figure to
   #wq-step-4d, which is already in the family Heat Source and the Version
   cards belong to. The reference is reachable by re-columning and by nothing
   else.

   Counts are FIXED per screen, never auto-fit. Measured across 901-1920,
   repeat(auto-fit, minmax(220px, 1fr)) re-columns three times and leaves a
   one-tile orphan on five of the eight rows — 4+1 on the five-option screens
   at 1440, 3+1 on pipe spacing at 1280, a five-row stack on Rooms below 1100.
   A fixed count cannot do any of that.

   Counts chosen: 5 -> 3+2, 6 -> 3+3, 7 -> 4+3. These are the same splits the
   <=900 rules already use, so desktop and mobile now agree on how these
   screens break. Part rows stay LEFT-ranged, per journey.css:484 — grid, not
   a centred flex row: the title is the anchor and the row shares its edge.

   Rooms (10) and pipe spacing (4) get their caps lifted and keep their counts.
   4+4+2 on Rooms measures 231px a tile but costs a third row carrying two
   tiles beside a 490px hole, +224px, to gain 49px of width; 2+2 on pipe
   spacing measures 475px a tile — wider than the Version card — for four
   numbers, at +258px. Both trade badly and both were left alone.

   Deliberately untouched: #wq-step-2 and the Version stage (the references),
   #wq-step-4d and #systemTypeRow (already on target), #eq-q7 / #eq-q8 (the
   620px two-option cap is intentional, see the :812 block), and the thermostat
   rows — pinned at 218px by width AND max-width, and read back by
   wTstatCarouselScroll at legacy-core.js:3085.

   Vertical cost at 1440x900, measured: five screens go from 140px of page
   scroll to 270-366px. The Rooms screen already sits at 268 today. */
@media screen and (min-width: 901px) {

  /* Cap lifts. These four rows stop short of the shared column for no reason
     that survives the widening — 820, 880 and 930 against a 966px container at
     1440 and a 1040px one above 1528. Raising a cap can never make anything
     narrower, so these are safe at every width inside the guard. */
  #wq-step-4ps .electric-covering-row,
  #wq-step-4fc .electric-covering-row,
  #wq-step-4 .room-tiles-grid,
  #eq-q1 .room-tiles-grid {
    max-width: var(--journey-content-max);
  }

  /* FIVE OPTIONS, FIVE UP (Loz, 2026-09-14: "make the tiles on the subfloor
     screen all in one row" — and all three five-option screens with it: the
     water subfloor, the floor level, and the electric subfloor). A rule here
     used to override journey.css's five-column base to THREE, which left a row
     of three and an orphan pair beneath it; measured at 1850x1000 that second
     row pushed the "Not sure" link and the Insights panel below the fold. The
     base rule now stands above 900px: 195px tiles on the 1040px column — the
     same size the room tiles already are, so consecutive screens stop switching
     tile sizes — and every label still on one line ("Existing Concrete" is the
     longest). journey.css's own breakpoints keep three-up below 900 and two-up
     below 700, untouched.

     THE BAND JUST ABOVE 900 STAYS THREE-UP. Measured at 950px the five-up tile
     is 146px and "Suspended Floor" and "Existing Concrete" wrap to two lines;
     the column is wide enough for five one-line labels from about 1000px. 1100
     is the breakpoint the heat panel already folds at, so the journey has one
     number for "too narrow for the wide layout" rather than two. */
  @media screen and (max-width: 1100px) {
    #wq-step-4flr .electric-covering-row,
    #wq-step-4c .electric-covering-row,
    #eq-q2 .electric-covering-row {
      grid-template-columns: repeat(3, minmax(0, 1fr));
    }
  }

  /* Seven options, four up, at every desktop width. This also removes a real
     inversion: journey.css:1661 gave 4-up/182.5px at 1180 and the base rule
     gave 7-up/101px at 1181, so tiles got 45% NARROWER as the window got
     WIDER. Measured across the boundary now: 183.9 / 184.1 / 184.3. */
  #wq-step-4fc .electric-covering-row {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  /* Electric floor covering had no per-screen rule anywhere in the stylesheet.
     It fell through to the base flex row, so its tiles were content-sized and
     UNEQUAL (measured 118.1, 108, 108, 108, 108, 108), never grew at any width
     from 901 to 1920, and sat marooned mid-row while justify-content:center
     held them 104px off the left-pinned headline. It also re-wrapped as the
     window moved — 3+3, then 4+2, then a live 5+1 orphan at ~1100, then 6-up.
     Grid fixes the width, the alignment and the orphan at once.

     Scoped to this ID and not to the class: #eq-q7 / #eq-q8 are the only other
     flex rows left, and their tiles are flex:1 1 0, which grid would make
     inert. The min-height is the one height here that is not inherited —
     eq-q3 is the only tile row in either journey with no min-height of its own
     — and it makes electric's two covering screens dimensionally identical,
     matching how #wq-step-4c and #wq-step-4flr already match each other. */
  #eq-q3 .electric-covering-row {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  #eq-q3 .filter-option {
    min-height: 235px;
    padding-right: 10px;
    padding-left: 10px;
  }

  /* The hard <br class="fo-brk"> in the locked markup forces New Concrete, New
     Timber and Existing Timber onto two lines beside a one-line Suspended
     Floor and Existing Concrete — the baseline is ragged, and a 311px tile
     makes that more conspicuous, not less. .fo-brk is only suppressed below
     700px today. Measured after: every subfloor label on one line at 1180 and
     above, at zero extra height. */
  #wq-step-4c .fo-brk,
  #eq-q2 .fo-brk {
    display: none;
  }
}

/* Floor covering rejoins the journey's tile scale.

   Split from the block above on purpose, and pinned to the same 1181 boundary
   journey.css:1661 already owns. The 58px icon, the 14px label and the 8px
   side padding are density concessions made for a 124px tile. They are still
   the right call at 901-1180, where the desktop shell gives this row only
   487-766px and four-up is 110-184px a tile — restoring 78px furniture there
   would push "Deep Carpet & Underlay" to three lines and add 26px of height to
   a tile with no room for it. Above 1181 the tile is 184px and up, so the
   screen can carry the same furniture as every other tile row.

   Measured at the boundary: width steps 184.1 -> 184.3, i.e. continuous; the
   min-height already steps 170 -> 190 there today and is unchanged. Content
   never exceeds it — one-line tiles measure 166px, the single two-line tile
   188px. At 1440 the row is 234x190 (aspect 1.23), 252.5x190 at 1720.
   Icon-to-tile lands at 33%, between Rooms at 36% and Install method at 25%. */
@media screen and (min-width: 1181px) {
  #wq-step-4fc .filter-option {
    padding: 24px 14px;
  }

  #wq-step-4fc .fo-icon {
    width: 78px;
    height: 78px;
  }

  #wq-step-4fc .fo-icon img {
    width: 74px;
    height: 74px;
  }

  #wq-step-4fc .fo-label {
    font-size: 16px;
  }

  #wq-step-4fc .fo-brk {
    display: none;
  }
}

/* ── ONE STROKE WEIGHT FOR EVERY CONTROL — 1px (Loz, 2026-09-18) ────────────
   "should we have ALL buttons throughout Thermii have the same border/stroke
   weight? I think they should be ALL the thinner stroke?" — yes, then a second
   pass: "just run it" for the selection tiles too. Audited live, the app
   carried THREE strokes with no system: 1px (nav chrome, the CTA outline,
   room/covering tiles), 1.5px (the switch pills, the Add-to-Kit tool buttons,
   the manifold port steppers, the document buttons, Back, the sticky bar, AND
   the js-radio-tile / thermostat-card tiles) and 2px (+ Add Another Room, Done,
   the electric watt selector, close-quote). Loz saw 1px/1.5px/2px side by side
   on the biggest button and picked 1px.

   ONE TOKEN, so the weight is stated once and cannot drift back to three.

   EVERY `<button>` IN THE JOURNEY, because the selection tiles ARE
   `<button>`s and Loz wants them unified with the rest — so the rule no longer
   excludes the tile classes, only the header nav chrome (a separate component,
   and already 1px). A tile's selected/recommended state is a colour swap and an
   `::after` ring, never a border WIDTH (checked across all four stylesheets), so
   thinning the resting stroke leaves every selection cue intact.

   THE COLLAPSE CHEVRON RINGS ARE UNTOUCHED, deliberately (Loz: leave the
   chevrons). They are `<span>` rings — `#eRoomChev-*`, `#eHeatChev`,
   `#wCatChev-*` — not `<button>`s, so this `button` selector never reaches them;
   they keep their 2px, which frames the ▼ icon without looking fussy the way a
   1px ring around an icon would.

   `!important` because most of these controls are drawn by the manifest-locked
   engine with an inline `border:1.5px solid …` / `border:2px solid …`
   shorthand, and an inline declaration is only beaten by `!important` — it
   overrides just the WIDTH, leaving each control's own colour and style intact.
   Setting a width where the style is `none` (the filled CTAs, the qty/trash
   controls) paints nothing, so the rule is safe to apply broadly. */
:root {
  --thermii-btn-stroke: 1px;
}

body.thermii-skin button:not([class*="thermii-skin"]) {
  border-width: var(--thermii-btn-stroke) !important;
}

/* ── THE SYSTEM TOTALS BLOCK, TIGHTENED (Loz, 2026-09-18 mock) ──────────────
   Three tweaks to the kit screen's "System totals" section, all screen-only
   (the PDF has its own totals treatment in journey.css's print block and must
   not inherit these):

   3. A HAIRLINE ABOVE THE HEADING, to separate the section from the add-room
      buttons above it ("the white line below them"). Full content width, so it
      reads as a section rule; the add-room row's own 40px bottom margin gives
      the air above it.

   4. THE HEADING SITS CLOSER TO ITS PANELS. It was ~38px off (the eyebrow's
      10px bottom margin plus the hero's 28px top); the mock wants the label
      tight to the cards it introduces, so both are pulled in to ~14px total.

   These two live on the same element — `.thermii-kit-eyebrow-totals` — so the
   line, the space under it, and the gap to the panels are one considered unit.
   `body.thermii-skin` lifts them to (0,2,0), over journey.css's (0,1,0). */
@media screen {
  body.thermii-skin .thermii-kit-eyebrow-totals {
    border-top: 1px solid #e6e9ef;
    padding-top: 30px;
    margin-top: 10px;
    margin-bottom: 0;
  }

  /* 4 — 14px from heading to panels (Loz, 2026-09-18: "tighten it to 14px").
     The eyebrow box bottom sits ~10px below its baseline text, so this 4px plus
     that residual reads as the 14px he asked for; measured on the 2-room kit. */
  body.thermii-skin .thermii-totals-hero {
    margin-top: 4px;
  }

  /* 5. "SET YOUR MIXING VALVE TO xx" LINES UP WITH "PLUS xxm SPARE IN THE
        COILS SUPPLIED." The two notes end their cards but sat 9px apart because
        the pipe card's bar+legend and the flow card's temps row are different
        heights. The cards are equal height, so pinning each note to its card's
        bottom edge aligns them across the row. `:has()` scopes the flex to only
        the two note-bearing cards, leaving the heated-area card (whose last row
        is the room breakdown) in normal flow. */
  body.thermii-skin .thermii-totals-card:has(.thermii-totals-spare),
  body.thermii-skin .thermii-totals-card:has(.thermii-totals-note) {
    display: flex;
    flex-direction: column;
  }

  body.thermii-skin .thermii-totals-spare,
  body.thermii-skin .thermii-totals-note {
    margin-top: auto;
  }
}

/* ── THE ADD-ON PANELS JOIN THE 1px SYSTEM (Loz, 2026-09-18) ────────────────
   The Floor Sensor and neoHub cards — both journeys, and both the deselected
   white state and the selected green `.is-added` state — are the last controls
   on the screen still wearing a 2px stroke, now that the buttons and selection
   tiles are 1px. They are the same selection-card family as the tiles, so they
   take the same hairline. They are `<div>`s (`.neohub-card`, which the Floor
   Sensor card reuses), not `<button>`s, so the app-wide button rule above does
   not reach them — this is the companion rule for them.

   thermii.css sets them `border: 2px solid #e8e8e8` (byte-locked); `.is-added`
   swaps only the border COLOUR to green, so a single width override covers both
   states. `body.thermii-skin` (0,2,0) outranks the base `.neohub-card` (0,1,0)
   and ties `.is-added` (0,2,0) but loads later and sets only the width, which
   `.is-added` never touches — no !important needed (these carry no inline
   border, unlike the engine's buttons). Same token, so there is still one
   stated stroke weight for the whole app. */
body.thermii-skin .neohub-card {
  border-width: var(--thermii-btn-stroke);
}
