/* ===================== THE SIMULATOR'S OWN STYLES =====================
 * Split out of style.css on 16 September 2026 and loaded ONLY on /simulator, after style.css.
 * Every rule here was proved twice before it moved: Chrome's rule-usage tracking said no other page
 * uses it, and every class it names appears in the simulator's markup or sim.js and nowhere else.
 * If you add a rule here that anything but the simulator needs, that page will silently lose it.
 * scratchpad/csssplit.mjs is what did the split and scratchpad/cssuse.mjs is what measured it. */


/* Shown only if the drawing cannot be built. It has to look like a considered message rather than
   an error, because the person reading it came here to be reassured about their home. */
.sim-fallback {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-5) var(--space-4);
  text-align: center;
  color: var(--text-muted);
}

.sim-fallback p { margin: 0 0 0.75rem; }

.sim-fallback p:last-child { margin-bottom: 0; }

.sim-fallback strong { color: var(--color-ink); }

.sim-fallback a { color: var(--color-blue); font-weight: 600; }



/* ===================== MOULD SIMULATOR: property/system pickers (Phase 2) ===================== */
.mould-section { background: var(--color-white); border-radius: var(--radius-lg); }


.picker-block { max-width: 900px; margin: 0 auto var(--space-6); }

.picker-heading {
  text-align: center;
  font-size: var(--text-base);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-ink-muted);
  margin-bottom: var(--space-3);
}

.picker-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-3);
}

.picker-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  background: var(--color-white);
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  cursor: pointer;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-ink);
  text-align: center;
  transition: transform var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}

.picker-card small { display: block; font-weight: 400; color: var(--color-ink-muted); margin-top: 2px; }

.picker-icon { width: 40px; height: 40px; color: var(--color-navy); }

.picker-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-soft); }

.picker-card[aria-checked="true"] {
  border-color: var(--color-blue);
  background: var(--color-surface-tint-blue);
  box-shadow: var(--shadow-soft);
}

.picker-card[aria-checked="true"] .picker-icon { color: var(--color-blue); }


.loft-toggle-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  margin-top: var(--space-4);
  flex-wrap: wrap;
}

.loft-toggle-label { font-weight: 600; font-size: var(--text-sm); }

.loft-toggle { display: flex; gap: var(--space-2); }

.loft-toggle-btn {
  border: 2px solid var(--color-border);
  background: var(--color-white);
  border-radius: var(--radius-sm);
  padding: 0.4rem 1.2rem;
  font-weight: 700;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}

.loft-toggle-btn:hover { transform: translateY(-2px); }

.loft-toggle-btn[aria-checked="true"] {
  border-color: var(--color-blue);
  background: var(--color-surface-tint-blue);
  color: var(--color-navy-dark);
}


.diagram-hint {
  text-align: center;
  color: var(--color-ink-muted);
  font-size: var(--text-sm);
  margin-top: var(--space-2);
}


/* Down from 9.5px. A room name is a caption on a drawing, not a headline - at the old size
   "Bathroom" ran the full width of the bathroom and sat on top of the thing it was naming. */
.room-label { font-family: var(--font-body); font-size: 8px; font-weight: 600; fill: #5f6a80; opacity: 0.9; }

.window-fog { pointer-events: none; }


.mould-widget {
  display: grid;
  grid-template-columns: 1.34fr 1fr;
  gap: 2rem;
  /* Top-aligned, not centred: once the result panel appears the right column is much taller, and
     centring would leave the diagram floating in the middle of a tall empty column. */
  align-items: start;
}

.house-svg {
  width: 100%;
  height: auto;
  /* The viewBox is 480x400. Stating it here rather than leaving `height: auto` to infer it is what
     stops Safari computing a height of zero and rendering the centrepiece of the site as a blank
     white rectangle. min-height is the second belt: something is always visible. */
  aspect-ratio: 480 / 400;
  min-height: 220px;
  background: #0c1730;
  border-radius: var(--radius);
}

.mould-colony { transition: opacity 0.5s ease; }

.mould-spot { transition: r 0.4s ease; }

.mould-controls label {
  display: block;
  font-weight: 700;
  margin-bottom: 0.5rem;
}

#humiditySlider { width: 100%; margin-bottom: 1rem; accent-color: var(--ice-blue); }

.mould-warning {
  background: #fdecea;
  color: #8a2e1c;
  border: 1px solid var(--flame-orange);
  padding: 0.6rem 0.8rem;
  border-radius: var(--radius);
  font-weight: 600;
  margin-bottom: 1rem;
}


/* ===================== MOULD SIMULATOR: guided flow, guidance + result (Phase 4) ===================== */

/* --- Step rail --- */
.sim-steps {
  list-style: none;
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  max-width: 900px;
  margin: 0 auto var(--space-5);
  padding: 0;
}

.sim-step {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.45rem 0.9rem;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-white);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-ink-muted);
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}

.sim-step-num {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--color-surface);
  color: var(--color-ink-muted);
  font-size: var(--text-xs);
  font-weight: 700;
  flex: none;
}

.sim-step.is-done { color: var(--color-ok); border-color: var(--color-ok); background: var(--color-ok-bg); }

.sim-step.is-done .sim-step-num { background: var(--color-ok); color: var(--color-white); }


/* --- System cards: extra "best for" tag line --- */
.picker-cards-sys { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }

.sys-tag {
  display: block;
  margin-top: auto;
  padding-top: var(--space-2);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--color-blue);
  letter-spacing: 0.02em;
}

.picker-card[aria-checked="true"] .sys-tag { color: var(--color-navy-dark); }


.sys-help { text-align: center; margin-top: var(--space-3); }


/* --- System detail panel --- */
.sys-detail {
  margin-top: var(--space-4);
  padding: var(--space-4);
  background: var(--color-surface-tint-blue);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.sys-detail h4 {
  font-family: var(--font-heading);
  margin: 0 0 var(--space-2);
  color: var(--color-navy-dark);
  font-size: var(--text-lg);
}

.sys-detail > p { margin: 0 0 var(--space-3); color: var(--color-ink); }

.sys-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-3);
  margin: 0;
}

.sys-facts div { margin: 0; }

.sys-facts dt {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
  color: var(--color-ink-muted);
  margin-bottom: 2px;
}

.sys-facts dd { margin: 0; font-size: var(--text-sm); color: var(--color-ink); }

.sys-recommended {
  display: inline-block;
  margin-bottom: var(--space-2);
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  background: var(--color-navy);
  color: var(--color-white);
  font-size: var(--text-xs);
  font-weight: 700;
}


/* --- Humidity control --- */
.hum-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

.mould-controls .hum-head label { margin-bottom: 0.35rem; }

.hum-readout {
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  font-weight: 700;
  color: var(--color-navy);
  line-height: 1;
}

.hum-band {
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
  font-weight: 600;
}

.hum-band.band-ok { color: var(--color-ok); }

.hum-band.band-watch { color: var(--color-warn); }

.hum-band.band-risk { color: var(--color-danger); }


/* --- Simulate button gating --- */
#simulateFlowBtn { width: 100%; }

#simulateFlowBtn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  filter: grayscale(0.4);
}

.sim-blocker {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--color-ink-muted);
  text-align: center;
}





/* One line under each question saying what to do about it. The people this has to work for are
   a ten-year-old and an eighty-year-old, and a heading on its own is an instruction only to
   somebody who already knows what the answer looks like. */
.picker-help {
  margin: -6px 0 var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-ink-muted);
  max-width: 62ch;
}

.picker-heading + .picker-help { margin-top: 0; }


/* The fans, turning. Only once the system is running - a fan spinning in a drawing of a house
   nobody has switched on is the same lie as a mock portal.
   transform-box: fill-box is not optional: without it the rotation is measured against the whole
   SVG viewport and the impeller swings off across the drawing instead of turning on its hub. */
.unit-blade { fill: #a7bcd4; opacity: 0.9; }

.unit-rotor { transform-box: fill-box; transform-origin: center; }

.is-running .unit-rotor { animation: rotorSpin 0.62s linear infinite; }

@media (prefers-reduced-motion: reduce) {
.is-running .unit-rotor { animation: none; }
}



/* Stairs. The newels, the rail and the balusters are what make a stepped profile read as a
   staircase rather than as a set of steps drawn in a gap. */
.stairs-newel { fill: #a89377; stroke: #86724f; stroke-width: 0.5; }

.stairs-baluster { stroke: #c6b596; stroke-width: 1; opacity: 0.9; }

.eaves-gutter-rim { stroke: #8c98a8; stroke-width: 0.8; opacity: 0.85; }


/* Air arriving and leaving at the louvres. Simulated in app.js ("AIR AT THE LOUVRES"): vapour
   puffs going out, tapered threads curving in. The old cone, dashes and chevrons are gone - they
   read as a torch beam and a road sign - and so are the straight streaks, which read as a starburst. */
.louvre-air { pointer-events: none; }

.louvre-body { fill: #d2e8f8; fill-opacity: 0.15; }

.louvre-thread { fill: #e6f3fd; stroke: none; }

.louvre-thread--halo { fill: #c2dff4; fill-opacity: 0.22; }


/* The dingy wash. Multiply, so it darkens and cools what is already there rather than veiling
   it in grey - a damp house does not look like a house with a sheet over it, it looks like a house
   with the light gone out of it. */
.dingy { mix-blend-mode: multiply; pointer-events: none; }


/* Numbered balloons on the drawing, and the key to them underneath.
   The chips these replace were the width of a room, floated over the loft, overlapped each other
   and covered the very thing they pointed at - and on a phone it got worse, because the label
   grows and the drawing does not. A balloon can sit on a component without hiding it; the words
   live outside the house where there is room for them. */
.balloon-disc { fill: var(--color-navy-dark); }

.balloon-ring { fill: none; stroke: #ffffff; stroke-width: 1.6; opacity: 0.9; }

.balloon-shadow { fill: #0B1220; opacity: 0.35; }

.balloon-tick { stroke: #ffffff; stroke-width: 1.5; opacity: 0.85; }

.balloon-anchor { fill: #ffffff; stroke: var(--color-navy-dark); stroke-width: 1.2; }

.balloon-num {
  fill: #ffffff;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 9.5px;
}


.sim-key {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 8px 18px;
}

.sim-key-item {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  font-size: var(--text-sm);
  color: var(--color-ink);
  line-height: 1.35;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}

.sim-key-num {
  flex: 0 0 auto;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: var(--color-navy-dark);
  color: #fff;
  display: grid;
  place-items: center;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.72rem;
  margin-top: 1px;
}

@media (prefers-reduced-motion: reduce) {
.sim-key-item { transition: none; }
}


/* Internal walls, cut. Plasterboard faces either side of a stud core - the same treatment the
   external walls get, because a one-pixel line between two rooms is not a wall, and the upstairs
   of this house read as one continuous strip of plaster until they were drawn. */
.part-core { fill: #cfd6e2; }

.part-stud { fill: #c2ab86; opacity: 0.75; }

.part-face { fill: #eef1f6; }

.part-shadow { fill: #0B1220; opacity: 0.11; }


/* Screen, so it lifts what is under it rather than laying a pale film over it. */
.fresh-lift { mix-blend-mode: screen; pointer-events: none; }


/* The load going round. transform-box: fill-box, or the rotation is measured against the whole
   SVG viewport and the washing swings across the kitchen - the same trap as the fan impellers. */
.dryer-drum { transform-box: fill-box; transform-origin: 50% 50%; animation: dryerSpin 3.4s linear infinite; }

@media (prefers-reduced-motion: reduce) {
.dryer-drum { animation: none; }
}

.bulkhead-edge { fill: #0B1220; opacity: 0.13; }

.cupboard-door { fill: #eaeff5; stroke: #b8c2d0; stroke-width: 0.7; }


.wet-wall { pointer-events: none; }

/* Water on a wall reads as water because of the RUNS down it and the beads standing on it. A
   flat wash of grey behind the tiles never read as wet at any opacity, which is what "the wetness
   needs to be obvious" was about. */
.wet-runs { pointer-events: none; }

.wet-run { fill: #cfe4f2; opacity: 0.7; }

.wet-bead { fill: #e4f1fa; opacity: 0.66; }


/* --- Simulator: the running commentary ------------------------------------------------------
   BESIDE the drawing, not over it. It used to be a pill floating on the roof, which covered the
   house at exactly the moment the house was worth watching and could only hold four words - and
   four words is a caption, not an explanation.

   Five parts: a state line with a dot that is alive while the run is; the stage in large type,
   cross-fading as it changes; a sentence of plain English a beat behind it; the five steps
   ticking off so you can see what is still to come; and a rail. */
.mould-diagram-wrap { position: relative; }


/* The right-hand column: the commentary at the top, level with the roof of the house next to it,
   and the controls that change what it is describing underneath. */
.mould-side {
  display: grid;
  gap: var(--space-5);
  align-items: start;
}


.sim-narrate {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  background: linear-gradient(158deg, #17253f 0%, #0e1728 58%, #0a1120 100%);
  color: #fff;
  box-shadow: var(--shadow-soft);
}

/* One slow bloom of light drifting behind the type while the thing is live. It is the only
   moving part of the panel, and it is what makes it read as an instrument rather than a caption.
   Behind the content on its own z-index so it can never wash out the text. */
.sim-narrate::after {
  content: '';
  position: absolute;
  inset: -45% -65%;
  z-index: -1;
  opacity: 0;
  background: radial-gradient(closest-side, rgba(93, 168, 224, 0.34), transparent 72%);
  transition: opacity 700ms var(--ease-out);
}

.sim-narrate[data-state="running"]::after,
.sim-narrate[data-state="rewind"]::after {
  opacity: 1;
  animation: narrateGlow 7.5s ease-in-out infinite;
}

.sim-narrate[data-state="done"]::after { opacity: 0.5; }


.sim-narrate-eyebrow {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 var(--space-3);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.6);
}

.sim-narrate-dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.34);
  transition: background 400ms var(--ease-out);
}

.sim-narrate[data-state="running"] .sim-narrate-dot,
.sim-narrate[data-state="rewind"] .sim-narrate-dot {
  background: var(--color-orange);
  animation: narratePulse 1.7s ease-out infinite;
}

.sim-narrate[data-state="done"] .sim-narrate-dot { background: #4ec98a; }


/* The headline and its ghost occupy the same box, so the outgoing line leaves through the
   incoming one instead of the panel changing height under them. */
.sim-narrate-stage {
  position: relative;
  display: block;
  min-height: 2.5em;
}

.sim-narrate-line,
.sim-narrate-ghost {
  display: block;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 1.15rem;
  line-height: 1.22;
  letter-spacing: -0.01em;
}

.sim-narrate-ghost {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
}

.sim-narrate-line.is-in  { animation: narrateLineIn 540ms var(--ease-out) both; }

.sim-narrate-ghost.is-out { animation: narrateLineOut 430ms var(--ease-out) both; }


.sim-narrate-note {
  margin: 11px 0 0;
  font-size: var(--text-sm);
  line-height: 1.52;
  color: rgba(255, 255, 255, 0.74);
}

.sim-narrate-note.is-in { animation: narrateLineIn 640ms 110ms var(--ease-out) both; }


.sim-narrate-steps {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: var(--space-4) 0 0;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  display: grid;
  gap: 10px;
}

.sim-narrate-step {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  font-size: 0.79rem;
  line-height: 1.25;
  color: rgba(255, 255, 255, 0.62);
  transition: color 340ms var(--ease-out);
}

.sim-narrate-step-mark {
  position: relative;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, 0.26);
  transition: border-color 340ms var(--ease-out), background 340ms var(--ease-out),
              box-shadow 340ms var(--ease-out);
}

.sim-narrate-step-text { display: block; padding-top: 1px; }

/* The detail line under each tick. Only once the run has finished - during the run the headline
   and its sentence are already saying what is happening, and a second copy under every row would
   be five paragraphs competing with the one that matters. */
.sim-narrate-step-text small {
  display: none;
  margin-top: 3px;
  font-size: 0.72rem;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.56);
}

.sim-narrate[data-state="done"] .sim-narrate-step.is-done .sim-narrate-step-text small { display: block; }

.sim-narrate[data-state="done"] .sim-narrate-steps { gap: 13px; }

.sim-narrate[data-state="done"] .sim-narrate-step { align-items: flex-start; }


.sim-narrate-step.is-now { color: #fff; }

.sim-narrate-step.is-now .sim-narrate-step-mark {
  border-color: var(--color-orange);
  background: var(--color-orange);
  box-shadow: 0 0 0 4px rgba(209, 65, 36, 0.22);
}

.sim-narrate-step.is-done { color: rgba(255, 255, 255, 0.82); }

.sim-narrate-step.is-done .sim-narrate-step-mark {
  border-color: #4ec98a;
  background: #4ec98a;
}

.sim-narrate-step.is-done .sim-narrate-step-mark::after {
  content: '';
  position: absolute;
  left: 4.6px;
  top: 1.4px;
  width: 3.6px;
  height: 7.6px;
  border: solid #0a1120;
  border-width: 0 1.8px 1.8px 0;
  transform: rotate(42deg);
}


.sim-narrate-track {
  display: block;
  margin-top: var(--space-4);
  height: 3px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.14);
  overflow: hidden;
}

.sim-narrate-track > i {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--color-orange), #ffb38f);
  transition: width 620ms var(--ease-out);
}

@media (min-width: 1100px) {
/* Two-up, which keeps the panel about as tall as the roof beside it - but one column once the
     detail lines open underneath, or the rows tear into ragged pairs. */
  .sim-narrate-steps { grid-template-columns: 1fr 1fr; gap: 10px 18px; }
.sim-narrate[data-state="done"] .sim-narrate-steps { grid-template-columns: 1fr; }
}

@media (max-width: 900px) {
/* ================= THE PHONE LAYOUT IS A DIFFERENT LAYOUT =================
     On a desktop this is two columns: the house on the left, the commentary beside it, and both
     are on screen the whole time by construction. A phone has one column and about 730 usable
     pixels once the fixed header and the WhatsApp bar are taken off, so the only way to have the
     picture and the words at the same time is to PIN one and scroll the other - and to be strict
     about how much of the screen the pinned one is allowed.

     WHAT WAS WRONG, AND IT IS WORTH WRITING DOWN BECAUSE IT LOOKED RIGHT IN THE SOURCE.
     `.mould-diagram-wrap` was the thing declared sticky, on the reasoning that it is "the
     drawing". It is not. It is the drawing PLUS the numbered key, the caption, the Start button,
     the blocker and the result panel. Measured on a 390x844 phone at the commissioning beat:

         .mould-diagram-wrap   3046px   <- the element that said position: sticky
           house svg            273px
           numbered key         155px
           caption               69px
           Start button         106px
           result panel        2348px

     A sticky element four times taller than the viewport does not pin in any useful way - it
     scrolls like a static block and only catches at the very end. So the drawing did not stay on
     screen at all, and the commentary, sitting after that 3046px block, was six thousand pixels
     down the page. You could have the picture or the words and there was no scroll position that
     gave you both. That is exactly the reported fault.

     So the wrapper is dropped out of the layout entirely with `display: contents` and the SVG
     ITSELF is what pins - it is 273px, it is the thing you watch, and it is the only part of that
     stack that must never leave the screen. Everything else becomes a sibling and is ordered. */
  /* FULL-BLEED AND THE SCROLLBAR, MEASURED RATHER THAN GUESSED.
     Both pinned drawings below use `width: 100vw` so the copy cannot scroll up through a gutter
     beside them. 100vw includes a classic scrollbar, so this is the classic way to gain 15px of
     horizontal scroll - measured at 390, 700 and 880px, scrollWidth is unchanged either way, so
     nothing is done about it. If it ever does show up, the fix is `overflow-x: clip` on the body,
     and it MUST be `clip` rather than `hidden`: `hidden` makes a scroll container, and a scroll
     container is what `sticky` measures against, so it would silently unpin both drawings. */

  /* One gap, set here, rather than the desktop 2rem plus each block's own margin on top of it -
     that was stacking to about 90px between the caption and the Start button on a 390px screen,
     which is a quarter of the reading space spent on nothing. */
  .mould-widget { display: flex; flex-direction: column; gap: var(--space-4); }
.mould-side { display: contents; }
.mould-diagram-wrap { display: contents; }
.mould-diagram-wrap .sim-cta,
  .mould-diagram-wrap .sim-result { margin-top: 0; }
/* THE DRAWING. Full-bleed, because a pinned panel narrower than the screen lets the words
     scroll up through the gutters either side of it. Capped at 38vh so there is always most of
     the screen left for what it is being explained by - on an 844px phone that is 321px pinned
     and roughly 400px to read in, and on a short phone it shrinks with the screen rather than
     eating it. The shadow is what makes content read as passing UNDER it rather than through. */
  .house-svg {
    order: 1;
    position: sticky;
    top: 64px;
    z-index: 3;
    width: 100vw;
    margin-inline: calc(50% - 50vw);
    max-height: 38vh;
    border-radius: 0;
    box-shadow: 0 14px 28px -12px rgba(0, 0, 0, 0.75);
  }
.sim-fallback { order: 1; }
/* THE ORDER IS DRIVEN BY STATE, because what a phone screen owes you changes with it.
     Before pressing: the drawing and the Start button - a button below a 349px panel is a button
     nobody finds. While running: the drawing and the words, adjacent, nothing between them.
     Once finished: the result is the payoff and the commentary drops back down to it. */
  .sim-narrate[data-state="running"],
  .sim-narrate[data-state="rewind"] { order: 2; margin: 0; }
.diagram-hint { order: 3; }
.sim-cta, .sim-blocker { order: 4; }
.sim-narrate { order: 5; margin: 0; }
.sim-key { order: 6; }
.mould-controls { order: 7; }
/* ...and once it has finished, below the controls. The done state opens a detail line under
     every tick, which is most of a phone screen, and by then the thing worth reading is the
     result. */
  .sim-narrate[data-state="done"] { order: 8; margin: 0; }
.sim-result { order: 9; }
/* THE PANEL AND THE DRAWING HAVE TO BE ON ONE SCREEN TOGETHER, or the words describing what you
     are watching are off the bottom of it the moment you press the button. At desktop sizing the
     panel measured 628px against a 313px drawing on a 390px phone - the two together were taller
     than the viewport. */
  .sim-narrate { padding: var(--space-4); }
.sim-narrate-steps { grid-template-columns: 1fr 1fr; gap: 9px 16px; }
.sim-narrate[data-state="done"] .sim-narrate-steps { grid-template-columns: 1fr; }
.sim-narrate-line,
  .sim-narrate-ghost { font-size: 1.05rem; }
.sim-narrate-stage { min-height: 2.3em; }
.sim-narrate-note { font-size: 0.86rem; margin-top: 8px; }
.sim-narrate-steps { margin-top: 12px; padding-top: 12px; gap: 7px; }
.sim-narrate-step { font-size: 0.76rem; gap: 9px; }
.sim-narrate-step-mark { width: 14px; height: 14px; }
.sim-narrate[data-state="done"] .sim-narrate-steps { gap: 10px; }
.sim-narrate-step-text small { font-size: 0.7rem; }
}

@media (max-width: 460px) {
/* Still two-up. One column costs about sixty pixels of height, and the panel and the house
     have to fit on the same phone screen at the same time or the commentary is not commentary. */
  .sim-narrate-line, .sim-narrate-ghost { font-size: 1.02rem; }
.sim-narrate-note { font-size: 0.82rem; }
.sim-narrate-step { font-size: 0.72rem; gap: 8px; }
.sim-narrate-steps { gap: 8px 12px; }
}

@media (prefers-reduced-motion: reduce) {
.sim-narrate::after { animation: none; }
.sim-narrate-dot { animation: none; }
.sim-narrate-line.is-in,
  .sim-narrate-ghost.is-out,
  .sim-narrate-note.is-in { animation: none; }
.sim-narrate-ghost { display: none; }
.sim-narrate-track > i,
  .sim-narrate-step,
  .sim-narrate-step-mark { transition: none; }
}


/* The ring drawn round each source of moisture while it is being named. */
.cause-ring { pointer-events: none; }

/* Everything that is NOT the cause, dimmed. A ring on its own is a weak instruction on a picture
   this busy; taking the light off the rest of the house is not. */
.cause-dim { fill: #04070f; opacity: 0.66; }

.cause-ring-halo { fill: none; stroke: #D14124; stroke-width: 9; opacity: 0.16; }

.cause-ring-line { fill: none; stroke: #ff7a4d; stroke-width: 2; opacity: 0.95; stroke-dasharray: 5 4; }

/* Its name, on the drawing, inside the lit circle. Two words - the sentence stays in the panel. */
.cause-tick { stroke: #ff7a4d; stroke-width: 1.2; opacity: 0.8; fill: none; }

.cause-tag-bg { fill: #10192b; opacity: 0.92; }

.cause-tag {
  fill: #ffe8dd;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 7.6px;
  letter-spacing: 0.01em;
}

.cause-ring-line { animation: causeSpin 7s linear infinite; transform-box: fill-box; transform-origin: 50% 50%; }

@media (prefers-reduced-motion: reduce) {
.cause-ring-line { animation: none; }
}


/* The one pass of light. Screen blend so it lifts the drawing rather than veiling it. */
.sim-sweep { mix-blend-mode: screen; pointer-events: none; }


/* --- Simulator: the optional half ---------------------------------------------------------
   Closed by default. Everything inside it only refines the "before" picture, and none of it is
   needed to get a correct answer - so it should not be standing between the visitor and the one
   button on the page. */
.sim-tune {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-white);
  margin-bottom: var(--space-4);
}

.sim-tune > summary {
  cursor: pointer;
  padding: var(--space-4);
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 44px;
  position: relative;
  padding-right: 44px;
}

.sim-tune > summary::-webkit-details-marker { display: none; }

.sim-tune > summary::after {
  content: '';
  position: absolute;
  right: var(--space-4);
  top: 50%;
  width: 9px; height: 9px;
  border-right: 2px solid var(--color-ink-muted);
  border-bottom: 2px solid var(--color-ink-muted);
  transform: translateY(-70%) rotate(45deg);
  transition: transform var(--dur-fast) var(--ease-out);
}

.sim-tune[open] > summary::after { transform: translateY(-30%) rotate(-135deg); }

.sim-tune-title {
  font-family: var(--font-heading);
  font-weight: 700;
  color: var(--color-navy-dark);
}

.sim-tune-sub { font-size: var(--text-sm); color: var(--color-ink-muted); }

.sim-tune-body { padding: 0 var(--space-4) var(--space-4); }

.sim-tune-body > *:first-child { margin-top: 0; }


/* The button now lives under the drawing, so it spans that column rather than a sidebar. */
.mould-diagram-wrap .sim-cta { margin-top: var(--space-4); }

.mould-diagram-wrap .sim-result { margin-top: var(--space-4); }


/* --- Simulator: how the duct gets outside -------------------------------------------------
   Three real options with three real prices. Showing them here is the difference between a
   customer understanding a quote and being surprised by one. */
.piv-loft-note {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--color-blue);
  background: var(--color-surface-tint-blue);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  font-size: 0.92rem;
  line-height: 1.6;
  color: var(--color-ink);
}

.piv-loft-note strong { color: var(--color-navy); }


.outlet-wrap {
  margin-top: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-white);
}

.outlet-head {
  margin: 0 0 var(--space-3);
  font-family: var(--font-heading);
  font-weight: 700;
  color: var(--color-navy-dark);
}

.outlet-picker { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-2); }

.outlet-btn {
  display: block;
  padding: 0.6rem 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-white);
  cursor: pointer;
  text-align: center;
  min-height: 56px;
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}

.outlet-btn:hover { border-color: var(--color-blue); }

.outlet-btn-name {
  display: block;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-navy-dark);
  line-height: 1.25;
}

.outlet-btn-cost {
  display: block;
  margin-top: 2px;
  font-size: var(--text-xs);
  color: var(--color-ink-muted);
}

.outlet-note {
  margin: var(--space-3) 0 0;
  font-size: var(--text-sm);
  color: var(--color-ink-muted);
  line-height: 1.6;
}


/* Eaves: fascia, soffit board and gutter. A roof that sits straight on the wall with no overhang
   is a lid, and there is nowhere for a soffit terminal to go. */
/* Eaves. White uPVC fascia and soffit against a red roof, which is what most of the housing
   stock in South East London actually has, and a grey aluminium-look gutter hung off the fascia.
   The shadow line under the soffit is the detail that makes the overhang read as depth rather
   than as a white stripe painted on the wall. */
.eaves-soffit { fill: #eef1f6; stroke: #b3bccb; stroke-width: 0.6; }

.eaves-fascia { fill: #dfe4ec; stroke: #9aa5b6; stroke-width: 0.6; }

/* A darker gutter, so the fascia, the gutter and the terminal plenum stop reading as one white
   mass at the end of the roof. Most of this stock has black or grey half-round on it. */
.eaves-gutter { fill: #55606f; stroke: #39424f; stroke-width: 0.6; }

.eaves-shadow { stroke: #0B1220; stroke-width: 1.1; opacity: 0.22; }


/* The gable: the end wall carried up into the loft, which is where the wall route's louvres go.
   The verge is the mortared edge the tiling finishes against. */
.gable-verge { stroke: #cfd5df; stroke-width: 2.6; stroke-linecap: round; opacity: 0.85; }


/* --- Simulator: the one button that matters -----------------------------------------------
   It sits at the bottom of a column of choices, and it is the only control on the page that
   actually does anything, so it is sized like it. The ring breathes only while it is enabled and
   has not been pressed - a control that pulses after you have used it is just noise. */
.sim-cta {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  margin-top: var(--space-4);
  padding: var(--space-4);
  border: 0;
  border-radius: var(--radius-lg);
  background: linear-gradient(135deg, var(--color-orange) 0%, #b8371c 100%);
  color: var(--color-white);
  text-align: left;
  cursor: pointer;
  box-shadow: 0 2px 4px rgba(11, 18, 32, 0.12), 0 14px 30px -10px rgba(209, 65, 36, 0.55);
  transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}

.sim-cta:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(11, 18, 32, 0.14), 0 20px 40px -10px rgba(209, 65, 36, 0.6); }

.sim-cta:disabled { background: var(--color-surface); color: var(--color-ink-muted); box-shadow: none; cursor: not-allowed; }

.sim-cta-play {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.18);
  position: relative;
}

.sim-cta-play::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 54%;
  transform: translate(-50%, -50%);
  border-left: 14px solid currentColor;
  border-block: 9px solid transparent;
}

.sim-cta:disabled .sim-cta-play { background: rgba(11, 18, 32, 0.08); }

.sim-cta-text { display: block; }

.sim-cta-label {
  display: block;
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: -0.01em;
}

.sim-cta-sub { display: block; font-size: var(--text-sm); opacity: 0.82; }

.sim-cta.is-ready { animation: simReady 2.4s var(--ease-in-out) infinite; }

@media (prefers-reduced-motion: reduce) {
.sim-cta.is-ready { animation: none; }
}

.sim-blocker { margin-top: var(--space-2); }


/* --- Result panel --- */
.sim-result {
  margin-top: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--color-blue);
  border-radius: var(--radius-md);
  background: var(--color-white);
  box-shadow: var(--shadow-soft);
}

.sim-result h4 {
  font-family: var(--font-heading);
  margin: 0 0 var(--space-2);
  color: var(--color-navy-dark);
  font-size: var(--text-lg);
}

.sim-result-lead { margin: 0 0 var(--space-3); color: var(--color-ink); }

.sim-spec { list-style: none; padding: 0; margin: 0 0 var(--space-3); }

.sim-spec li {
  position: relative;
  padding-left: 1.5rem;
  margin-bottom: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-ink);
}

.sim-spec li::before {
  content: '✓';
  position: absolute;
  left: 0;
  top: 0;
  color: var(--color-ok);
  font-weight: 700;
}

.sim-delta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  padding: var(--space-3);
  margin-bottom: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  font-size: var(--text-sm);
  font-weight: 600;
}

.sim-result-flag {
  margin: var(--space-4) 0 0;
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--color-orange);
  background: #fdf1ec;
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  font-size: 0.94rem;
  line-height: 1.65;
  color: var(--color-ink);
}

.sim-result-flag strong { color: var(--color-navy); }


.sim-result-note {
  margin: var(--space-2) 0 0;
  font-size: var(--text-xs);
  color: var(--color-ink-muted);
  text-align: center;
}

@media (max-width: 768px) {
.sim-steps { gap: var(--space-1); }
.sim-step { font-size: var(--text-xs); padding: 0.35rem 0.6rem; }
.hum-head { flex-direction: row; }
}

@media (max-width: 768px) {
.mould-widget { grid-template-columns: 1fr; }
}



/* ===================== SIMULATOR STAGE (Phase 5) ===================== */
/* The stage is painted inside the SVG now, so the element itself is transparent. */
.house-svg {
  /* NOT transparent. The drawing paints its own sky, so an opaque ground behind it is never seen
     when things are working - and when they are not, the visitor sees the night sky rather than a
     white hole where the house should be. */
  background: #0c1730;
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: 0 18px 48px rgba(11, 18, 32, 0.28), 0 2px 6px rgba(11, 18, 32, 0.16);
}


/* The whole-dwelling figure. The number gets to be a number - it is the one thing on this panel
   somebody will write down - and the sentence beside it carries the caveat that stops it being a
   quote. */
.sim-result-rate {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  margin-top: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-tint-blue);
  border-radius: var(--radius-md);
  border-left: 3px solid var(--ice-blue);
}

.sim-rate-figure {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.55rem;
  line-height: 1.1;
  color: var(--navy);
  flex: none;
}

.sim-rate-body { font-size: 0.88rem; line-height: 1.55; color: var(--text-muted); }

@media (max-width: 560px) {
.sim-result-rate { flex-direction: column; gap: var(--space-2); }
}


/* --- The engineer -------------------------------------------------------------------------
   Read from the kit, not from the body, and never from a face - see the note in engineerSymbol.
   Everything is flat: a highlight on a figure this size reads as a cartoon before it reads as
   light. */
.eng-boot { fill: #1d2026; }

/* ORANGE WORK TROUSERS. With the white polo and the hard hat that is the whole read: you know what
   he does before you have looked at him, which is the point of drawing the kit rather than the
   person. */
.eng-trews { fill: #D9541F; stroke: #A83E13; stroke-width: 0.22; }

.eng-pad { fill: #2b3038; }

.eng-belt { fill: #1a1d23; }

.eng-pouch { fill: #2a2f38; }

/* WHITE, because the mark goes on it. Navy on navy put the logo on a body its own colour. */
.eng-polo { fill: #f4f6f8; stroke: #c8d0d9; stroke-width: 0.28; }

.eng-polo-shade { fill: #cfd6de; opacity: 0.75; }

.eng-collar { fill: none; stroke: #1A3B8B; stroke-width: 0.7; stroke-linejoin: round; }

.kit-box { fill: #c2a077; stroke: #9a7f5c; stroke-width: 0.35; }

.kit-box-lid { fill: #d4b48d; }

.kit-tape { stroke: #8d7350; stroke-width: 0.5; }

.kit-label { fill: #eee6d6; }

.eng-sleeve-line { stroke: #f4f6f8; }

.eng-sleeve-edge { stroke: #c3ccd6; }

.eng-skin-line { stroke: #c99b76; }

.eng-skin { fill: #c99b76; }

.eng-hair { fill: #4a382b; }

/* A HARD HAT, not a cap: a dome over the whole top of the head with a brim right round it, and
   the ribs that make it a hat rather than a bowl. Drawn as a band across the crown the skull shows
   above it and he reads as bald with a sweatband on, which is exactly what the cap did. */
.eng-hat { fill: #E8641E; stroke: #A8410E; stroke-width: 0.22; }

.eng-hat-brim { fill: #C9500F; }

.eng-hat-rib { fill: none; stroke: #C05412; stroke-width: 0.3; opacity: 0.85; }

/* A mug of tea, held level. */
.eng-mug { fill: #f2f4f7; stroke: #9aa5b1; stroke-width: 0.3; }

.eng-mug-handle { fill: none; stroke: #9aa5b1; stroke-width: 0.5; }

.eng-mug-tea { fill: #8a5a32; }


/* The tool bag. It goes in and out with him, which is most of what says "on a job" rather than
   "standing in a garden". */
.eng-bag { fill: #2b3240; stroke: #171c25; stroke-width: 0.3; }

.eng-bag-top { fill: #3c4657; }

.eng-bag-handle { fill: none; stroke: #1d232e; stroke-width: 0.55; stroke-linecap: round; }

.eng-bag-tool { stroke: #c8cfd8; stroke-width: 0.5; stroke-linecap: round; }


/* A pair of steps. The raked back legs are the part that stops it reading as a ladder leaning on
   nothing. */
/* TRADE YELLOW, not timber. A wooden pair of steps is a decorator's; the fibreglass or
   aluminium ones a contractor turns up with are yellow, and it is the same argument as the yellow
   ladder on the van roof - it says working vehicle, and this says working trade. The far stile is
   in shade so the two legs read as an A-frame rather than as a flat shape. */
/* Heavier, because at this size weight is what makes a thing read - not detail. The rails were
   1.3 units on a ladder nineteen pixels across, which is a hairline. */
.eng-steps-stile { fill: none; stroke: #E8B32A; stroke-width: 2.1; stroke-linecap: round; }

.eng-steps-back { fill: none; stroke: #A87C15; stroke-width: 1.9; stroke-linecap: round; }

.eng-steps-tread { stroke: #F7D66E; stroke-width: 1.9; stroke-linecap: round; }

.eng-steps-top { fill: #F7D66E; stroke: #A87C15; stroke-width: 0.5; }

.eng-steps-brace { fill: none; stroke: #A87C15; stroke-width: 1.2; stroke-linecap: round; }

.eng-board { fill: #e6dcc4; stroke: #a8a08a; stroke-width: 0.4; }

.eng-board-clip { fill: #8a919b; }

.eng-meter { fill: #2b3240; }

.eng-meter-lcd { fill: #9fd3a8; }

.eng-meter-vane { fill: none; stroke: #a8bcd8; stroke-width: 0.3; stroke-linecap: round; }

.eng-coil { fill: none; stroke: #b8c0ca; stroke-width: 1.6; }

.eng-coil-eye { fill: none; stroke: #b8c0ca; stroke-width: 1.1; }

.eng-coil-rib { stroke: #cdd4dc; stroke-width: 0.45; opacity: 0.8; }


/* The sign-off card. It is a sign-off, not a testimonial - the line under the words is what
   makes it mean something, and there is nothing on it pretending to be a review. */
.signoff-card {
  fill: #ffffff;
  stroke: #d6d9e0;
  stroke-width: 0.8;
  filter: drop-shadow(0 3px 10px rgba(11, 18, 32, 0.28));
}

.signoff-head {
  font-family: 'Space Grotesk', 'Inter', system-ui, sans-serif;
  font-size: 10px;
  font-weight: 700;
  fill: #1A3B8B;
  letter-spacing: -0.01em;
}

.signoff-sub {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 5.9px;
  font-weight: 500;
  fill: #4A5568;
}


/* The loft question, when the "pick one for me" button needs it answered before it can act. It
   comes off as soon as it is answered, or after a few seconds if it is not - a control that stays
   lit is a control that looks broken. */
.loft-toggle-wrap.is-asking {
  border-radius: var(--radius-md);
  outline: 2px solid var(--ice-blue);
  outline-offset: 6px;
  animation: askPulse 1.1s ease-in-out 3;
}

@media (prefers-reduced-motion: reduce) {
.loft-toggle-wrap.is-asking { animation: none; outline-color: var(--ice-blue); }
}


/* Extract fans. The sleeve is the part that says this goes OUTSIDE rather than into a cupboard,
   so it is drawn as a hole through the masonry - darker than the casing, with the backdraught
   shutter visible at the far end of it. */
.fan-sleeve { fill: #2f3845; stroke: #566274; stroke-width: 0.6; opacity: 0.95; }

.fan-shutter { stroke: #c3ccd9; stroke-width: 1.1; fill: none; stroke-linecap: round; }

.fan-fascia { fill: #f4f7fb; stroke: #8a94a5; stroke-width: 0.7; }

.fan-cord { stroke: #b9c3d1; stroke-width: 0.7; }

.fan-cord-bead { fill: #e8edf4; stroke: #96a1b2; stroke-width: 0.4; }

.fan-strap { stroke: #8d99aa; stroke-width: 1.1; stroke-linecap: round; }


/* The flow hood. Translucent skirt on a light frame - it has to read as fabric stretched over a
   former, not as a solid funnel, or it looks like a loudhailer. */
/* Outlined, not shaded. A pale hood on a pale wall with a 0.4 stroke is a smudge - the edge is
   what tells you it is an object being held up against something. */
.eng-hood-skirt { fill: #c6d2e2; fill-opacity: 0.88; stroke: #5d6878; stroke-width: 0.55; }

.eng-hood-rim   { fill: #eef2f8; stroke: #5d6878; stroke-width: 0.5; }

.eng-hood-rib   { fill: none; stroke: #8592a6; stroke-width: 0.42; stroke-linecap: round; }

.commission-body { fill: #0f2d1c; fill-opacity: 0.92; stroke: #3fbf6b; stroke-width: 1; }

.commission-rate { font-family: var(--font-heading); font-size: 9px; font-weight: 700; fill: #6ee7a0; }

.commission-unit { font-family: var(--font-body); font-size: 5.2px; font-weight: 700; fill: #6ee7a0; letter-spacing: 0.06em; }

.commission-room { font-family: var(--font-body); font-size: 4.4px; font-weight: 600; fill: rgba(255,255,255,0.82); letter-spacing: 0.04em; }

.commission-pass { font-family: var(--font-body); font-size: 4.6px; font-weight: 700; fill: #0f2d1c; letter-spacing: 0.08em; }

.commission-tick { fill: #3fbf6b; }

.commission-lead { stroke: #3fbf6b; stroke-width: 0.8; fill: none; stroke-dasharray: 2.5 2; }

.commission-check {
  fill: none;
  stroke: #0f2d1c;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 12;
  stroke-dashoffset: 12;
  animation: commissionTick 0.42s var(--ease-out) 0.3s forwards;
}

@media (prefers-reduced-motion: reduce) {
.commission-check { animation: none; stroke-dashoffset: 0; }
}


/* The tools. Steel reads as steel because it has a light edge and a dark body, not because it is
   grey - at this size that one contrast is the whole of it. */
/* Tools need an OUTLINE at this size, not shading. A pale steel shape on a pale wall with a
   quarter-unit stroke on it disappears; the outline is what holds the silhouette together. */
.eng-tool-steel { fill: #ccd5e1; stroke: #5d6878; stroke-width: 0.42; }

.eng-tool-lit   { fill: none; stroke: rgba(255, 255, 255, 0.5); stroke-width: 0.3; stroke-linecap: round; }

.eng-board-paper { fill: #f4f6fa; }

.eng-board-rule  { fill: none; stroke: #9aa6b6; stroke-width: 0.22; stroke-linecap: round; }

.eng-tool-hole  { fill: #46505f; }


/* THE SAME FAN SEEN FACE ON, in the wall behind the room. A bathroom fan is a white plastic
   fascia with a dark throat behind it, and the throat has to be genuinely darker than the wall
   or the whole thing reads as a square tile stuck on the plaster. */
.fan-fascia-face  { fill: #f6f8fc; stroke: #8a94a5; stroke-width: 0.8; }

.fan-fascia-inner { fill: #e6ebf3; stroke: #aab4c3; stroke-width: 0.5; }

.fan-throat       { fill: #26303e; stroke: #4d5868; stroke-width: 0.7; }

.fan-screw        { fill: #b7c0cd; stroke: #8a94a5; stroke-width: 0.3; }


/* Air leaving through a wall you are looking AT goes away from you, so there is no plume to draw
   across the picture - what you see is the disturbance around the mouth of it. Rings opening out
   and fading is the one honest way to draw a direction that is perpendicular to the page, and it
   is the same convention every ventilation drawing has always used for it. */
.back-plume-ring {
  fill: none;
  stroke: #9fa987;
  stroke-width: 1.6;
  opacity: 0;
}

.is-running .back-plume-ring { animation: backPlume 3.1s ease-out infinite; }

.is-running .back-plume-ring:nth-of-type(2) { animation-delay: -1.03s; }

.is-running .back-plume-ring:nth-of-type(3) { animation-delay: -2.06s; }

.back-plume-ring { transform-box: fill-box; transform-origin: 50% 50%; }

@media (prefers-reduced-motion: reduce) {
.is-running .back-plume-ring { animation: none; opacity: 0.5; transform: scale(1.6); }
}


/* The window in that same back wall reuses #window-frame, so it needs no styles of its own -
   which is the point. A bespoke set was written here first and could not be made to read through
   the stale-air haze that correctly sits above windowLayer. */

/* What he says at the end, in a bubble beside him. It does not say "healthy": that is a claim
   about a person's health, and every health statement on this site is a direct quotation from a
   named source precisely so the business never makes one in its own voice. */
.speech-body {
  fill: #ffffff;
  stroke: #d6d9e0;
  stroke-width: 0.8;
  filter: drop-shadow(0 3px 10px rgba(11, 18, 32, 0.3));
}

.speech-head {
  font-family: 'Space Grotesk', 'Inter', system-ui, sans-serif;
  font-size: 7.4px;
  font-weight: 700;
  fill: #1A3B8B;
  letter-spacing: -0.01em;
}

.speech-sub {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 5.6px;
  font-weight: 500;
  fill: #4A5568;
}


/* The drill. Three shapes and it is unmistakable, which is the whole point of putting one in his
   hand rather than relying on the pose. */
.eng-tool { fill: #2f3742; }

.eng-tool-chuck { fill: #9aa5b4; }

.eng-tool-batt { fill: #D14124; }


/* The background ventilator, with the air going through it. Every stream in this drawing has to
   start or finish outside, and the ones that start at a trickle vent used to begin in mid-room -
   which quietly left out the one thing extract-only systems depend on.

   Static, deliberately. This is the one place that has to say a DIRECTION and nothing else, and
   there is already plenty moving. */
.vent-slot { fill: #33404f; stroke: #7c8899; stroke-width: 0.5; }


/* --- The van -------------------------------------------------------------------------------
   White body, navy wrap, flame hairline: the brand at a distance, in two colours, which is all
   livery ever gets. Everything here is flat - a gradient and a gloss on a vehicle in a drawing
   this size reads as a toy car, not as a van. */
.van-shell { fill: #f2f5f8; stroke: #b9c2cc; stroke-width: 0.7; stroke-linejoin: round; }

.van-wrap { fill: #1A3B8B; }

.van-flame { fill: none; stroke: #D14124; stroke-width: 1.5; }

.van-shut { stroke: #b3bcc7; stroke-width: 0.7; stroke-linecap: round; }

.van-glass { fill: #97b5cd; stroke: #6f8ba3; stroke-width: 0.6; stroke-linejoin: round; }

.van-glass-lit { fill: #cfe0ee; opacity: 0.55; }

.van-trim { fill: #414c5c; }

.van-trim-line { stroke: #414c5c; stroke-width: 1.6; stroke-linecap: round; }

.van-lamp { fill: #dfe7ee; stroke: #9aa7b5; stroke-width: 0.5; }

.van-lamp-rear { fill: #c0342a; stroke: #8d251d; stroke-width: 0.5; }

/* The other two lenses in the cluster. Three colours is what stops it reading as a red stripe. */
.van-lamp-ind  { fill: #E8A33C; stroke: #B87A1E; stroke-width: 0.5; }

.van-lamp-rev  { fill: #eef2f7; stroke: #9aa5b3; stroke-width: 0.5; }

/* An arch is an OPENING with a wheel in it, not an arc drawn on the panel. The dark inner face
   and the lip either side are what make that read. */
.van-arch-in { fill: #1b2029; }

.van-arch { fill: none; stroke: #59626f; stroke-width: 1.5; }

/* The line every van has and almost nothing else does. */
.van-crease { fill: none; stroke: #c3cbd4; stroke-width: 1.1; opacity: 0.9; }

.van-grille { fill: #2f3742; }

.van-bumper { fill: #4a535f; }

.van-sheen-line { stroke: #ffffff; stroke-width: 1.2; opacity: 0.5; stroke-linecap: round; }

.van-spin { transform-box: view-box; }

.van-rack { fill: #464e59; stroke: #2f353d; stroke-width: 0.4; }

/* Trade yellow, and not restyled toward the brand palette - a ladder is yellow because it has to
   be seen, and that is the whole reason it reads as one at this size. */
.van-ladder { fill: #F0B429; stroke: #A9781A; stroke-width: 0.4; }

.van-ladder-rung { stroke: #C6931F; stroke-width: 1.2; stroke-linecap: round; }

.van-sheen { stroke: #ffffff; stroke-width: 1.4; opacity: 0.75; stroke-linecap: round; }


/* Livery text. The one thing in this drawing that has to be READ rather than looked at, so it
   gets the brand face at a real size rather than being squeezed to fit. */
/* The two contact lines sit ON the navy wrap now, between the wheel arches, so they are white.
   Left navy they were navy on navy and simply did not exist. */
.van-line {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 8px;
  font-weight: 700;
  fill: #ffffff;
  letter-spacing: -0.01em;
}

/* The handle is the longest thing on the van and it sets its own size: at the line above's size
   it runs into the rear wheel arch, and type that crosses an arch is type nobody signwrote. */
.van-line--sm { font-size: 6.5px; letter-spacing: -0.015em; }

.van-url {
  font-family: 'Space Grotesk', 'Inter', system-ui, sans-serif;
  font-size: 9.2px;
  font-weight: 700;
  fill: #1A3B8B;
  letter-spacing: 0.005em;
}


/* Room labels sit on lit interior surfaces, so they keep the dark ink colour */
.room-label { paint-order: stroke; stroke: #ffffff; stroke-width: 2.5px; stroke-linejoin: round; }

/* A name for a room too narrow to take one at full size. A landing is 48 units across and
   "Landing" is not, so it ran out of both ends and into the bedrooms either side. */
.room-label--tight { font-size: 6.4px; letter-spacing: 0.01em; stroke-width: 2px; }


/* --- Cold-surface toggle --- */
.sim-toggles {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
}

.sim-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  align-self: flex-start;
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-white);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-ink-muted);
  cursor: pointer;
  transition: all var(--dur-fast) var(--ease-out);
}

.sim-toggle:hover { border-color: var(--color-blue); color: var(--color-navy-dark); }

.sim-toggle-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--color-border);
  transition: background var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}

.sim-toggle-hint { font-size: var(--text-xs); color: var(--color-ink-muted); }



/* ===================== PHASE 7: PLANT, TERMINALS AND AIR ===================== */
/* Ductwork reads as three weights: trunk to outside, branch runs, then terminals.

   Four air streams, not two. Diagrams that colour ducts by "supply" and "extract" alone cannot
   tell the story heat recovery exists to tell, because the interesting thing is that each stream
   CHANGES as it crosses the exchanger. Hue carries fresh (blue) against stale (orange); lightness
   carries temperature. So cold fresh air arrives dark and cold, leaves the core bright and warm;
   warm stale air arrives bright, and leaves for the outside world dulled and cooled. */
.duct-line { fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 4.2; }
   /* unit -> outside: cooled stale air */
.duct-line.trunk   { stroke-width: 6.8; }


/* Insulation on the two runs between the unit and outside. Those are the only ducts that have to
   be lagged - they carry air at outdoor temperature through warm indoor air, so bare they sweat
   onto whatever is underneath them. It is the detail nearly every published drawing omits. */
.duct-lag {
  fill: none;
  stroke: #d8dfe8;
  stroke-width: 10.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.95;
}

.duct-lag-wrap {
  fill: none;
  stroke: #a9b4c4;
  stroke-width: 10.4;
  stroke-linecap: butt;
  stroke-dasharray: 0.9 4.6;
  opacity: 0.55;
}


/* Plant: white bodies with a navy case line, so the units read as real equipment on the dark stage */
.vent-unit-body {
  fill: url(#matCasing);
  stroke: #7f8b9d;
  stroke-width: 1;
  filter: url(#lift);
}

/* Inside the heat recovery unit. Each stream is filtered on the way IN and fanned on the way
   OUT, and the two cross at the core - so the supply fan is genuinely on the supply side and the
   extract fan on the exhaust side, which is how the machine is built and why the supply fan is
   the one commissioned to the room rates. */
.unit-stream { fill: none; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; opacity: 0.5; }


/* A loft PIV has no duct to outside - it breathes the roof space, which is ventilated at the
   eaves. Drawn as loose air rather than as pipework, because that is what it is. */
.loft-air-band {
  fill: none; stroke: #7fb6d8; stroke-width: 6;
  stroke-linecap: round; opacity: 0.16; filter: blur(1.3px);
}

.loft-air-trace {
  fill: none; stroke: #cfe7f7; stroke-width: 1.3;
  stroke-linecap: round; stroke-dasharray: 8 30;
}

/* Nothing moves before the button is pressed, same rule as the duct highlights. Every dash cycle
   divides the 152 the ductFlow keyframe travels, so it loops seamlessly. */
.is-running .loft-air { opacity: 0.95; }

.is-running .loft-air-trace { animation: ductFlow 4.6s linear infinite; }


.unit-filter { fill: #dbe2ec; stroke: #8a95a7; stroke-width: 0.7; }

.unit-filter-slot { stroke: #9aa8bf; stroke-width: 0.8; }

.unit-spigot { fill: url(#matSteel); stroke: #6f7b8d; stroke-width: 0.7; }

.unit-spigot-main { fill: #b9c4d6; }

.vent-unit-core { stroke: var(--color-blue); stroke-width: 1.5; fill: none; }

.unit-fan { stroke: var(--color-blue); stroke-width: 1.6; fill: none; stroke-linecap: round; }


/* Terminals, drawn as the three components that are really there: a plenum box screwed to a
   joist in the ceiling void, its collar through the plasterboard, and the valve on the face of
   it. A supply valve and an extract valve are usually the same product - what differs is the
   duct behind them - so they are drawn alike and the arrows carry the distinction. */
.plenum-body {
  fill: url(#matSteel);
  stroke: #6c7788;
  stroke-width: 0.8;
  filter: url(#liftSoft);
}

.plenum-lug { fill: #98a3b4; }

.plenum-collar { fill: #b6c0ce; stroke: #798496; stroke-width: 0.6; }

.valve-flange { fill: #f4f7fb; stroke: #8a94a5; stroke-width: 0.7; }

.valve-stem { stroke: #8a94a5; stroke-width: 0.9; }

.diffuser-cap { fill: #eef2f7; stroke: #8a94a5; stroke-width: 0.7; }


/* The manifold the room ducts fan out of - one for supply, one for extract. */
.manifold-face { fill: #d3dae6; stroke: #9aa5b6; stroke-width: 0.5; }

.spigot-band { fill: #77839a; }

.manifold-body {
  fill: url(#matCasing);
  stroke: #7f8b9d;
  stroke-width: 0.9;
  filter: url(#liftSoft);
}

.manifold-supply .manifold-body { stroke: #59a7d4; }


/* The one plumbing connection on an MVHR unit, and it has to run into a trap. */
.condensate-pipe {
  fill: none;
  stroke: #9aa5b6;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The cords a PIV unit is hung from a rafter on. */
.unit-hanger { stroke: #8f9aab; stroke-width: 0.9; stroke-linecap: round; }

.grille-arrow-supply {
  stroke: #56b4f5; stroke-width: 1.5; fill: none; stroke-linecap: round;
}

.grille-arrow-extract {
  stroke: #e8763f; stroke-width: 1.5; fill: none; stroke-linecap: round;
}

.perimeter-arrow-line {
  stroke: #7fc4ff; stroke-width: 2; fill: none; stroke-linecap: round;
  filter: drop-shadow(0 0 3px rgba(127, 196, 255, 0.55));
}

@media (max-width: 768px) {
/* The house diagram scales with its container, so at phone width every label inside it shrinks
     to around 7px. Bumping the SVG-internal type keeps the drawing readable rather than decorative. */
  .room-label { font-size: 13px; }
.house-svg { border-radius: var(--radius-sm); }
/* Give the diagram the full width of the screen — it is the centrepiece and needs the pixels.
     The negative margin lives on the SVG rather than on .mould-diagram-wrap, because at <=900px
     the wrapper is `display: contents` and has no box to hang a margin on. */
  .diagram-hint { padding-inline: 0; }
/* Two ventilation system cards per row rather than one, so step 2 is not three screens tall. */
  .picker-cards-sys { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.picker-cards-sys .sys-tag { font-size: 0.7rem; }
}

.sim-toggle { min-height: 44px; }



/* ===================== PHASE 11: SIMULATOR AIRFLOW ===================== */
/* A travelling highlight over each duct. The duct itself stays a solid pipe; this rides on top of
   it as a bright dash, so the run reads as air moving through it rather than as a coloured line
   with a dot on it. Supply is cool and bright, extract is warm and murkier — fresh air in, stale
   air out, readable at a glance. */
/* Three layers on the same path at three speeds — see addDuct(). The separation as they travel
   is what makes it read as air with volume rather than a dashed line. */
.duct-flow { fill: none; stroke-linecap: round; pointer-events: none; }


/* No glow on any of it. A drop-shadow on a moving dash is the one thing that reads as lighting
   rather than as air, and with the highlight now carrying the duct's own colour it does not need
   one to be seen. */

/* The arrows say which way, so they only have to be legible - not bright. */
.flow-particle { opacity: 0; }

.is-running .flow-particle { opacity: 0.55; }


/* Terminal arrows pulse gently once running, so the eye is drawn to where air enters and leaves.
   Gated behind .is-running: an unconditional opacity animation overrides the opacity="0" the
   arrows are built with, which put them on screen before anything had been simulated. */
.is-running .grille-arrows { animation: terminalPulse 2.4s var(--ease-in-out) infinite; }


/* Stale air needs to be seen before it can be seen to go. */
.stale-air { pointer-events: none; mix-blend-mode: multiply; }

@media (prefers-reduced-motion: reduce) {
.duct-flow, .grille-arrows { animation: none; }
}



/* ===================== PHASE 16: DAMP QUESTIONNAIRE ===================== */
/* Replaces the humidity slider. Answerable questions, big tap targets, and the derived figure
   labelled as an estimate rather than a measurement. */
.damp-quiz {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin: 0 0 var(--space-4);
  background: var(--color-white);
}

.damp-quiz-legend {
  padding: 0 var(--space-2);
  margin-left: calc(var(--space-2) * -1);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--text-base);
  color: var(--color-navy-dark);
}

.damp-q + .damp-q { margin-top: var(--space-4); }

.damp-q-label {
  display: block;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-ink);
  margin-bottom: var(--space-2);
}

.damp-opts { display: flex; flex-wrap: wrap; gap: var(--space-2); }

.damp-opt {
  flex: 1 1 auto;
  min-width: 84px;
  min-height: 44px;
  padding: 0.5rem 0.8rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-white);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-ink-muted);
  cursor: pointer;
  transition: all var(--dur-fast) var(--ease-out);
}

.damp-opt:hover { border-color: var(--color-blue); color: var(--color-navy-dark); }

.hum-derived {
  margin: calc(var(--space-2) * -1) 0 var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-ink-muted);
  line-height: 1.55;
}

.hum-exact { margin: 0 0 var(--space-3); }

.hum-exact summary {
  cursor: pointer;
  font-size: var(--text-xs);
  color: var(--color-blue);
  font-weight: 600;
  padding: var(--space-2) 0;
  min-height: 32px;
}

.hum-exact #humiditySlider { width: 100%; margin-top: var(--space-2); accent-color: var(--color-blue); }

/* Boxing. A real riser is cased in plasterboard, taped and skimmed, and the only thing a customer
   ever sees of it is a square column in the corner. Drawn solid with a lit arris rather than as a
   dashed outline: a dashed rectangle reads as an annotation, and the point of this one is that it
   is a physical thing in the room. */
.riser-body {
  fill: #cfd6e2;
  stroke: #a8b2c2;
  stroke-width: 0.9;
}

.riser-bead {
  stroke: #eef1f6;
  stroke-width: 1.6;
  opacity: 0.95;
  stroke-linecap: round;
}

@media (max-width: 900px) {
/* The loft answer and the hygrometer disclosure are real controls in the middle of the
     simulator's flow, not incidental links — they get the full target. */
  .loft-toggle-btn { min-height: 44px; padding-inline: var(--space-4); }
.hum-exact summary { min-height: 44px; display: flex; align-items: center; }
}


/* The cold-surface overlay. Transitioned in CSS so the toggle works whether or not the animation
   library ever loads — this control decides whether something is visible, and those must never
   depend on a script from a CDN. */
#thermalLayer { transition: opacity 0.35s var(--ease-out); }

@media (prefers-reduced-motion: reduce) {
#thermalLayer { transition: none; }
}




/* --- Simulator: inside the plant --------------------------------------------------------
   The units are drawn with the front panel off. A box with a cross in it says "some equipment";
   a core, two fans and two filters says what the customer is paying for. */
.unit-hilite { fill: #ffffff; opacity: 0.5; }

.unit-cavity { fill: #1e3050; }

.unit-core-body { fill: #dbe3ee; }

.unit-core-plate { stroke: #8794a8; stroke-width: 0.5; }

.unit-fan-body { fill: #38496a; stroke: #b6c3d6; stroke-width: 0.8; }

.unit-fan-hub { fill: #b0bccd; }

.unit-led { fill: #6ee7a8; }


/* --- Simulator: building fabric --------------------------------------------------------- */
.fabric-face { stroke: #8f99a9; stroke-width: 0.9; }

.fabric-skirting { fill: #eef1f6; opacity: 0.9; }

.fabric-skirting-top { fill: #ffffff; opacity: 0.7; }


/* The floor going back into the room. One trapezoid per room, and suddenly the drawing has a
   third dimension in it everywhere at once. */
/* A house is not one material. Boards in the hall, carpet in the bedrooms and the living room,
   tiles in the kitchen and the bathroom - the eye reads a room by its floor before it reads
   anything else in it, and every room here used to have the same one. */
.fabric-floor { opacity: 0.94; }

.fabric-board { stroke: #6d6050; stroke-width: 0.6; opacity: 0.5; }

.fabric-tile-joint { stroke: #74808d; stroke-width: 0.55; opacity: 0.6; }

.fabric-nap { stroke: #6b6862; stroke-width: 0.8; opacity: 0.34; stroke-linecap: round; }

.fabric-floor-shadow { stroke: #0B1220; stroke-width: 1.6; opacity: 0.14; fill: none; }


/* Radiator under the window: instantly recognisable, and the reason the glass above it is the
   coldest surface in the room rather than the warmest. */
.rad-body { fill: #eaeef4; stroke: #b3bcc9; stroke-width: 0.6; }

.rad-top { fill: #ffffff; opacity: 0.8; }

.rad-fin { stroke: #c3cbd6; stroke-width: 1.1; }

.rad-leg { fill: #9aa4b3; }

.door-handle { fill: #8d99ab; }

.timber-rafter { fill: #cbaf83; }

.timber-collar { fill: #b89b6f; }

.timber-plate { fill: #b99a6b; }

/* The sawn end of a joist. In a section you are looking at the cut face of every one of them, and
   a flat rectangle is the one thing that never reads as timber. */
.timber-endgrain { fill: #dcc190; opacity: 0.75; }

/* Grain. Fine, low-contrast and ALONG the member - see the note in buildFabric. Heavy enough to
   see at full size and light enough to disappear at thumbnail, which is what grain does. */
.timber-grain {
  fill: none;
  stroke: #8d7148;
  stroke-width: 0.5;
  opacity: 0.42;
  stroke-linecap: round;
}


/* Loft insulation. 270mm of glass wool between and over the joists - the reason the loft is cold,
   and therefore the reason every duct crossing it is lagged. It is not scenery. */
.loft-insul { fill: #d8c69c; }

.loft-insul-lip { stroke: #f6efdc; stroke-width: 1.3; opacity: 0.85; }

.loft-hatch { fill: #b9a781; stroke: #8d7c5c; stroke-width: 0.7; }

/* The opening itself. You are looking down a trimmed hole into the landing, so it is the darkest
   thing in the loft floor. */
.hatch-hole { fill: #16202f; }

.hatch-lining { fill: #d8cdb4; stroke: #ab9d7f; stroke-width: 0.4; }

.loft-ladder line { stroke: #c2ab7c; stroke-width: 1.1; stroke-linecap: round; opacity: 0.92; }

/* Rafters going away from you. Same timber, further off, hazing toward the dark of the roof
   space - which is all aerial perspective is. */
.timber-back { fill: #7d6a4e; }


/* The contact shadow under every floor-standing piece. Nothing in the drawing cast anything, and
   furniture with no shadow under it is a sticker on a wall - this is most of the difference
   between "placed in a room" and "in a room". */
.furn-shadow { fill: #0B1220; opacity: 0.3; filter: blur(2.2px); }

/* Sarking felt, sagging between the rafters. */
.roof-felt { stroke: #3f3730; stroke-width: 1.5; opacity: 0.72; stroke-linejoin: round; }

.timber-binder { fill: #b89b6f; }

.fabric-ground-line { stroke: #6c7789; stroke-width: 1.2; }

.fabric-cavity { fill: #6f7889; opacity: 0.55; }

.fabric-cut-edge { stroke: #7d8798; stroke-width: 1.1; }


/* The stair is beyond the plane of the cut - you are seeing it through the hall doorway - so it
   is drawn back rather than forward. Push the contrast up and it starts to look like a staircase
   sitting in the middle of somebody's bedroom. */
/* A staircase you can see. At 38% of a pale grey it was invisible against the hall wall, which
   rather defeated the point of giving every property a hall to put it in. Treads read as timber
   with a painted string, which is what most of this housing stock has. */
.stairs-flight { fill: #cdb894; opacity: 0.95; stroke: #9c8560; stroke-width: 0.7; }

.stairs-nosing { stroke: #8a7350; stroke-width: 0.9; opacity: 0.85; }

.stairs-rail { stroke: #9c8560; stroke-width: 1.8; opacity: 0.95; stroke-linecap: round; }


/* Loft insulation between the joists. It is also the surface the two outdoor duct runs cross,
   which is the whole reason those two have to be lagged. */
.loft-batt { fill: #c9c2ae; opacity: 0.5; }

.loft-line { stroke: #a9a390; stroke-width: 1; opacity: 0.5; }




/* WHAT THEY GET, AT THE MOMENT THEY DECIDE - the last block above the survey button in the
   simulator result. Four real commitments, each linking to where it is set out in full.
   A tick down the left of each row rather than a box round the lot: this sits at the end of a
   long panel that already has two flagged notes in it, and a third bordered box would read as
   another warning rather than as the reassurance it is. */
.sim-assure {
  list-style: none;
  margin: var(--space-4) 0;
  padding: var(--space-4);
  display: grid;
  gap: var(--space-3);
  /* NAVY, NOT FLAME, and that is the whole point of the treatment. `.sim-result-flag` directly
     above it is already flame with an orange left rule - it is the panel's "worth knowing" voice -
     so a flame strip under it read as a third warning rather than as the reassurance it is. Navy
     is the trust colour, it separates the two at a glance, and it leaves the orange doing only
     what orange does on this site: calls to action. */
  background: var(--color-surface-tint-navy);
  border-radius: var(--radius-md);
  box-shadow: inset 3px 0 0 var(--color-navy);
}

.sim-assure li {
  display: grid;
  gap: 0.15rem;
  padding-left: 1.65rem;
  position: relative;
}

/* The tick is drawn, not a character: a text tick sits on the baseline and cannot be aligned to
   the cap height of the line beside it without a magic number that breaks at another font size. */
.sim-assure li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.32em;
  width: 0.95rem;
  height: 0.95rem;
  background: no-repeat center/contain
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231A3B8B' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E");
}

.sim-assure strong {
  font-family: var(--font-heading);
  font-size: var(--text-sm);
  color: var(--color-ink);
}

.sim-assure span {
  font-size: 0.86rem;
  line-height: 1.55;
  color: var(--color-ink-muted);
}

.sim-assure a { color: var(--color-navy); font-weight: 600; }

.air-ribbon {
  stroke: none;
  opacity: 0.3;
  filter: blur(0.55px);
}

.air-trace {
  fill: none;
  stroke-width: 1.15;
  stroke-linecap: round;
  stroke-dasharray: 6 21;
  animation: airTrace 2.6s linear infinite;
}


/* Colour comes from a gradient laid along each path in userSpaceOnUse, not from a class - cool
   where the air arrives, warm where it leaves, so you can watch it pick the room up on the way
   through. The classes only carry weight and opacity. */
.air-ribbon { opacity: 0.26; }

.air-trace { opacity: 0.78; }

@media (prefers-reduced-motion: reduce) {
.air-trace { animation: none; }
}


/* ===================== THE ENGINEER, IN THREE DIMENSIONS =====================
   The far arm and the far leg were drawn in exactly the same colours as the near ones, so the
   figure was a flat cut-out however carefully its proportions were worked out. Putting the far
   limbs into shade is the single thing that makes a small figure read as a body in space rather
   than as a pictogram — it is depth, not detail, and detail does not survive at this size. */
.eng-far .eng-trews { fill: #A8420F; stroke: #7d2f08; }

.eng-far .eng-boot { fill: #14171c; }

.eng-far .eng-sole { fill: #2a2f36; }

.eng-far .eng-pad { fill: #21262d; }

.eng-far .eng-sleeve-line { stroke: #cdd5de; }

.eng-far .eng-sleeve-edge { stroke: #a9b3be; }

.eng-far .eng-skin-line { stroke: #a87f5f; }

.eng-far .eng-skin { fill: #a87f5f; }

.eng-far .eng-trews-lit { display: none; }


/* The sole, and one lit edge down the near leg. The scene lights from the upper left, and a limb
   with no light on it at all is a shape rather than a leg. */
.eng-sole { fill: #3a4048; }

.eng-trews-lit {
  stroke: #F08A4B;
  stroke-width: 0.55;
  stroke-linecap: round;
  opacity: 0.7;
}



/* The way out of "no wall fan can be fitted here". It sits inside the note that delivers the bad
   news, because the moment a visitor is told their plan rules a system out is the moment to hand
   them the one that does not. */
.sim-swap-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding: 0.6rem 1.05rem;
  font: inherit;
  font-weight: 600;
  color: #fff;
  background: var(--color-navy);
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.18s var(--ease-out), transform 0.18s var(--ease-out);
}

.sim-swap-btn::after { content: '\2192'; font-size: 1.05em; line-height: 1; }

.sim-swap-btn:hover { background: var(--color-navy-dark); }

.sim-swap-btn:active { transform: translateY(1px); }

.sim-swap-btn:focus-visible { outline: 3px solid var(--color-orange); outline-offset: 2px; }



/* A room the chosen system does not ventilate. Wall fans are intermittent and there is no supply
   anywhere on that system, so the only air crossing a bedroom is what a fan two rooms away is
   pulling through a trickle vent - and the honest picture of that is "almost nothing", not the
   same confident ribbon the bathroom gets. It is the customer's real question ("will this fix the
   bedroom?") answered in the drawing rather than only in the copy. */
.airflow--weak .air-ribbon { opacity: 0.22; animation-duration: 9.5s; }

.airflow--weak .air-trace  { opacity: 0.3; animation-duration: 9.5s; }



/* --- Little signs of life -------------------------------------------------------------------
   A figure that holds one exact pose reads as a cut-out however well it is proportioned, and the
   cheapest fix by a distance is breathing plus a slow shift of weight. Both go on .eng-torso,
   which is the ONE group in the figure that engApply never writes a transform to - put them on a
   joint and the CSS transform replaces the joint angle and the limb flies off, which is the trap
   already recorded against .unit-rotor.

   The sway is roughly twice the period of the breath, so the two never land together and it never
   settles into a visible loop. Amplitudes are deliberately tiny: at the size he is displayed,
   anything you can actually SEE happening is too much. */
/* The breathing goes on .eng-breathe, which is INSIDE .eng-torso. engApply writes the pose lean
   to the torso; a CSS animation beats an attribute, so sharing one element would throw the lean
   away every time he stood still - which is exactly when it reads. */
.eng-breathe { transform-box: fill-box; transform-origin: 50% 100%; }

.eng-still .eng-breathe { animation: engIdle 6.4s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
.eng-still .eng-breathe { animation: none; }
}


/* --- The gate ------------------------------------------------------------------------------
   It is drawn face on across the opening, so its hinge swings TOWARD the viewer - which
   foreshortens it rather than rotating it. Squashing it about the hinge stile is the true
   depiction; rotating it in the plane of the page would lay it flat along the wall, which is not
   a thing gates do. */
.front-gate {
  transform-box: fill-box;
  transform-origin: 0% 50%;
  transition: transform 0.85s cubic-bezier(0.34, 0.9, 0.4, 1);
}

@media (prefers-reduced-motion: reduce) {
.front-gate { transition: none; }
}



/* NOBODY IS ASLEEP WHILE HE IS WORKING. The two under the duvet are the fifth moisture source and
   they belong to the "before" - it is first light, the house has been breathing all night, and
   that is the whole reason they are drawn. The van pulls in, the light comes up, and it is a
   working day: the duvet drops flat and the bed is just a bed. setDaylight puts .is-day on the
   svg past the half-way point of the sunrise, so the two swap while the light is still changing
   and neither reads as popping. */
   AND THE RULE HAS TO GET INTO A <use> SHADOW TREE. The bed is a <symbol> placed with <use>, so
   the sleepers are shadow-cloned content: a document selector like `.is-day .bed-sleeper` does
   NOT match them, and `opacity` is not an inherited property either, so it cannot reach them that
   way. It never applied once - the two stayed in bed all day, which is exactly what was reported.
   CUSTOM PROPERTIES DO INHERIT INTO THE SHADOW TREE, so the switch is a variable set out here and
   read by a style attribute on the group inside the symbol. Same trap as the presentation
   attribute and the stale duplicate: a rule that looks right and cannot reach its target. */
.house-svg { --sleepO: 1; --madeO: 0; }

.house-svg.is-day { --sleepO: 0; --madeO: 1; }

@media (prefers-reduced-motion: reduce) {
.bed-sleeper, .bed-made { transition: none !important; }
}


/* The wall-mounted PIV in a flat: louvre slots under the casing, its fused spur, and the power
   light - the one detail that says "this is switched on and running". */
.piv-wall-slot { stroke: #7c879a; stroke-width: 0.7; stroke-linecap: round; }

.piv-wall-spur { fill: #eef1f5; stroke: #8e98a8; stroke-width: 0.5; }

.piv-wall-led { fill: #3ecf6e; }
