* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font-body);
  background: var(--canvas);
  color: var(--ink);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 { font-family: var(--font-head); font-weight: 600; margin: 0; color: var(--navy); }
a { color: var(--blue); }

/* ---------- shell ---------- */
.shell { display: flex; min-height: 100vh; }

.sidebar {
  width: 248px; flex: 0 0 248px;
  background: var(--navy);
  color: #fff;
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100vh;
}
.sidebar__brand {
  padding: 20px 20px 16px;
  border-bottom: 1px solid rgba(255,255,255,.10);
  display: flex; align-items: center; gap: 10px;
}
/* The mark is drawn in near-black with blue and amber, and the rail behind it
   is navy — on screen it read as a smudge beside the wordmark. It sits on a
   light chip, the way a dark logo does on any dark ground, rather than being
   recoloured into something the brand does not own. */
.sidebar__brand img {
  height: 42px; width: auto; display: block;
  box-sizing: content-box; padding: 6px;
  background: #fff; border-radius: 11px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .28);
}
.sidebar__brandtext { font-family: var(--font-head); font-size: 19px; letter-spacing: .2px; }
.sidebar__org {
  padding: 14px 20px; font-size: 12px;
  color: rgba(255,255,255,.55); text-transform: uppercase; letter-spacing: .09em;
}
.sidebar__org strong { display: block; font-size: 14px; color: #fff; text-transform: none; letter-spacing: 0; margin-top: 3px; }
.nav { padding: 6px 12px; flex: 1; overflow-y: auto; }
.nav__item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; margin-bottom: 2px;
  border-radius: var(--r); cursor: pointer;
  color: rgba(255,255,255,.78); font-size: 14px;
  border: none; background: none; width: 100%; text-align: left;
  font-family: var(--font-body);
}
/* The nav items are <a href> now, on both tools — review item 3.8. A link
   brings an underline and the visited colour with it, and neither belongs on
   a sidebar item. */
.nav__item { text-decoration: none; }
.nav__item:hover { background: rgba(255,255,255,.07); color: #fff; }
.nav__item.is-active { background: var(--blue); color: #fff; }
.nav__item .dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 7px; }
.nav__sub { padding-left: 14px; }
.nav__label {
  padding: 14px 12px 6px; font-size: 11px; letter-spacing: .09em;
  text-transform: uppercase; color: rgba(255,255,255,.42);
}
.nav__count { margin-left: auto; font-size: 12px; color: rgba(255,255,255,.5); }
.nav__item.is-active .nav__count { color: rgba(255,255,255,.85); }

.sidebar__foot {
  padding: 12px 20px; font-size: 11.5px; color: rgba(255,255,255,.4);
  border-top: 1px solid rgba(255,255,255,.10);
}

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.topbar {
  background: var(--surface); border-bottom: 1px solid var(--line);
  padding: 14px 28px; display: flex; align-items: center; gap: 16px;
  position: sticky; top: 0; z-index: 20;
}
.crumbs { font-size: 13px; color: var(--ink-mid); display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.crumbs button {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--blue); font-size: 13px; font-family: var(--font-body);
}
.crumbs button:hover { text-decoration: underline; }
.crumbs span.sep { color: var(--ink-soft); }
.topbar__right { margin-left: auto; display: flex; align-items: center; gap: 14px; }
.asof {
  font-size: 12.5px; color: var(--ink-soft);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

.live {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; color: var(--ink-mid);
}
.live__pulse {
  width: 8px; height: 8px; border-radius: 50%; background: var(--teal);
  box-shadow: 0 0 0 0 rgba(47,168,148,.6); animation: pulse 2.4s infinite;
}
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgba(47,168,148,.55); }
  70%  { box-shadow: 0 0 0 7px rgba(47,168,148,0); }
  100% { box-shadow: 0 0 0 0 rgba(47,168,148,0); }
}

.content { padding: 24px 28px 56px; max-width: 1440px; width: 100%; }

/* ---------- page head ---------- */
.pagehead { display: flex; align-items: flex-start; gap: 20px; margin-bottom: 20px; }
.pagehead h1 { font-size: 26px; line-height: 1.25; }
.pagehead p { margin: 5px 0 0; color: var(--ink-mid); font-size: 14px; }
.pagehead__right { margin-left: auto; display: flex; gap: 10px; align-items: center; }

/* ---------- cards ----------
   Border, not shadow. Carrying both was belt-and-braces: the shadow lifted
   every card off the canvas equally, which is a lot of visual effort spent
   saying nothing, and it softened edges that read better crisp. --shadow-lg
   is still there for things that genuinely float (toasts, chart readouts). */
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg);
}
.card__head {
  padding: 15px 18px; border-bottom: 1px solid var(--line-soft);
  display: flex; align-items: center; gap: 12px;
}
.card__head h3 { font-size: 15.5px; }
.card__head .sub { font-size: 12.5px; color: var(--ink-mid); margin-left: auto; }
.card__body { padding: 18px; }
/* Tables live in here. A wide one scrolls inside its own card rather than
   pushing the whole page sideways. */
.card__body.tight { padding: 0; overflow-x: auto; }

.grid { display: grid; gap: 16px; }
.grid--kpi  { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.grid--main { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); align-items: start; }
/* Without this a child's min-content width wins over the track and the PAGE
   scrolls sideways rather than the card (review item 3.2). */
.grid--main > * { min-width: 0; }
.grid--units { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.grid--sensors { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
/* A unit's six readings. Six across where there is room, then three, then two
   — always an even split, so no reading is left alone on a row of its own the
   way auto-fit would leave the sixth. */
.grid--readings { grid-template-columns: repeat(6, minmax(0, 1fr)); }
@media (max-width: 1280px) { .grid--readings { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 640px)  { .grid--readings { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 1080px) { .grid--main { grid-template-columns: minmax(0, 1fr); } }

/* ---------- KPI ---------- */
.kpi { padding: 16px 18px; }
.kpi__label { font-size: 11.5px; text-transform: uppercase; letter-spacing: .08em; color: var(--ink-soft); }
.kpi__value { font-family: var(--font-head); font-size: 30px; line-height: 1.15; margin-top: 6px; color: var(--navy); }
.kpi__value small { font-size: 15px; color: var(--ink-mid); font-family: var(--font-body); margin-left: 3px; }
.kpi__foot { font-size: 12.5px; color: var(--ink-mid); margin-top: 5px; display: flex; align-items: center; gap: 6px; }

/* ---------- status pills ---------- */
.pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 9px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .01em;
  white-space: nowrap;
}
.pill .dot { width: 6px; height: 6px; border-radius: 50%; }
.pill--ok      { background: var(--teal-tint);  color: var(--teal-dark); }
.pill--ok .dot { background: var(--teal); }
.pill--warn      { background: var(--amber-tint); color: var(--amber); }
.pill--warn .dot { background: var(--amber); }
.pill--alert      { background: var(--red-tint);   color: var(--red); }
.pill--alert .dot { background: var(--red); }
.pill--info      { background: var(--blue-tint);  color: var(--blue-dark); }
.pill--info .dot { background: var(--blue); }
.pill--muted      { background: #EEF1F6; color: var(--ink-mid); }
.pill--muted .dot { background: var(--ink-soft); }

/* ---------- buttons ---------- */
.btn {
  font-family: var(--font-body); font-size: 13.5px;
  padding: 8px 14px; border-radius: var(--r);
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  cursor: pointer; display: inline-flex; align-items: center; gap: 7px;
}
/* Some buttons are links — the way back from a not-found address, for one.*/
.btn { text-decoration: none; }
.btn:hover { border-color: var(--ink-soft); }
.btn--primary { background: var(--blue); border-color: var(--blue); color: #fff; }
.btn--primary:hover { background: var(--blue-dark); border-color: var(--blue-dark); }
.btn--teal { background: var(--teal); border-color: var(--teal); color: #fff; }
.btn--teal:hover { background: var(--teal-dark); border-color: var(--teal-dark); }
.btn--ghost { background: none; }
.btn--sm { padding: 5px 10px; font-size: 12.5px; }
.btn:disabled { opacity: .5; cursor: not-allowed; }

/* ---------- unit cards ---------- */
.unitcard { padding: 15px 16px; cursor: pointer; transition: border-color .12s, transform .12s; }
.unitcard:hover { border-color: var(--blue); transform: translateY(-1px); }
.unitcard__top { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 3px; }
.unitcard__name { font-family: var(--font-head); font-size: 15.5px; color: var(--navy); }
.unitcard__zone { font-size: 12.5px; color: var(--ink-mid); }
/* A GRID AND NOT A WRAPPING ROW — review item 3.4. Three fixed columns means
   the fourth reading is in the same place on every card in the page's grid,
   which a flex row only manages while the numbers happen to be the same
   width. Six readings, two rows, always. */
.unitcard__metrics {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 11px 14px; margin-top: 12px;
}
.metricmini__label { font-size: 10.5px; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-soft); }
.metricmini__value {
  font-family: var(--font-head); font-size: 16px; color: var(--navy);
  display: flex; align-items: baseline; gap: 3px;
}
.metricmini__value.is-warn  { color: var(--amber); }
.metricmini__value.is-alert { color: var(--red); }
/* The unit, on every number. Set down from the figure so a column of values
   still reads as a column of values. */
.metricmini__value small {
  font-family: var(--font-body); font-size: 11px; color: var(--ink-soft);
}

/* Which reading put this unit out of range, and what it is past. The reading
   itself stays where it is in the grid above, coloured; this says why. */
.unitcard__why { margin-top: 11px; font-size: 12px; color: var(--ink-mid); }
.unitcard__why b { color: var(--navy); }
.unitcard__why.is-warn b  { color: var(--amber); }
.unitcard__why.is-alert b { color: var(--red); }
.unitcard__foot {
  margin-top: 13px; padding-top: 11px; border-top: 1px solid var(--line-soft);
  display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink-mid);
}

/* A unit in trouble gets an edge, so the grid is scannable down the left
   margin without reading a single number. */
.unitcard.is-alert { border-left: 3px solid var(--red); }
.unitcard.is-warn  { border-left: 3px solid var(--amber); }

/* The promoted reading — a 28px block above the others — went with review
   item 3.4, and its rules with it. A reading that moves and resizes depending
   on whether it is the problem cannot be compared across a grid of cards. */
.unitcard__quiet { margin-top: 12px; font-size: 13px; color: var(--ink-soft); }

/* Direction of a breach — above the band or below it. Carried alongside
   colour, never instead of it, so the distinction survives a red-green
   deficiency and a black-and-white printout. */
.mark { font-size: .72em; line-height: 1; }

/* ---------- what changed ----------
   The only part of the page built from two points in time. */
.changes { margin-bottom: 16px; overflow: hidden; }
.changes.is-worse  { border-left: 3px solid var(--red); }
.changes.is-better { border-left: 3px solid var(--teal); }
.changes.is-quiet  { border-left: 3px solid var(--line); }
.changes__head {
  padding: 13px 18px; display: flex; align-items: center; gap: 9px;
  font-size: 14px; color: var(--navy);
}
.changes.is-quiet .changes__head { color: var(--ink-mid); }
.changes.is-quiet .changes__head svg { color: var(--teal); }
.changes__sub { font-size: 12.5px; color: var(--ink-soft); }
.changes__count { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; }
.changes__count.is-worse  { color: var(--red); }
.changes__count.is-better { color: var(--teal-dark); }
.changes__list { border-top: 1px solid var(--line-soft); }
.changerow {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 18px; border: none; background: none; cursor: pointer;
  font-family: var(--font-body); font-size: 13px; color: var(--ink-mid);
  border-bottom: 1px solid var(--line-soft);
}
.changerow:last-child { border-bottom: none; }
.changerow:hover { background: var(--blue-tint); }
.changerow__mark { font-size: 11px; }
.changerow__mark.is-worse  { color: var(--red); }
.changerow__mark.is-better { color: var(--teal); }
.changerow__name { font-family: var(--font-head); font-size: 14px; color: var(--navy); }
.changerow__zone { color: var(--ink-soft); font-size: 12.5px; }
.changerow__move { margin-left: auto; display: flex; align-items: center; gap: 7px; white-space: nowrap; }
.changerow__move b { color: var(--navy); }
.changerow__arrow { color: var(--ink-soft); }
.changerow.is-more { color: var(--ink-soft); cursor: default; font-size: 12.5px; }

/* ---------- status composition ----------
   Numbers first, proportion underneath. */
.statrow { display: flex; gap: 12px; flex-wrap: wrap; }
.stat { flex: 1 1 88px; }
.stat__value {
  font-family: var(--font-head); font-size: 30px; line-height: 1.1; color: var(--navy);
}
.stat.is-zero .stat__value { color: var(--ink-soft); }
.stat.is-loud .stat__value { color: var(--red); }
.stat__label {
  display: flex; align-items: center; gap: 6px; margin-top: 3px;
  font-size: 12px; color: var(--ink-mid);
}
.stat__swatch { width: 8px; height: 8px; border-radius: 2px; flex: 0 0 8px; }
.statbar {
  display: flex; height: 8px; border-radius: 999px; overflow: hidden;
  margin-top: 16px; background: var(--line-soft);
}
.statbar__seg { display: block; min-width: 3px; }

/* ---------- occupant experience ---------- */
.xp__tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.xp__tile { border: 1px solid var(--line); border-radius: var(--r); padding: 13px 14px; }
.xp__tile.is-good { border-color: #BFE3DA; background: var(--teal-tint); }
.xp__tile.is-fair { border-color: #EBD3A0; background: var(--amber-tint); }
.xp__tile.is-poor { border-color: #EEBAB4; background: var(--red-tint); }
.xp__tilelabel {
  font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-mid);
}
.xp__tilevalue {
  font-family: var(--font-head); font-size: 26px; color: var(--navy);
  margin-top: 4px; line-height: 1.15;
}
.xp__tilevalue small { font-size: 13px; color: var(--ink-soft); font-family: var(--font-body); }
.xp__tile.is-good .xp__tilevalue { color: var(--teal-dark); }
.xp__tile.is-fair .xp__tilevalue { color: var(--amber); }
.xp__tile.is-poor .xp__tilevalue { color: var(--red); }
.xp__tilescale { font-size: 11.5px; color: var(--ink-soft); margin-top: 3px; }
.xp__tilen { font-size: 11px; color: var(--ink-soft); margin-top: 5px; }

/* A withheld area is shown, greyed, with the reason. Dropping the row would
   read as "nobody answered", which is a different and untrue statement. */
.xp__withheld td { background: #FAFBFD; color: var(--ink-soft); }
table.tbl td.is-good { color: var(--teal-dark); font-weight: 600; }
table.tbl td.is-fair { color: var(--amber); font-weight: 600; }
table.tbl td.is-poor { color: var(--red); font-weight: 600; }

.xp__verdict {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 14px 16px; border-radius: var(--r); margin-bottom: 16px; font-size: 13.5px;
}
.xp__verdict.is-linked { background: var(--red-tint); color: #8E332B; }
.xp__verdict.is-unlinked { background: var(--teal-tint); color: #1F6E60; }
.xp__verdict svg { flex: 0 0 auto; margin-top: 1px; }
.xp__verdict b { display: block; font-size: 14.5px; }
/* The caveat under a claim about cause. Same weight and same job as the one
   under a recommendation's track record: quiet, and never absent. */
.xp__hedge {
  margin-top: 8px; padding-top: 7px;
  border-top: 1px solid rgba(255,255,255,.35);
  font-size: 12px; font-style: italic; line-height: 1.5; opacity: .85;
}

.xp__joinfoot { margin-top: 12px; font-size: 12px; color: var(--ink-soft); }

/* Heating / shoulder / cooling side by side. Three is the right number to
   compare at a glance, which is why this is columns and not a chart. */
.xp__seasons { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; }
.xp__season {
  border: 1px solid var(--line); border-radius: var(--r); padding: 14px 15px;
}
.xp__season.is-worst { border-color: #EEBAB4; background: var(--red-tint); }
.xp__season.is-empty { background: #FAFBFD; color: var(--ink-soft); }
.xp__seasonname {
  font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink-mid);
}
.xp__season.is-worst .xp__seasonname { color: var(--red); font-weight: 600; }
.xp__seasonvalue {
  font-family: var(--font-head); font-size: 27px; color: var(--navy);
  margin-top: 4px; line-height: 1.15;
}
.xp__season.is-worst .xp__seasonvalue { color: var(--red); }
.xp__seasonnote { font-size: 11.5px; color: var(--ink-soft); margin-top: 3px; }
.xp__seasonout { font-size: 11.5px; color: var(--ink-mid); margin-top: 8px; }
.xp__seasonvalue small { font-size: 13px; color: var(--ink-soft); font-family: var(--font-body); }

/* What choosing a building opens up. Dashed and unfilled, so it reads as a
   pointer to something rather than as a card with content of its own. */
.xp__hint {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 13px 16px; border: 1px dashed var(--line); border-radius: var(--r-lg);
  background: var(--surface); font-size: 13.5px; color: var(--ink-mid);
  line-height: 1.55;
}
.xp__hint svg { flex: 0 0 auto; margin-top: 3px; color: var(--blue); }
.xp__hint b { color: var(--navy); }

.xp__audiences { display: flex; gap: 26px; flex-wrap: wrap; }
.xp__aud { min-width: 90px; }
.xp__audn { font-family: var(--font-head); font-size: 24px; color: var(--navy); }
.xp__aud.is-withheld .xp__audn { color: var(--ink-soft); }
.xp__audlabel { font-size: 12.5px; color: var(--ink-mid); margin-top: 2px; }
.xp__audnote { font-size: 11px; color: var(--ink-soft); margin-top: 1px; }

/* ---------- remedy plans ----------
   Rendered as a numbered sequence, not a table. The number is the product: a
   fault list with no order is the thing this replaces. */
.plan__where { font-size: 12.5px; color: var(--ink-mid); margin-top: 2px; }
.plan__total { font-size: 13px; color: var(--ink-mid); font-variant-numeric: tabular-nums; }
.plan__summary {
  margin: 0 0 16px; font-size: 14px; color: var(--ink-mid);
  max-width: 78ch; line-height: 1.6;
}
.plan__collapsed {
  display: flex; align-items: center; gap: 14px;
  font-size: 13.5px; color: var(--ink-mid);
}

/* The case for the plan: the same six questions the recommendation card
   answers, so it borrows that card's blocks (.rec__qa and friends) rather than
   growing a second set of labels at a slightly different size. Only the
   disclosure itself needs anything here — on a recommendation the summary is
   right-aligned in a footer row, and here it opens a full-width section
   between the plan's summary and its running order. */
.plan__brief { margin: -4px 0 18px; }
.plan__brief > .rec__whysum { font-size: 13px; }
.plan__brief > .rec__detail { margin-top: 9px; font-size: 13px; }
.plan__brief[open] { margin-bottom: 22px; }

/* The same block one level down, on a single job. Indented to the rationale
   it sits under rather than to the step body, so the two read as one argument
   about this job and not as a new section of the plan. */
.step__brief { margin: 10px 0 0 13px; }
.step__brief > .rec__whysum { font-size: 12px; }
.step__brief > .rec__detail { margin-top: 8px; }
.step__brief[open] { margin-bottom: 4px; }

/* The staff copy of the same disclosure, inside a review card's field stack.
   It sits under a field label like everything else on that screen, so the
   summary line loses the blue it carries on the customer's page and reads as
   one more control in the form. */
.review__derived > .rec__whysum { font-size: 12px; color: var(--ink-mid); }
.review__derived > .rec__whysum:hover { color: var(--navy); }
.review__derived > .rec__detail { margin-top: 8px; background: #FBFCFE; }

.plan__banner {
  display: flex; align-items: flex-start; gap: 11px; padding: 14px 18px;
  border-left: 3px solid var(--blue); background: var(--blue-tint);
  font-size: 13.5px; color: var(--navy-600);
}
.plan__banner svg { color: var(--blue); flex: 0 0 auto; margin-top: 1px; }
.plan__banner b { color: var(--navy); }

.steps { list-style: none; margin: 0; padding: 0; }
.step {
  display: flex; gap: 14px; padding: 16px 0;
  border-bottom: 1px solid var(--line-soft);
}
.step:first-child { padding-top: 0; }
.step:last-child { border-bottom: none; padding-bottom: 0; }

.step__num {
  flex: 0 0 30px; height: 30px; border-radius: 50%;
  background: var(--navy); color: #fff;
  font-family: var(--font-head); font-size: 15px;
  display: flex; align-items: center; justify-content: center;
}
.step.is-done .step__num { background: var(--teal); }

.step__body { flex: 1; min-width: 0; }
.step__head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.step__title { font-family: var(--font-head); font-size: 15.5px; color: var(--navy); }
.step__trade {
  font-size: 11px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--ink-soft); background: var(--line-soft);
  padding: 2px 8px; border-radius: 999px;
}

/* The reason this job is here and not elsewhere. Same weight as the job. */
.step__why {
  font-size: 13.5px; color: var(--ink-mid); line-height: 1.6;
  margin-top: 6px; max-width: 78ch;
  border-left: 2px solid var(--line); padding-left: 11px;
}

.step__meta {
  display: flex; gap: 16px; flex-wrap: wrap; margin-top: 9px;
  font-size: 12.5px; color: var(--ink-soft);
}
.step__meta b { color: var(--ink); font-variant-numeric: tabular-nums; }
/* A figure the customer has agreed to reads differently from one we guessed. */
.step__approved, .step__paid { color: var(--teal-dark); }
.step__approved b, .step__paid b { color: var(--teal-dark); }
.step__quoted { color: var(--blue); }
.step__quoted b { color: var(--blue); }
.step__cap { color: var(--ink-soft); font-weight: 400; }
.step__work { display: inline-flex; align-items: center; gap: 6px; color: var(--teal-dark); }

.step__verdict {
  display: flex; align-items: flex-start; gap: 8px; margin-top: 10px;
  padding: 9px 12px; border-radius: var(--r); font-size: 12.5px;
}
.step__verdict.is-improved { background: var(--teal-tint); color: #1F6E60; }
.step__verdict.is-worse    { background: var(--red-tint);  color: #8E332B; }
.step__verdict.is-unchanged,
.step__verdict.is-inconclusive { background: #EEF1F6; color: var(--ink-mid); }
.step__verdict svg { flex: 0 0 auto; margin-top: 1px; }

/* Who does this step. A segmented pair rather than a checkbox, because both
   options are real choices and neither is a default worth hiding. */
.step__assign { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 10px; }
.assign { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
.assign button {
  border: none; background: var(--surface); cursor: pointer;
  font-family: var(--font-body); font-size: 12px; color: var(--ink-mid);
  padding: 5px 11px; border-right: 1px solid var(--line);
}
.assign button:last-child { border-right: none; }
.assign button:hover { background: var(--blue-tint); }
.assign button.is-active { background: var(--navy); color: #fff; }
.assign.is-locked {
  padding: 4px 11px; font-size: 12px; color: var(--ink-mid);
  background: var(--line-soft); border-color: transparent;
}
.assign.is-locked.is-inhouse { background: var(--teal-tint); color: var(--teal-dark); }
.step__assignnote { font-size: 12px; color: var(--teal-dark); }

/* A step the customer kept reads as theirs down the left margin. */
.step.is-inhouse .step__num { background: var(--teal); }
.step.is-inhouse .step__why { border-left-color: var(--teal); }

/* ---------- deciding one step at a time ----------
   The decision lives on the step, not in a bar at the bottom, because that is
   where the thing being decided is described. */
.step__decide { margin-top: 12px; }
.step__decide .rejectbox { margin-top: 10px; }
.step__decidenote { margin-top: 10px; font-size: 12px; max-width: 62ch; }

.step__decided {
  display: flex; gap: 8px; align-items: flex-start;
  margin-top: 12px; padding: 9px 12px;
  border-radius: var(--r); font-size: 12.5px; line-height: 1.5;
}
.step__decided svg { flex: 0 0 auto; margin-top: 2px; }
.step__decided b { font-weight: 600; }
.step__decided.is-accepted { background: var(--teal-tint); color: #1F6E60; }
.step__decided.is-declined { background: #EEF1F6; color: var(--ink-mid); }
.step__by { color: var(--ink-soft); }
.step__declinewhy { margin-top: 3px; color: var(--ink-soft); }

/* A step that was passed on stays legible and stops competing — it is still
   part of the record of what was proposed, just not part of the work. */
.step.is-passed .step__num { background: var(--line); color: var(--ink-soft); }
.step.is-passed .step__title,
.step.is-passed .step__why,
.step.is-passed .step__meta { opacity: .62; }

/* ---------- where one job has got to ----------

   A rail of six nodes. Finished stages carry a tick rather than a filled disc,
   because six identical circles read as a progress bar cut into pieces and a
   tick reads as "this happened" — which is what it means.

   Hovering a node swaps its label for the date it happened. In place of, not
   beside: a row of six labels each with a date under it is a table, and the
   reason to draw a tracker instead of a table is that its shape can be read
   without reading any of it. The date is the second question, so it costs a
   hover. Touch has no hover, so the same string is on the title attribute.

   Laid out as a flex row that scrolls rather than a grid that divides: inside a
   step body on a narrow screen, equal columns got to about twelve pixels each
   and broke "Scheduled" into a vertical column of single letters. */
.track {
  margin: 12px 0 0; padding: 14px 16px 12px;
  border: 1px solid var(--line); border-radius: var(--r);
  background:
    linear-gradient(180deg, rgba(47,168,148,.045) 0%, rgba(47,168,148,0) 46px),
    var(--surface);
}
/* The one thing on the card that is the customer's move gets the only fill. */
.track.is-waiting {
  border-color: var(--blue);
  background:
    linear-gradient(180deg, rgba(46,111,217,.10) 0%, rgba(46,111,217,0) 52px),
    var(--blue-tint);
}

.track__rail {
  display: flex; margin: 0; padding: 0 0 2px; list-style: none;
  overflow-x: auto; scrollbar-width: thin;
}

.track__st {
  position: relative; flex: 1 0 78px; min-width: 78px; text-align: center;
  font-size: 10.5px; line-height: 1.3; color: var(--ink-soft);
}
/* The connector, drawn from the node before this one. Coloured by the stage
   BEHIND it, so an untravelled segment never leads into a reached node. */
.track__st::before {
  content: ''; position: absolute; top: 10px; right: 50%; width: 100%; height: 3px;
  border-radius: 2px; background: var(--line);
}
.track__st:first-child::before { display: none; }
.track__st.is-done + .track__st::before { background: var(--teal); }
/* A stage that does not apply is stepped over, not stopped at — the work did
   move past it, so the line does. */
.track__st.is-na + .track__st::before { background: var(--teal); }

.track__node {
  position: relative; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  width: 23px; height: 23px; margin: 0 auto 8px;
}
.track__dot {
  position: absolute; inset: 0; border-radius: 50%;
  border: 2px solid var(--line); background: var(--surface);
  transition: transform .16s ease, box-shadow .16s ease;
}
.track__node svg { position: relative; color: #fff; }

.track__st.is-done .track__dot { border-color: var(--teal); background: var(--teal); }
.track__st.is-done .track__label { color: var(--ink-mid); }

/* The stage in motion: hollow, ringed, and haloed so it reads as the front of
   the line rather than another completed one. */
.track__st.is-now .track__dot {
  border-color: var(--teal); background: var(--surface);
  box-shadow: 0 0 0 4px rgba(47,168,148,.16);
}
.track__st.is-now .track__label { color: var(--navy); font-weight: 600; }

.track__st.is-waiting .track__dot {
  border-color: var(--blue); background: var(--blue);
  box-shadow: 0 0 0 5px rgba(46,111,217,.18);
}
.track__st.is-waiting .track__label { color: var(--blue); font-weight: 600; }

.track__st.is-declined .track__dot { border-color: var(--red); background: var(--red); }
.track__st.is-declined .track__label { color: var(--red); }

/* Not applicable rather than not yet — a step the customer's own team is doing
   has no AtmosIQ price and no AtmosIQ invoice, and an empty circle under
   "Billed" would read as a bill that has not arrived. */
.track__st.is-na { opacity: .4; }
.track__st.is-na .track__dot { border-style: dashed; }

/* The label / date swap. The two strings are stacked, so nothing reflows and
   the rail does not twitch as the cursor crosses it.

   The fades are staggered rather than crossed: whichever text is leaving goes
   first, and the one arriving waits for it. Overlapped, the two sit on the same
   baseline at different widths, and the half-faded frame reads as corrupted
   text rather than as a transition. */
.track__text { position: relative; display: block; height: 1.3em; }
.track__label, .track__when { display: block; white-space: nowrap; }
.track__label { opacity: 1; transition: opacity .1s ease .1s; }
.track__when {
  position: absolute; inset: 0; opacity: 0;
  transition: opacity .1s ease;
  color: var(--navy); font-variant-numeric: tabular-nums;
}
.track__st.has-when { cursor: default; }
.track__st.has-when:hover .track__label { opacity: 0; transition: opacity .1s ease; }
.track__st.has-when:hover .track__when { opacity: 1; transition: opacity .1s ease .1s; }
.track__st.has-when:hover .track__dot { transform: scale(1.12); }

@media (prefers-reduced-motion: reduce) {
  .track__label, .track__when, .track__dot { transition: none; }
  .track__st.has-when:hover .track__dot { transform: none; }
}

.track__say {
  margin-top: 12px; padding-top: 10px;
  border-top: 1px solid var(--line-soft);
  font-size: 12.5px; color: var(--ink-mid); line-height: 1.55;
}
.track.is-waiting .track__say { border-top-color: #CBDCF6; color: var(--ink); }
.track__decide { margin-top: 10px; }
/* The quiet half of a decision: the line beside the button saying what to do
   if the answer is no. It had a class and no rule in either stylesheet, so it
   rendered at body size in full-strength ink — reading louder than the
   control it qualifies. Found by the audit behind review item 3.1. */
.track__askto { font-size: 12.5px; color: var(--ink-mid); line-height: 1.5; }

/* ---------- a plan card, opened and closed ----------
   The whole head is the control. It was a "See the plan" button on its own row
   under the summary, which spent a line of vertical space per plan and put the
   control somewhere other than the thing it controlled. */
.plan__head {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 14px 18px; border: none; border-bottom: 1px solid transparent;
  background: none; text-align: left; cursor: pointer;
  font-family: var(--font-body); color: inherit;
}
.plan__head:hover { background: #FBFCFE; }
.plan__head:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }
.plan__head.is-open { border-bottom-color: var(--line-soft); }

.plan__chev {
  flex: 0 0 auto; display: inline-flex; color: var(--ink-soft);
  transition: transform .16s ease;
}
.plan__head.is-open .plan__chev { transform: rotate(180deg); color: var(--navy); }

.plan__headtext { flex: 1; min-width: 0; }
.plan__title {
  display: block; font-family: var(--font-head); font-size: 16px;
  color: var(--navy); line-height: 1.3;
}
/* Building, then what is outstanding — the second half is the only reason to
   open a plan in a hurry, so it rides on the same line rather than waiting
   inside. */
.plan__where {
  display: block; font-size: 12.5px; color: var(--ink-mid); margin-top: 2px;
}
.plan__dot { margin: 0 7px; color: var(--ink-soft); }

.plan__headright {
  flex: 0 0 auto; display: flex; align-items: center; gap: 12px;
}
.plan__totals { text-align: right; }
.plan__total { display: block; }
.plan__totalsub { display: block; }

@media (max-width: 720px) {
  /* Title over figures rather than beside them; at this width the two were
     fighting and the title lost. */
  .plan__head { flex-wrap: wrap; }
  .plan__headright { width: 100%; justify-content: flex-start; padding-left: 27px; }
}

/* ---------- the staff sequence ----------
   The same rail the customer sees, one per job, above the numbers table. */
.opssteps { list-style: none; margin: 0; padding: 0; }
.opsstep {
  display: flex; gap: 13px; padding: 14px 0;
  border-bottom: 1px solid var(--line-soft);
}
.opsstep:first-child { padding-top: 0; }
.opsstep:last-child { border-bottom: none; padding-bottom: 0; }
.opsstep__num {
  flex: 0 0 26px; height: 26px; border-radius: 50%;
  background: var(--navy); color: #fff;
  font-family: var(--font-head); font-size: 13.5px;
  display: flex; align-items: center; justify-content: center;
}
.opsstep.is-inhouse .opsstep__num { background: var(--teal); }
.opsstep__body { flex: 1; min-width: 0; }
.opsstep__head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.opsstep__title { font-family: var(--font-head); font-size: 14.5px; color: var(--navy); }
/* The rail is already inside a card here, so it drops its own border and sits
   on the row instead of floating in a box within a box. */
.track--staff { border-color: var(--line-soft); }

.plan__totals { text-align: right; }
.plan__totalsub { font-size: 11.5px; color: var(--teal-dark); margin-top: 2px; }

.plan__decide { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line); }

/* Where the plan stands, before the promises that go with it. */
.plan__tally { margin-bottom: 14px; }
.plan__tallyline { font-size: 13px; color: var(--ink-mid); margin-bottom: 4px; }
.plan__tallyline b { color: var(--navy); }
.plan__tallyline.is-open { color: var(--blue); }
.plan__tallycost { color: var(--ink); font-variant-numeric: tabular-nums; }

.plan__decidenote {
  font-size: 12.5px; color: var(--ink-mid); max-width: 74ch;
}

/* The approved contractors, sitting directly under the paragraph that promises
   them. Set on a tinted ground so it reads as the evidence for that paragraph
   rather than as another section of the plan. */
.vendors:not(:empty) {
  margin-top: 14px; padding: 14px 16px;
  background: var(--blue-tint); border: 1px solid #D3E0F5;
  border-radius: var(--r);
}
.vendors__head { margin-bottom: 12px; }
.vendors__head b {
  display: block; font-family: var(--font-head);
  font-size: 13.5px; color: var(--navy); margin-bottom: 3px;
}
.vendors__note { font-size: 12px; color: var(--ink-mid); line-height: 1.55; }

.vendors__grid {
  display: grid; gap: 12px 20px;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
.vendors__tradename {
  font-size: 10.5px; letter-spacing: .7px; text-transform: uppercase;
  color: var(--ink-soft); margin-bottom: 5px;
}
.vendors__list { margin: 0; padding: 0; list-style: none; }
.vendors__list li { margin-bottom: 7px; }
.vendors__name {
  display: block; font-size: 13px; color: var(--navy); font-weight: 600;
}
/* The checkable part, kept small but present — a list of names proves nothing;
   a licence number can be run against the state board. */
.vendors__lic {
  display: block; font-size: 11.5px; color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
.vendors__none { font-size: 12px; color: var(--ink-mid); line-height: 1.5; }
.plan__decidenote p { margin: 0 0 8px; line-height: 1.6; }
.plan__decidenote p:last-child { margin-bottom: 0; }
.plan__decidenote b { color: var(--navy); font-weight: 600; }
.plan__decided {
  margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line);
  font-size: 13.5px; color: var(--ink-mid);
}
.plan__decided b { color: var(--navy); }
.plan__by { color: var(--ink-soft); }
.plan__note { margin-top: 6px; font-size: 12.5px; color: var(--ink-soft); max-width: 78ch; }

/* ---------- seasonality ----------
   The verdict card carries the whole point of the view, so it is the one
   element here allowed to shout. Three states: nothing repeating, a pattern the
   weather explains, and a pattern it does not. Only the last is loud. */
.season__verdict { border-left: 3px solid var(--line); }
.season__verdict.is-calm { border-left-color: var(--teal); }
.season__verdict.is-note { border-left-color: var(--blue); }
.season__verdict.is-act  { border-left-color: var(--red); background: var(--red-tint); }

.season__verdicthead {
  display: flex; gap: 12px; align-items: flex-start; padding: 16px 18px;
}
.season__verdicthead > svg { flex: 0 0 auto; margin-top: 2px; }
.season__verdict.is-calm .season__verdicthead > svg { color: var(--teal-dark); }
.season__verdict.is-note .season__verdicthead > svg { color: var(--blue); }
.season__verdict.is-act  .season__verdicthead > svg { color: var(--red); }

.season__verdicttitle {
  font-family: var(--font-head); font-size: 17px; line-height: 1.3; color: var(--navy);
}
.season__verdict.is-act .season__verdicttitle { color: var(--red); }
.season__verdictbody {
  font-size: 13.5px; color: var(--ink-mid); margin-top: 5px; max-width: 76ch;
}
/* The yearly figures behind the verdict — evidence, so quieter than the claim. */
.season__years {
  font-size: 12.5px; color: var(--ink-soft); margin-top: 6px; max-width: 76ch;
  font-variant-numeric: tabular-nums;
}

.season__legend {
  display: flex; gap: 18px; flex-wrap: wrap; margin-top: 12px;
  font-size: 12.5px; color: var(--ink-mid);
}
.season__key { display: inline-flex; align-items: center; gap: 7px; }
.season__swatch { width: 12px; height: 9px; border-radius: 2px; opacity: .45; }
.season__dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: #fff; border: 1.4px solid var(--ink-mid);
}
.season__dash {
  width: 16px; height: 0; border-top: 1.5px dashed var(--navy);
}

/* The companion reading, which is a line rather than bars so the two series
   on one chart can never be mistaken for each other. */
.season__line { width: 16px; height: 2.5px; border-radius: 2px; }
/* Drawn dashed on the chart when it shares the subject's axis, so two lines
   in the same units are told apart without checking the colours. */
.season__line.is-dashed {
  height: 0; border-radius: 0; background: none !important;
  border-top: 2.5px dashed currentColor;
}

.season__resfoot {
  display: flex; gap: 14px; flex-wrap: wrap; align-items: baseline;
  margin-top: 13px; padding-top: 12px; border-top: 1px solid var(--line-soft);
  font-size: 13px; color: var(--ink-mid);
}
.season__resfoot b { color: var(--navy); }
.season__corr { margin-left: auto; font-size: 12px; color: var(--ink-soft); }

/* ---------- fragrance coverage ----------
   A configuration fact, stated in one line. It used to hold half the width of
   the page beside unit status, as though the two were comparable questions. */
.fragstrip {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px 18px; font-size: 13.5px; color: var(--ink-mid);
}
.fragstrip svg { color: var(--teal); }
.fragstrip b { color: var(--navy); }
.fragstrip__note {
  margin-left: auto; display: flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--ink-soft);
}
.fragstrip__note svg { color: var(--ink-soft); }

/* ---------- table ---------- */
table.tbl { width: 100%; border-collapse: collapse; font-size: 13.5px; }
table.tbl th {
  text-align: left; font-family: var(--font-body); font-weight: 600;
  font-size: 11.5px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--ink-soft); padding: 11px 18px; border-bottom: 1px solid var(--line);
}
table.tbl td { padding: 12px 18px; border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
table.tbl tr:last-child td { border-bottom: none; }
table.tbl tbody tr.clickable { cursor: pointer; }
table.tbl tbody tr.clickable:hover { background: var(--blue-tint); }
/* The remedy-plan table on the staff side carries two money columns that each
   stack a second line under the figure. Left to size themselves, the browser
   gave those numbers room and squeezed the job description into a four-character
   column reading one word per line.

   Fixed layout with declared widths: the prose column takes what is left, and
   the numbers get exactly enough to sit on one line. The container scrolls
   rather than the columns collapsing further. */
.tbl--plan { table-layout: fixed; min-width: 880px; }
.tbl--plan th, .tbl--plan td { padding-left: 12px; padding-right: 12px; }
.tbl--plan col.col-n     { width: 34px; }
.tbl--plan col.col-job   { width: auto; }
.tbl--plan col.col-trade { width: 96px; }
/* Wide enough for the "Customer's team" pill, which does not wrap and so
   overlapped the money column beside it at 92px. */
.tbl--plan col.col-who   { width: 124px; }
.tbl--plan col.col-est   { width: 104px; }
.tbl--plan col.col-quote { width: 128px; }
.tbl--plan col.col-wait  { width: 118px; }
.tbl--plan col.col-work  { width: 116px; }
.tbl--plan td { vertical-align: top; }
.tbl--plan .tbl__sub { margin-top: 3px; }
/* Money reads down the column, so it must not wrap mid-figure. */
.tbl--plan td.mono { white-space: nowrap; }
.tbl--plan td.mono div { white-space: normal; }

/* ---------- a table, past the width a table works at ----------
 *
 * Review item 3.2: "Below ~900 px the building name column wraps one word per
 * line ('Meadow / Lane / Memory / Care'), tables overflow into a horizontal
 * page scrollbar, and the 'What is being watched' table scrolls inside its
 * card."
 *
 * All three measured true at 800px: the name column was 108px and six lines
 * tall, the executive summary's table overflowed its card by 13px, and All
 * buildings pushed the page itself 100px sideways.
 *
 * A .tbl--stack row becomes a block when the table no longer fits the card it
 * is in: the first cell is its heading, every other cell is its column's name
 * beside its value. The name comes from the <th> via a data-label attribute,
 * stamped in js/ui.js at el() — CSS can read an attribute off the element it
 * styles and nothing else. The label is positioned rather than laid out as a
 * flex item, because a cell holding three pills would otherwise spread them
 * across the row.
 *
 * WHEN IT NO LONGER FITS, not below a width somebody picked. The review says
 * 900px and 900px is wrong for half these tables: at 1280 the executive
 * summary's five columns have 941px and are comfortable, while All buildings'
 * seven have 660px in the narrow column of a two-column page and are not. The
 * measuring is in js/ui.js stacked(); this sheet supplies the floor it tests
 * against and the blocks it switches to.
 *
 * .tbl--plan keeps its horizontal scroll on purpose: it is table-layout:fixed
 * with a declared min-width, a Gantt-ish thing where the columns ARE the
 * sequence, and stacking it would throw away what it is for.
 */
/* THE FLOOR, which is also the trigger. A name column narrower than this is
   where "Meadow Lane Memory Care" starts arriving as four lines of one word,
   so a table that cannot give its first column this much does not fit — and
   js/ui.js, which measures, stacks it. */
table.tbl--stack th:first-child,
table.tbl--stack td:first-child { min-width: 168px; }

/* table.tbl… rather than .tbl…: the full-width rules are written that way and
   a bare class loses to them on specificity, which would leave the cells with
   their table padding and their borders. */
table.tbl--stack.is-stacked thead { display: none; }
table.tbl--stack.is-stacked, table.tbl--stack.is-stacked tbody,
table.tbl--stack.is-stacked tr, table.tbl--stack.is-stacked td { display: block; }
table.tbl--stack.is-stacked tr {
  padding: 12px 18px; border-bottom: 1px solid var(--line);
}
table.tbl--stack.is-stacked tr:last-child { border-bottom: none; }

table.tbl--stack.is-stacked td {
  position: relative; border: none;
  padding: 2px 0 2px 42%; min-height: 1.45em;
  text-align: right; vertical-align: baseline;
}
table.tbl--stack.is-stacked td::before {
  content: attr(data-label);
  position: absolute; left: 0; top: 2px; width: 38%;
  text-align: left; text-transform: uppercase; letter-spacing: .07em;
  font-size: 10.5px; line-height: 20.25px; color: var(--ink-soft);
}
/* An actions column has no heading, and a row the stamp skipped — the empty
   state with its colspan — has no label to show. Neither gets an empty box of
   space held open for it. */
table.tbl--stack.is-stacked td:not([data-label]) {
  padding-left: 0; text-align: left;
}
table.tbl--stack.is-stacked td:not([data-label])::before { content: none; }

/* The first cell names the row. It is a heading, not a label and a value, and
   it is the one cell that gets the whole width to say what it is. */
table.tbl--stack.is-stacked td:first-child {
  padding: 0 0 7px; text-align: left; min-height: 0;
}
table.tbl--stack.is-stacked td:first-child::before { content: none; }

.tbl__name { font-family: var(--font-head); font-size: 14.5px; color: var(--navy); }
.tbl__sub { font-size: 12px; color: var(--ink-mid); }

/* ---------- sensor tiles ---------- */
.sensor { border: 1px solid var(--line); border-radius: var(--r); padding: 14px; background: var(--surface); }
.sensor.is-warn  { border-color: #EBD3A0; background: #FFFDF7; }
.sensor.is-alert { border-color: #EEBAB4; background: #FFFAF9; }
.sensor__label { font-size: 11.5px; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-soft); }
.sensor__val { font-family: var(--font-head); font-size: 25px; color: var(--navy); margin-top: 4px; }
.sensor__val small { font-size: 13px; color: var(--ink-mid); font-family: var(--font-body); margin-left: 3px; }
.sensor__val.is-warn  { color: var(--amber); }
.sensor__val.is-alert { color: var(--red); }
.sensor__range { font-size: 11.5px; color: var(--ink-soft); margin-top: 2px; }
.sensor__spark { margin-top: 8px; }

/* ---------- fragrance control ---------- */
.frag { display: flex; align-items: center; gap: 14px; padding: 14px 16px; border: 1px solid var(--line); border-radius: var(--r); }
.frag.is-locked { background: #FAFBFD; border-style: dashed; }
.frag__text { flex: 1; min-width: 0; }
.frag__title { font-family: var(--font-head); font-size: 14.5px; color: var(--navy); }
.frag__note { font-size: 12.5px; color: var(--ink-mid); margin-top: 2px; }

.switch { position: relative; width: 46px; height: 26px; flex: 0 0 46px; }
.switch input { opacity: 0; width: 0; height: 0; position: absolute; }
.switch__track {
  position: absolute; inset: 0; background: #C6CDDA; border-radius: 999px;
  transition: background .16s; cursor: pointer;
}
.switch__track::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 20px; height: 20px; border-radius: 50%; background: #fff;
  transition: transform .16s; box-shadow: 0 1px 3px rgba(0,0,0,.22);
}
.switch input:checked + .switch__track { background: var(--teal); }
.switch input:checked + .switch__track::after { transform: translateX(20px); }
.switch input:disabled + .switch__track { background: #DFE4EC; cursor: not-allowed; }
.switch input:disabled + .switch__track::after { background: #F2F4F8; }

.lockmsg {
  display: flex; gap: 9px; align-items: flex-start;
  background: var(--blue-tint); border-left: 3px solid var(--blue);
  padding: 11px 13px; border-radius: 0 var(--r) var(--r) 0;
  font-size: 12.5px; color: var(--navy-600); margin-top: 12px;
}

/* ---------- recommendations -------------------------------------------------
 *
 * A card holds five things a reader decides from and four that explain why.
 * The first five are laid out; the four are behind a disclosure. See the note
 * on recCard in js/views/recommendations.js for why.
 */
.rec { padding: 16px 18px; border-bottom: 1px solid var(--line-soft); }
.rec:last-child { border-bottom: none; }

.rec__top {
  display: flex; align-items: center; gap: 9px; margin-bottom: 8px;
  flex-wrap: wrap;
}

/* Where it is. A button because it goes somewhere, styled as text because a
   bordered control here would compete with the action below it. */
.rec__where {
  border: none; background: none; padding: 0; cursor: pointer;
  font-family: var(--font-body); font-size: 12.5px; color: var(--ink-mid);
}
.rec__where:hover { color: var(--blue); text-decoration: underline; }

.rec__state { font-size: 11.5px; font-weight: 600; color: var(--blue); }
.rec__state.is-done { color: var(--ink-soft); font-weight: 400; }

/* What to do. The only thing on the card at full weight, and the reason the
   rest of it can be quiet. */
.rec__title {
  font-size: 15px; color: var(--navy); line-height: 1.5; max-width: 72ch;
}
.rec__title .actionsteps li { margin-bottom: 4px; }

/* The money and the way in, on one line. Two things a reader wants next, and
   stacking them spent a row of vertical space on each. */
.rec__foot {
  display: flex; align-items: baseline; gap: 18px;
  flex-wrap: wrap; margin-top: 11px;
}
/* The estimate sits in this row rather than opening its own section, so it
   loses the rule it draws for itself elsewhere. */
.rec__foot .costest { margin-top: 0; padding-top: 0; border-top: none; }

.rec__why { margin-left: auto; }
.rec__whysum {
  display: inline-flex; align-items: center; gap: 5px;
  cursor: pointer; list-style: none;
  font-size: 12.5px; color: var(--blue);
  padding: 2px 0;
}
.rec__whysum::-webkit-details-marker { display: none; }
.rec__whysum:hover { color: var(--blue-dark); }
.rec__whysum svg { transition: transform .16s ease; }
details[open] > .rec__whysum svg { transform: rotate(180deg); }
.rec__whysum:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }

/* The answer to "why are you telling me this", which is a second question. */
.rec__detail {
  margin-top: 10px; padding: 12px 14px;
  background: #FAFBFD; border-left: 2px solid var(--line);
  border-radius: 0 var(--r) var(--r) 0;
  font-size: 12.5px; color: var(--ink-mid); line-height: 1.55;
  /* It opens under a right-aligned summary, so it has to reclaim the width. */
  width: 100%;
}
.rec__because b, .rec__assumes b, .rec__method b { color: var(--ink-soft); font-weight: 600; }
.rec__body { margin-top: 6px; }
.rec__assumes { margin-top: 6px; }

/* ---- six questions, six answers -------------------------------------------
   A labelled block each. The complaint that started this was that six kinds
   of sentence arrived at one weight; the fix is not shorter sentences, it is
   saying which question each one answers. */
.rec__qa { margin-bottom: 13px; }
.rec__qa:last-of-type { margin-bottom: 0; }
.rec__q {
  font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-soft); font-weight: 600; margin-bottom: 3px;
}
.rec__a { color: var(--ink); line-height: 1.6; max-width: 74ch; }
.rec__method { margin-top: 5px; color: var(--ink-mid); font-size: 12px; }
.rec__eg { margin-top: 6px; }
.rec__eg b { color: var(--ink-soft); font-weight: 600; }

/* The caveat under a claim about outcomes. Quiet, and never absent — the
   sentence above it is the one most worth being careful about. */
.rec__hedge {
  margin-top: 6px; font-size: 11.5px; color: var(--ink-soft);
  font-style: italic; line-height: 1.5;
}

/* How long, beside what it costs. */
.rec__dur { display: inline-flex; align-items: baseline; gap: 7px; }
.rec__durlabel {
  font-size: 11px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-soft);
}
.rec__durval {
  font-size: 14px; font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.rec__meta {
  margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--line-soft);
  font-size: 11.5px; color: var(--ink-soft);
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.rec__vetted {
  display: inline-flex; align-items: center; gap: 5px;
  color: var(--teal-dark); font-weight: 600;
}

/* A details element inside a flex row needs to be able to span it when open. */
.rec__foot > details[open] { width: 100%; margin-left: 0; }
.rec__foot > details[open] > .rec__whysum { float: right; }

/* ---------- what review caught ----------
   Three figures and one sentence. It was a hundred and ninety characters of
   prose containing the same three numbers, and the numbers were the part
   anybody read. */
.vetnote {
  display: flex; gap: 12px; align-items: flex-start;
  background: var(--teal-tint); border: 1px solid #BFE3DA;
  padding: 13px 16px; border-radius: var(--r);
  color: #1F6E60; margin-bottom: 16px;
}
.vetnote__body { flex: 1; min-width: 0; }
.vetnote__figs { display: flex; gap: 22px; flex-wrap: wrap; }
.vetnote__fig { display: flex; align-items: baseline; gap: 6px; }
.vetnote__fig b {
  font-family: var(--font-head); font-size: 20px; color: #17564B;
  line-height: 1;
}
.vetnote__fig span { font-size: 12px; }
.vetnote__say { margin-top: 7px; font-size: 12.5px; }
.vetnote__more { margin-top: 6px; }
.vetnote__more .rec__whysum { color: #1F6E60; }
.vetnote__more .rec__detail {
  background: rgba(255,255,255,.6); border-left-color: #BFE3DA; color: #1F6E60;
}

/* Where a stated fact came from. Quiet by default — provenance is reassurance
   while it is fresh — and marked once the entry is old enough that the reader
   should check it, because at that point it is the most useful thing on the
   card. */
.exsource {
  display: flex; gap: 9px; align-items: flex-start;
  margin-top: 12px; padding-top: 11px;
  border-top: 1px solid var(--line-soft);
  font-size: 12px; color: var(--ink-soft); line-height: 1.55;
}
.exsource svg { flex: 0 0 auto; margin-top: 2px; }
.exsource__ask { margin-top: 3px; }
.exsource.is-stale {
  color: var(--ink-mid);
  border-top-color: #EBD3A0;
}
.exsource.is-stale svg { color: var(--amber); }
.exsource.is-stale .exsource__ask { color: var(--amber); }

/* ---------- chart ---------- */
/* The box a chart is measured against. Block-level and full width so the
   measurement is of the card, and min-height so a card does not collapse and
   then jump in the frame between insertion and the first draw. */
.chartfit { display: block; width: 100%; min-height: 120px; }
.chartwrap { position: relative; }
.chart-legend { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12.5px; color: var(--ink-mid); margin-top: 10px; }
.chart-legend button {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  border: none; background: none; font-family: var(--font-body); font-size: 12.5px;
  color: var(--ink-mid); padding: 2px 0;
}
.chart-legend .swatch { width: 11px; height: 3px; border-radius: 2px; }
/* The key under a chart: what each line is. Plain text — the reading picker
   above the chart is the control. */
.chart-legend__item { display: inline-flex; align-items: center; gap: 6px; }
/* Wraps on a narrow screen rather than pushing the card wider than the page. */
.readingpicker { flex-wrap: wrap; }
.chart-legend button.is-off { opacity: .38; }
/* Which side of the chart a legend entry is read against. Only shown for the
   right-hand axis: the left is where a reader assumes a line belongs, and
   labelling both would be labelling the default. */
.chart-legend__side {
  margin-left: 5px; font-size: 10.5px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-soft);
}


/* ---------- this tab is behind the server ----------
   Fixed to the bottom rather than pushed into the layout: it is about the
   page itself, not about anything on it, and it must not move the content
   somebody is in the middle of reading. */
.stalebar {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: 18px; z-index: 60;
  display: flex; align-items: center; gap: 11px;
  max-width: min(620px, calc(100vw - 32px));
  padding: 12px 16px; border-radius: var(--r-lg);
  background: var(--navy); color: #fff;
  box-shadow: var(--shadow-lg);
  font-size: 13.5px;
}
.stalebar svg { color: #fff; flex: 0 0 auto; }
.stalebar b { font-weight: 600; }
.stalebar .btn {
  background: rgba(255,255,255,.12); border-color: rgba(255,255,255,.3);
  color: #fff;
}
.stalebar .btn:hover { background: rgba(255,255,255,.2); }

/* A numbered recommendation. The marker carries the order, which is half the
   advice — "calibrate, then believe the reading" is not the same instruction
   in the other order. */
.actionsteps { margin: 0; padding-left: 1.35em; }
.actionsteps li { margin-bottom: 5px; }
.actionsteps li:last-child { margin-bottom: 0; }
.actionsteps li::marker { color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* The reviewer's five boxes. Numbered down the left so the order being edited
   is the order the customer will read. */
.steplist { display: flex; flex-direction: column; gap: 7px; }
.steplist__row { display: flex; align-items: center; gap: 9px; }
.steplist__row input { flex: 1; min-width: 0; }
.steplist__n {
  flex: 0 0 22px; height: 22px; border-radius: 50%;
  background: var(--line-soft); color: var(--ink-mid);
  font-size: 11.5px; font-variant-numeric: tabular-nums;
  display: flex; align-items: center; justify-content: center;
}

/* What a recommendation is likely to cost. Deliberately quiet: a figure set
   in a bordered, tinted box reads as a quote, and this is not one. A rule and
   a weight is enough to find it while scanning. */
.costest {
  margin-top: 10px; padding-top: 9px;
  border-top: 1px solid var(--line-soft);
}
.costest__head {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
.costest__lede {
  font-size: 11px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-soft);
}
.costest__figure {
  font-size: 15px; font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* The caveat travels with the number, every time it is shown. */
.costest__basis {
  margin-top: 5px; font-size: 12px; line-height: 1.5; color: var(--ink-mid);
}
/* "No cost" and "needs pricing" have no figure to carry the weight, so the
   lede takes it rather than sitting there as a tiny grey label. */
.costest--flat .costest__lede {
  font-size: 13px; text-transform: none; letter-spacing: 0;
  font-weight: 600; color: var(--ink);
}

/* The reviewer's side: three postures, one of which opens two money boxes. */
.costedit { display: flex; flex-direction: column; gap: 8px; margin-bottom: 9px; }
.costedit__opt {
  display: flex; align-items: flex-start; gap: 9px; cursor: pointer;
  font-size: 13px;
}
.costedit__opt input { margin-top: 3px; flex: 0 0 auto; }
.costedit__body { flex: 1; min-width: 0; }
.costedit__optname { display: block; font-weight: 600; }
.costedit__opthint {
  display: block; font-size: 11.5px; color: var(--ink-soft); line-height: 1.45;
}
.costedit__range {
  display: flex; align-items: center; gap: 5px; margin-top: 6px;
}
.costedit__range.is-off { opacity: .4; }
.costedit__cur { color: var(--ink-soft); font-size: 13px; }
.costedit__to { color: var(--ink-soft); font-size: 12px; margin: 0 3px; }

/* The dispensers behind a reorder count. Chips rather than a list: they sit
   inside a sentence that already runs two lines, and each is a jump rather
   than a row of its own. */
.duelist { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }
.duechip {
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 3px 9px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface);
  font-family: var(--font-body); font-size: 12px; color: var(--ink);
  cursor: pointer;
}
.duechip:hover { border-color: var(--blue); background: var(--blue-tint); }
.duechip__pct {
  font-size: 11px; color: var(--amber); font-variant-numeric: tabular-nums;
}
.duechip.is-more { cursor: default; color: var(--ink-soft); border-style: dashed; }
.duechip.is-more:hover { border-color: var(--line); background: var(--surface); }

/* A unit named inside an order. Underlined rather than buttoned: it sits in a
   run of comma-separated names inside a table cell, and four buttons in a row
   would read as four controls rather than one list. */
.unitlink {
  border: none; background: none; padding: 0; cursor: pointer;
  font-family: var(--font-body); font-size: inherit; color: var(--blue);
  text-decoration: underline; text-underline-offset: 2px;
}
.unitlink:hover { color: var(--blue-dark); }

/* Arrived here by clicking something.

   A ring and a wash rather than a border: a border changes the element's size
   and shunts everything below it by a pixel at the moment somebody is trying
   to read it. The fade is slow enough to be noticed and short enough not to
   become another piece of state to dismiss — it answers "which one" and then
   gets out of the way. */
@keyframes reveal {
  0%   { box-shadow: 0 0 0 3px var(--blue); background-color: var(--blue-tint); }
  55%  { box-shadow: 0 0 0 3px var(--blue); background-color: var(--blue-tint); }
  100% { box-shadow: 0 0 0 3px rgba(46,111,217,0); background-color: transparent; }
}
.is-revealed {
  animation: reveal 2.6s ease-out forwards;
  border-radius: var(--r);
  scroll-margin: 90px;
}
/* A table row cannot carry a box-shadow ring on most engines, so it takes the
   wash alone rather than nothing. */
tr.is-revealed { animation: revealrow 2.6s ease-out forwards; }
@keyframes revealrow {
  0%, 55% { background-color: var(--blue-tint); }
  100%    { background-color: transparent; }
}

/* Everything the board can point at needs room under the sticky header when it
   is scrolled to, or it lands beneath it. */
[data-focus] { scroll-margin-top: 90px; }

@media (prefers-reduced-motion: reduce) {
  .is-revealed, tr.is-revealed { animation: none; background-color: var(--blue-tint); }
}

.empty { padding: 40px 18px; text-align: center; color: var(--ink-soft); font-size: 13.5px; }

.seg { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
.seg button {
  border: none; background: var(--surface); padding: 6px 12px;
  font-family: var(--font-body); font-size: 12.5px; color: var(--ink-mid); cursor: pointer;
  border-right: 1px solid var(--line);
}
.seg button:last-child { border-right: none; }
.seg button.is-active { background: var(--navy); color: #fff; }

/* A multi-select row. Separated so it does not read as "one of these": the
   gaps say each one is its own switch, and several can be down at once. */
.seg--multi { border: none; gap: 6px; overflow: visible; }
.seg--multi button {
  border: 1px solid var(--line); border-radius: var(--r);
  margin: 0;
}
.seg--multi button.is-active { border-color: var(--navy); }


/* ---------- controls both dashboards use ----------
 *
 * Review item 3.1, which read: "Building and parameter selector chips have no
 * visible selected state (native unstyled buttons); the chip row wraps so the
 * wrong chip is easy to hit."
 *
 * The parameter selector is .seg--multi above and had been fixed by then. The
 * chips had not, and the reason was that every rule below lived in
 * css/ops.css — which index.html does not load. On the customer's dashboard
 * they rendered as the browser's own buttons and inputs: 2px outset black
 * borders, Arial, square corners, and the SELECTED chip looking exactly like
 * the others, so the building a chart was about could not be read off the
 * control that chose it.
 *
 * THE RULE THIS ESTABLISHES: anything a customer page can reach lives here.
 * css/ops.css is for chrome only the staff tool has. A test walks the scripts
 * index.html loads and fails on a class that is defined in ops.css alone —
 * which is how this was found, and there were six more of them.
 *
 * The names are ops-era (.opsinput, .opsnote, .qcheck) because they were
 * written for the staff tool and are used by customer pages now. Renaming
 * them is 150 call sites across both dashboards for no reader's benefit, so
 * they keep their names and this note explains them.
 */
.chips {
  display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
  margin-bottom: 14px;
}
.chip {
  border: 1px solid var(--line); background: #fff; border-radius: 999px;
  padding: 6px 14px; font-size: 12.5px; color: var(--ink-mid);
  cursor: pointer; font-family: var(--font-body);
  /* A building name is ONE target, not three. Wrapping inside a chip is what
     makes a wrapped row easy to mis-hit: the chips take different heights,
     the row reflows, and what is under the pointer is not what was there a
     moment ago. The ROW wraps; a label does not.

     And a label longer than the row it is in does not push the whole PAGE
     sideways either: it is capped at the row's width and clipped, with the
     name still whole in the button's title and in what a screen reader
     reads. A horizontal scrollbar on the page is review item 3.2. */
  white-space: nowrap;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.chip:hover { border-color: var(--ink-soft); }
.chip.is-active { background: var(--navy); border-color: var(--navy); color: #fff; }
/* Styling a button takes the browser's own focus ring with it, and these are
   the only way to change what a chart is about from the keyboard. */
.chip:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

.field { margin-bottom: 12px; }
.field__label {
  display: block; font-size: 11px; text-transform: uppercase;
  letter-spacing: .08em; color: var(--ink-soft); margin-bottom: 5px;
}
.field input[type="text"], .field textarea, .opsinput {
  width: 100%; font-family: var(--font-body); font-size: 13.5px;
  color: var(--ink); background: #fff;
  border: 1px solid var(--line); border-radius: var(--r);
  padding: 8px 11px; resize: vertical;
}
.field input[type="text"]:focus, .field textarea:focus, .opsinput:focus {
  outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px rgba(46,111,217,.12);
}
.field textarea { min-height: 84px; line-height: 1.55; }
.field__hint { font-size: 11.5px; color: var(--ink-soft); margin-top: 4px; }

/* Declining something looks like declining something. This was a plain button
   on the customer's remedy plans, which is the one screen where the button
   that says no must not look like the button that says yes. */
.btn--danger { background: var(--red); border-color: var(--red); color: #fff; }
.btn--danger:hover { background: #A83A31; border-color: #A83A31; }

.opsnote {
  display: flex; gap: 10px; align-items: flex-start;
  background: var(--amber-tint); border-left: 3px solid var(--amber);
  padding: 12px 14px; border-radius: 0 var(--r) var(--r) 0;
  font-size: 12.5px; color: #7A5405; margin-bottom: 16px;
}
.opsnote svg { color: var(--amber); flex: 0 0 auto; margin-top: 1px; }

.qcheck {
  display: flex; align-items: center; gap: 8px; margin-bottom: 5px;
  font-size: 13.5px; color: var(--ink-mid);
}

.stack { display: flex; flex-direction: column; gap: 16px; }
.row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.muted { color: var(--ink-mid); }
.mono { font-variant-numeric: tabular-nums; }

/* ---------- needs attention ----------
   Above everything on the portfolio, and only when there is something. A
   permanent band that usually says "nothing" trains people to stop reading it.

   Rows are buttons where there is somewhere to go and plain where there is not
   — an offline sensor is a fact AtmosIQ is already acting on, and making it
   look clickable would promise the reader a control that does not exist. */
.needs {
  background: var(--surface); border: 1px solid var(--line);
  border-left: 3px solid var(--blue); border-radius: var(--r-lg);
  margin-bottom: 18px; overflow: hidden;
}
.needs__head {
  display: flex; align-items: center; gap: 9px;
  padding: 12px 18px; border-bottom: 1px solid var(--line-soft);
  font-size: 14px; color: var(--navy);
}
.needs__head svg { color: var(--blue); }
.needs__count { margin-left: auto; font-size: 12.5px; color: var(--ink-soft); }

.needsrow {
  display: flex; align-items: baseline; gap: 12px; width: 100%; text-align: left;
  padding: 11px 18px; border: none; background: none;
  font-family: var(--font-body); font-size: 13.5px;
  border-bottom: 1px solid var(--line-soft);
}
.needsrow:last-child { border-bottom: none; }
button.needsrow { cursor: pointer; }
button.needsrow:hover { background: var(--blue-tint); }
button.needsrow:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }

.needs__what {
  flex: 0 0 auto; font-family: var(--font-head); color: var(--navy);
}
.needsrow.is-alert .needs__what { color: var(--red); }
.needsrow.is-warn .needs__what { color: var(--amber); }
.needs__detail {
  flex: 1 1 auto; min-width: 0; color: var(--ink-mid); font-size: 12.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.needs__days {
  flex: 0 0 auto; font-size: 12px; color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

/* ---------- the banner ----------
   One sentence at a size that survives being screenshotted into a board pack.

   The executive summary used to open with one of these, naming the worst
   building in the portfolio. It was accurate and it was the wrong thing to
   meet first, so it is gone from that page. The class stays because the
   compliance page still uses it, and there the loud sentence is the point:
   "a surveyor may arrive any working day" is exactly what somebody opening
   that page needs before anything else. */
.exhead {
  display: flex; gap: 14px; align-items: flex-start;
  padding: 20px 22px; margin-bottom: 18px;
  border-radius: var(--r-lg); border: 1px solid var(--line);
  border-left: 4px solid var(--ink-soft); background: var(--surface);
}
.exhead.is-good  { border-left-color: var(--teal); background: var(--teal-tint); }
.exhead.is-act   { border-left-color: var(--red);  background: var(--red-tint); }
.exhead.is-quiet { border-left-color: var(--line); }
.exhead__line {
  font-family: var(--font-head); font-size: 21px; line-height: 1.35; color: var(--navy);
}
.exhead.is-act .exhead__line { color: #8E332B; }
.exhead.is-good .exhead__line { color: #1F6E60; }
.exhead__sub { margin-top: 6px; font-size: 14px; color: var(--ink-mid); }
.exhead.is-act .exhead__sub { color: #8E332B; }
.exhead.is-good .exhead__sub { color: #1F6E60; }

.exgrid { grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); margin-bottom: 16px; }

/* One building per row: name, shape, direction, whether work is open. Four
   columns is the most an executive reads across without a header. */
.exrow {
  display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  padding: 12px 18px; border: none; background: none; cursor: pointer;
  font-family: var(--font-body); border-bottom: 1px solid var(--line-soft);
}
.exrow:last-child { border-bottom: none; }
.exrow:hover { background: var(--blue-tint); }
.exrow:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }
.exrow.is-act { border-left: 3px solid var(--blue); }
.exrow__name {
  flex: 1 1 auto; min-width: 0; font-family: var(--font-head);
  font-size: 14.5px; color: var(--navy);
}
.exrow__where {
  display: block; font-family: var(--font-body); font-size: 11.5px;
  color: var(--ink-soft); margin-top: 1px;
}
/* Two lines on the left now, so the row hangs from the top rather than
   centring a tall cell against short ones. */
.exrow { align-items: flex-start; }
.exrow__move, .exrow__plan { padding-top: 1px; }
.exrow__move { flex: 0 0 220px; font-size: 12.5px; text-align: right; }
.exrow__plan { flex: 0 0 130px; text-align: right; font-size: 12.5px; }

.exmove.is-worse  { color: var(--red); }
.exmove.is-better { color: var(--teal-dark); }
.exmove.is-steady { color: var(--ink-soft); }
.exmove b { font-variant-numeric: tabular-nums; }

/* The verification record. Counts before examples, because the shape of the
   record is the claim and the examples are the proof of it. */
.exverdicts {
  display: grid; gap: 12px; margin-bottom: 14px;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
}
/* The band ladder is ten tiles rather than the usual three or four, and at
   the base minimum they squeezed to 120px and wrapped every label onto four
   lines. A wider rung reads in two rows of five instead. */
.exverdicts--ladder {
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
}

.extile {
  border: 1px solid var(--line-soft); border-radius: var(--r);
  padding: 12px 14px;
}
.extile .kpi__value { font-size: 28px; margin: 0 0 2px; }

.exrecent { border-top: 1px solid var(--line-soft); padding-top: 12px; }
.exverdict {
  display: flex; gap: 9px; align-items: flex-start;
  padding: 8px 0; font-size: 13px;
}
.exverdict.is-improved svg { color: var(--teal); }
.exverdict.is-worse svg { color: var(--red); }
.exverdict.is-unchanged svg, .exverdict.is-inconclusive svg { color: var(--ink-soft); }
.exverdict__job { color: var(--navy); }
.exverdict__num { font-size: 12px; color: var(--ink-mid); font-variant-numeric: tabular-nums; }

.exmoney { font-variant-numeric: tabular-nums; }
.exmoney__row {
  display: flex; justify-content: space-between; gap: 20px;
  padding: 7px 0; border-bottom: 1px solid var(--line-soft);
  font-size: 13.5px; color: var(--ink-mid);
}
.exmoney__row:last-child { border-bottom: none; }
.exmoney__row b { color: var(--navy); }
.exmoney__row.is-open { color: var(--blue); }
.exmoney__row.is-open b { color: var(--blue); }

/* ---------- money, grouped by where the work has got to ----------
   The group label does the work the old column of five rows could not: it
   says which question each figure answers. Rows carry a second line where the
   figure needs a qualifier — a count, or the fact that a number is an estimate
   AtmosIQ never invoiced and cannot vouch for. */
.exgroup { margin-bottom: 16px; }
.exgroup:last-child { margin-bottom: 0; }
.exgroup__label {
  font-size: 11px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--ink-soft); font-weight: 600;
  padding-bottom: 6px; border-bottom: 1px solid var(--line);
  margin-bottom: 2px;
}
.exgroup__foot {
  margin-top: 6px; font-size: 12px; color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
/* The row is now two lines on the left and a figure on the right, so it stops
   centring and hangs both from the top. */
.exmoney__row { align-items: flex-start; }
.exmoney__note {
  font-size: 11.5px; color: var(--ink-soft); line-height: 1.45;
  margin-top: 2px; max-width: 46ch;
}
.exmoney__row.is-open .exmoney__note { color: var(--ink-soft); }

.exnote {
  margin: 12px 0 0; font-size: 12.5px; color: var(--ink-soft);
  line-height: 1.6; max-width: 62ch;
}
.exnote.is-caution { color: var(--ink-mid); border-left: 2px solid var(--line); padding-left: 11px; }

.exout {
  margin-top: 14px; padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}
.exout .btn { color: var(--blue); border-color: var(--line); }
.exout .btn:hover { border-color: var(--blue); background: var(--blue-tint); }
.exout .btn svg { transform: rotate(-90deg); }
/* Two cards put the link in a body of its own to sit under a table; that
   body has no padding above it, so the rule would double up. */
.card__body + .card__body > .exout { margin-top: 0; border-top: none; padding-top: 0; }

.exlede {
  margin: 0 0 14px; font-size: 13.5px; color: var(--ink-mid);
  line-height: 1.55; max-width: 68ch;
}



/* ---------- the chain ----------

   Better outcomes on the left, complexity on the right — the same order the
   page runs in, so the bar and the scroll agree instead of asking the reader
   to hold two directions at once.

   One colour per stage, and it is used twice: on the pill up here and on the
   badge and rail of the section it names. That is what ties the promise to the
   evidence — before, a pill in a bar and a grey heading four screens down were
   two unrelated things. */
:root {
  --st-outcomes:   #35B18C;
  --st-confidence: #2699AE;
  --st-clarity:    #2F6FE0;
  --st-visibility: #3C5BBE;
  --st-complexity: #16233F;   /* the navy showing through the outlined pill */
}

.chain {
  display: flex; align-items: center; flex-wrap: nowrap;
  gap: 6px; padding: 18px 20px; margin-bottom: 22px;
  background: var(--navy); border-radius: var(--r-lg);
  overflow-x: auto; scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.28) transparent;
}
.chain::-webkit-scrollbar { height: 6px; }
.chain::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,.28); border-radius: 999px;
}
.chain::-webkit-scrollbar-track { background: none; }
.chain__step { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto; }

/* Bigger, rounder and set in the headline face, because this is the diagram
   the business is sold on rather than a row of filter chips. */
.chain__pill {
  display: inline-block; padding: 11px 22px; border-radius: 999px;
  font-family: var(--font-head); font-size: 15px; font-weight: 600;
  color: #fff; text-decoration: none; white-space: nowrap;
  border: 1.5px solid transparent;
  transition: box-shadow .14s ease, transform .14s ease;
}

/* The halo on the diagram, made to mean something: it marks the pill under the
   cursor or keyboard focus. The bar scrolls away with the page, so wiring it to
   scroll position would leave it invisible almost all of the time. */
.chain__pill:hover {
  box-shadow: 0 0 0 4px rgba(255,255,255,.22);
  transform: translateY(-1px);
}
.chain__pill:focus-visible {
  outline: none; box-shadow: 0 0 0 4px rgba(255,255,255,.55);
}
@media (prefers-reduced-motion: reduce) {
  .chain__pill { transition: none; }
  .chain__pill:hover { transform: none; }
}

.chain__pill.is-outcomes   { background: var(--st-outcomes); }
.chain__pill.is-confidence { background: var(--st-confidence); }
.chain__pill.is-clarity    { background: var(--st-clarity); }
.chain__pill.is-visibility { background: var(--st-visibility); }

/* Complexity is drawn as an outline rather than a fill. It is the raw end of
   the chain — the part the customer already has without AtmosIQ — so it is the
   one stage that is not yet anything, and an empty pill says that before a
   word is read. */
.chain__pill.is-complexity {
  background: none; border-color: rgba(255,255,255,.55);
}
.chain__pill.is-complexity:hover { border-color: rgba(255,255,255,.85); }

/* The arrows carry the finished end's colour, so the eye is pulled along the
   bar towards the outcome rather than stopping at each gap. */
.chain__arrow {
  color: var(--st-outcomes); font-size: 18px; line-height: 1;
  padding: 0 2px;
}

@media (max-width: 720px) {
  .chain { padding: 14px; }
  .chain__pill { padding: 9px 16px; font-size: 13.5px; }
}

/* ---------- a section, headed by its stage ----------

   The heading has to win against what follows it.

   A grey 19px line above a stack of white cards lost every time — the cards
   have borders, figures and colour, and the heading had none of it, so the
   page read as one long run of cards. It now sits on a tinted band in its
   stage's colour, numbered, with the same hue as the pill that jumps to it.

   The tint is deliberately pale. A saturated band would compete with the
   figures inside the section, which are the point; this only has to be
   unmissable, not loud. */
.exsection { margin-bottom: 34px; }

.exsection__head {
  display: flex; align-items: flex-start; gap: 13px;
  padding: 14px 18px; border-radius: var(--r-lg) var(--r-lg) 0 0;
  border-left: 4px solid var(--line);
  background: #F2F4F8;
}
.exsection__badge {
  flex: 0 0 auto; width: 28px; height: 28px; border-radius: 50%; margin-top: 1px;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-head); font-size: 14px; font-weight: 600;
  color: #fff; background: var(--line);
}
.exsection__title {
  font-family: var(--font-head); font-size: 20px; line-height: 1.2;
  color: var(--navy); letter-spacing: -.2px;
}
.exsection__sub {
  font-size: 15px; font-weight: 600; color: var(--ink-mid);
  margin-top: 3px; line-height: 1.4; max-width: 60ch;
}

/* The body hangs off the same rail, so the section is one block rather than a
   heading with loose cards under it. */
.exsection__body {
  padding: 16px 0 0 14px; border-left: 4px solid var(--line);
}

.exsection.is-outcomes   .exsection__head,
.exsection.is-outcomes   .exsection__body { border-left-color: var(--st-outcomes); }
.exsection.is-outcomes   .exsection__head { background: #E6F5F2; }
.exsection.is-outcomes   .exsection__badge { background: var(--st-outcomes); }

.exsection.is-confidence .exsection__head,
.exsection.is-confidence .exsection__body { border-left-color: var(--st-confidence); }
.exsection.is-confidence .exsection__head { background: var(--blue-tint); }
.exsection.is-confidence .exsection__badge { background: var(--st-confidence); }

.exsection.is-clarity    .exsection__head,
.exsection.is-clarity    .exsection__body { border-left-color: var(--st-clarity); }
.exsection.is-clarity    .exsection__head { background: #EAEEF9; }
.exsection.is-clarity    .exsection__badge { background: var(--st-clarity); }

.exsection.is-visibility .exsection__head,
.exsection.is-visibility .exsection__body { border-left-color: var(--st-visibility); }
.exsection.is-visibility .exsection__head { background: #ECEEF4; }
.exsection.is-visibility .exsection__badge { background: var(--st-visibility); }

.exsection.is-complexity .exsection__head,
.exsection.is-complexity .exsection__body { border-left-color: var(--st-complexity); }
.exsection.is-complexity .exsection__head { background: #EEF0F4; }
.exsection.is-complexity .exsection__badge { background: var(--st-complexity); }

/* ---------- compliance ---------- */
.exflags { margin-bottom: 14px; }
.exflag {
  display: flex; gap: 9px; align-items: flex-start;
  padding: 10px 12px; border-radius: var(--r); font-size: 13px; margin-bottom: 8px;
}
.exflag.is-act  { background: var(--red-tint); color: #8E332B; }
.exflag.is-note { background: var(--amber-tint); color: #7A5405; }

.exmodes { display: flex; gap: 18px; flex-wrap: wrap; margin-bottom: 14px; }
.exmode {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 13px; color: var(--ink-soft);
}
.exmode.is-on { color: var(--teal-dark); }
.exmode svg { flex: 0 0 auto; }

.exretain { font-variant-numeric: tabular-nums; }
.exretain__row {
  display: flex; justify-content: space-between; gap: 20px;
  padding: 6px 0; border-bottom: 1px solid var(--line-soft);
  font-size: 13px; color: var(--ink-mid);
}
.exretain__row:last-child { border-bottom: none; }
.exretain__row b { color: var(--navy); }

/* ---------- season or building ----------
   One bar, split. The proportion is the argument: most of a summer rise is
   summer, and the sliver that is not is the whole reason to spend money. */
.exclarity { margin-bottom: 16px; }
.exclarity:last-child { margin-bottom: 0; }
.exclarity__name { font-family: var(--font-head); font-size: 14.5px; color: var(--navy); }
.exclarity__bar {
  display: flex; height: 12px; border-radius: 999px; overflow: hidden;
  margin: 7px 0 5px; background: var(--line-soft);
}
.exclarity__season   { background: var(--ink-soft); }
.exclarity__building { background: var(--red); }
.exclarity__say { font-size: 12.5px; color: var(--ink-mid); }
.exclarity__say b { color: var(--red); }

/* ---------- a released finding ----------
   Severity, headline, where, and whether the customer has answered — the four
   things that decide whether to click it. A row rather than a card because
   five of them stacked as cards would out-shout the counts above them. */
.exfinding {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  width: 100%; text-align: left; padding: 9px 0; border: none; background: none;
  border-bottom: 1px solid var(--line-soft); cursor: pointer;
  font-family: var(--font-body);
}
.exfinding:last-child { border-bottom: none; }
.exfinding:hover .exfinding__title { text-decoration: underline; }
.exfinding:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.exfinding__title {
  flex: 1 1 240px; min-width: 0; font-size: 13.5px; color: var(--navy);
}
.exfinding__where { font-size: 11.5px; color: var(--ink-soft); }
.exfinding__state {
  flex: 0 0 auto; font-size: 11.5px; color: var(--blue); font-weight: 600;
}
.exfinding__state.is-done { color: var(--ink-soft); font-weight: 400; }

/* ---------- the recommendation log ----------
   Three lines per entry, in the order somebody reads them: what to do, why,
   and who signed for it. The action is set at reading size rather than as a
   list item — it is a sentence of advice, not a label, and the older single
   line treatment made a page of advice look like a page of problems. */
.exrec {
  display: block; width: 100%; text-align: left;
  padding: 13px 0; border: none; background: none; cursor: pointer;
  border-bottom: 1px solid var(--line-soft); font-family: var(--font-body);
}
.exrec:last-child { border-bottom: none; }
.exrec:hover .exrec__action { text-decoration: underline; }
.exrec:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

.exrec__top {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  margin-bottom: 7px;
}
.exrec__where { font-size: 11.5px; color: var(--ink-soft); }
.exrec__state {
  margin-left: auto; font-size: 11.5px; font-weight: 600; color: var(--blue);
}
.exrec__state.is-done { color: var(--ink-soft); font-weight: 400; }

.exrec__action {
  font-size: 14px; color: var(--navy); line-height: 1.55; max-width: 72ch;
}
/* The finding, demoted to the reason for the advice above it. Same words that
   used to be the headline here — the change is what they are now doing. */
.exrec__because {
  margin-top: 5px; font-size: 12.5px; color: var(--ink-mid);
  line-height: 1.5; max-width: 72ch;
}
.exrec__because b { color: var(--ink-soft); font-weight: 600; }
.exrec__by {
  margin-top: 5px; font-size: 11.5px; color: var(--ink-soft);
}

/* ---------- the issue log ----------
   A ledger row, so the age leads. It is the column the card exists for, it is
   the only one that is a number, and putting it first means the list can be
   read down the left margin without reading a word of it.

   The tone is carried on a left edge rather than in the text colour: eleven
   rows of coloured prose is a ransom note, and the edge survives a printout
   into a board pack where a subtle text tint does not. */
.exissue {
  display: flex; align-items: baseline; gap: 12px;
  width: 100%; text-align: left; padding: 9px 0 9px 11px;
  border: none; border-left: 3px solid var(--line); background: none;
  border-bottom: 1px solid var(--line-soft); cursor: pointer;
  font-family: var(--font-body);
}
.exissue:last-child { border-bottom: none; }
.exissue:hover { background: var(--blue-tint); }
.exissue:hover .exissue__title { text-decoration: underline; }
.exissue:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }

.exissue.is-open   { border-left-color: var(--blue); }
.exissue.is-active { border-left-color: var(--amber); }
.exissue.is-good   { border-left-color: var(--teal); }
.exissue.is-bad    { border-left-color: var(--red); }
.exissue.is-muted  { border-left-color: var(--line); }

.exissue__age {
  flex: 0 0 44px; font-size: 12.5px; color: var(--ink-mid);
  font-variant-numeric: tabular-nums; text-align: right;
}
.exissue__what { flex: 1 1 auto; min-width: 0; }
.exissue__title { display: block; font-size: 13.5px; color: var(--navy); }
.exissue__where { display: block; font-size: 11.5px; color: var(--ink-soft); }
.exissue__stage {
  flex: 0 0 auto; max-width: 34%; text-align: right;
  font-size: 11.5px; font-weight: 600; color: var(--ink-mid);
}
.exissue.is-open   .exissue__stage { color: var(--blue); }
.exissue.is-active .exissue__stage { color: var(--amber); }
.exissue.is-good   .exissue__stage { color: var(--teal-dark); }
.exissue.is-bad    .exissue__stage { color: var(--red); }
.exissue.is-muted  .exissue__stage { color: var(--ink-soft); font-weight: 400; }
/* Closed rows stay legible and stop competing — they are the record, not the
   work. */
.exissue.is-muted .exissue__title,
.exissue.is-good  .exissue__title { color: var(--ink-mid); }

/* ---------- compliance & surveys ----------
   The readiness rows reuse the issue-log row, because they are the same
   object: a thing with a state, a sentence explaining it, and somewhere to go.
   Two differences — the detail wraps rather than being a short location, and
   a check that is only a statement of fact is not a button. */
.exissue > .pill { flex: 0 0 auto; align-self: center; }
.exissue__what .exissue__where { white-space: normal; line-height: 1.5; margin-top: 2px; }
div.exissue { cursor: default; }
div.exissue:hover { background: none; }

/* On the survey page the checks stand alone rather than under a tally, so they
   get a little more room than they do inside the executive summary. */
.exsection__body > .exhead:first-child { margin-top: 0; }

/* The one date the customer controls, when it is close enough to matter. */
.exretain__row.is-loud b { color: var(--blue); }
.exretain__row.is-loud {
  background: var(--blue-tint); border-radius: var(--r-sm);
  padding: 8px 10px; margin: 2px -10px; border-bottom-color: transparent;
}

/* The badge on the survey page carries an icon rather than a step number —
   these are not stages of anything, they are separate programmes. */
.exsection__badge svg { display: block; }

/* The sequence a job has not started. Quieter than a live rail — it is a
   description of what will happen rather than a record of what has, and it
   sits under every step of an unaccepted plan, so at full strength it would
   shout the same sentence four times. */
.track--ahead { opacity: .72; background: var(--surface); }
.track--ahead .track__say { color: var(--ink-soft); }

/* ---- who is coming ---------------------------------------------------------
   Sits inside the tracker, under the rail, because it is what the 'Scheduled'
   node means. Bordered rather than tinted so it reads as a panel of facts
   about a day rather than as another status banner. */
.visit {
  margin-top: 11px; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--surface); font-size: 13px;
}
.visit.is-booked  { border-color: #BFE3DA; background: var(--teal-tint); }
.visit.is-waiting { border-color: #EBD3A0; background: var(--amber-tint); }
.visit.is-clash   { border-color: #EEBAB4; background: var(--red-tint); }
.visit.is-none    { border-style: dashed; }
.visit.is-off     { opacity: .65; }

.visit__head { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.visit__state {
  font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase;
  font-weight: 600; color: var(--ink-mid);
}
.visit.is-booked .visit__state { color: var(--teal-dark); }
.visit.is-waiting .visit__state { color: var(--amber); }
.visit.is-clash .visit__state { color: var(--red); }
/* How the commitment reached us. Quiet, but never merged with the commitment
   itself: a contractor's own word and a note of a call are different claims. */
.visit__via { font-size: 11.5px; color: var(--ink-soft); font-style: italic; }

.visit__say { margin-top: 5px; line-height: 1.55; color: var(--ink); }

/* The facts a desk needs, in the order it needs them. A definition list
   because that is what this is, and because the labels have to line up when
   somebody is reading them out over a phone. */
.visit__facts {
  display: grid; grid-template-columns: auto 1fr; gap: 4px 12px;
  margin: 10px 0 0; font-size: 12.5px;
}
.visit__facts dt {
  color: var(--ink-soft); font-size: 11px; text-transform: uppercase;
  letter-spacing: .05em; padding-top: 2px; white-space: nowrap;
}
.visit__facts dd { margin: 0; color: var(--ink); line-height: 1.5; }

.visit__told {
  display: flex; align-items: center; gap: 6px;
  margin-top: 10px; padding-top: 9px;
  border-top: 1px solid rgba(22,35,63,.08);
  font-size: 12px; color: var(--ink-mid);
}
.visit__acts { margin-top: 11px; display: flex; flex-direction: column; gap: 8px; }
.visit__link { font-size: 12px; }
.visit__link input { width: 100%; }
.visit__record, .visit__clashbox {
  margin-top: 9px; padding: 11px 12px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r);
}

/* ---------- the printed summary ---------------------------------------------
 *
 * An executive forwards this, and forwarding means a PDF in a board pack —
 * read on a laptop, sometimes printed on a colour machine, sitting in a folder
 * next to pages from three other vendors.
 *
 * This used to print MONO on the theory that board packs are photocopied. That
 * was true of board packs a decade ago. It is not true of a PDF somebody opens
 * on an iPad, and the cost of the assumption was high: every distinction the
 * page makes with colour — severity, stage, better versus worse — arrived on
 * paper as identical grey boxes, which is exactly the "stale" look. A summary
 * whose whole argument is a chain of five stages printed as five outlines.
 *
 * So: colour stays, deliberately, and print-color-adjust makes browsers honour
 * it instead of helpfully dropping the backgrounds. What goes is everything
 * that only makes sense on a screen — navigation, buttons, and the "open the
 * remedy plans" links at the foot of every card, which on paper are a promise
 * nobody can keep.
 */
/* ---------- a phone ----------------------------------------------------
   The shell is a 248px navy rail beside the page. Right on a laptop, and on a
   phone it left the content 300px wide inside a 550px page that scrolled
   sideways — which is what somebody opening the link from an email sees
   first. Below 760px the rail becomes a strip of tabs across the top and
   everything underneath gets the whole width.

   Both dashboards are laid out by this file, so this fixes the staff tool as
   well as the customer's.

   A TABLE CANNOT REFLOW, and two of them are deliberately wider than a phone
   (the plan table sets min-width: 880px, because a job, its trade, its
   contractor and three money columns do not fit any narrower and stacking
   them loses the comparison). Those scroll inside their own box instead of
   widening the page — the rule the rest of the project follows for anything
   wide. */
@media (max-width: 760px) {
  .shell { display: block; }

  .sidebar {
    width: 100%; flex: none; position: static; height: auto;
  }
  .sidebar__brand { padding: 14px 16px 11px; }
  .sidebar__org { padding: 10px 16px 6px; }
  .sidebar__foot { display: none; }

  /* The nav runs across rather than down, and scrolls if it has to. */
  .nav {
    display: flex; align-items: center; gap: 6px;
    padding: 8px 12px 10px; overflow-x: auto; overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
  }
  .nav__item { width: auto; flex: 0 0 auto; white-space: nowrap; margin-bottom: 0; }
  .nav__sub { padding-left: 12px; }
  /* "The work", "The estate" — section headings make no sense in a row. */
  .nav__label { display: none; }

  .topbar { padding: 12px 16px; gap: 10px; }
  .content { padding: 18px 16px 44px; }

  .pagehead { flex-direction: column; gap: 12px; }
  .pagehead h1 { font-size: 22px; }
  .pagehead__right { width: 100%; }

  /* Columns with a fixed minimum are wider than the phone on their own: the
     executive summary asks for 400px a column and the staff board 340px. One
     column each below this width. */
  .exgrid, .board__grid, .grid--units { grid-template-columns: 1fr; }

  /* A flex or grid child refuses to shrink below its content by default, so
     one wide table dragged its card — and the whole page — out with it. */
  .stack, .stack > *, .grid > *, .card, .card__body { min-width: 0; }

  .tblwrap, .card__body.tight { overflow-x: auto; }
  table.tbl { display: block; width: 100%; overflow-x: auto; }
}
/* The tally, grouped by the question each tap answers. A block per question
   with its own bars: the lists differ per question, so one flat chart would
   add "all the time" from the odour question to "all the time" from the
   temperature one and report a number about nothing. */
.offgroups { display: grid; gap: 18px; }
.offgroup__q {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  font-size: 13px; color: var(--navy); margin-bottom: 9px;
  padding-bottom: 6px; border-bottom: 1px solid var(--line-soft);
}
.offgroup__q b { color: var(--ink-soft); font-weight: 600; font-size: 12px; white-space: nowrap; }

/* "What is off?" — the tally of taps under a low score. Bars rather than a
   table: there are at most seven rows and the shape is the whole point. */
.offbars { display: grid; gap: 9px; }
.offbar__label {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 13px; color: var(--ink-mid); margin-bottom: 4px;
}
.offbar__label b { color: var(--navy); font-variant-numeric: tabular-nums; }
.offbar__track { height: 8px; border-radius: 999px; background: var(--line-soft); overflow: hidden; }
.offbar__fill { height: 100%; border-radius: 999px; background: var(--blue); display: block; }
.offbar__fill.is-most { background: var(--navy); }

/* ---------- tasks ----------
   A task is read as a block rather than a table row: what it is, where, where
   it has got to, and -- the part that matters -- what the reading did after
   the work. That last line is the product, so it gets a tinted panel of its
   own rather than another column somebody has to scan for. */
.tasklist { display: grid; gap: 12px; }
.task {
  border: 1px solid var(--line); border-radius: var(--r);
  padding: 13px 15px; background: var(--surface);
}
.task__head { display: flex; align-items: flex-start; gap: 12px; }
.task__title { font-family: var(--font-head); font-size: 15px; color: var(--navy); }
.task__where { font-size: 12.5px; color: var(--ink-mid); margin-top: 2px; }
.task__detail { font-size: 13px; color: var(--ink-mid); margin-top: 8px; max-width: 80ch; }
.task__meta {
  display: flex; gap: 14px; flex-wrap: wrap; margin-top: 9px;
  font-size: 12.5px; color: var(--ink-soft);
}
.task__meta .is-overdue { color: var(--red); font-weight: 600; }

/* The three things the verification can say, and they are not the same thing
   in three colours: it recovered, it did not, or nothing was fitted to tell
   us. The third is quiet on purpose -- an admitted gap should not shout. */
.task__verdict {
  margin-top: 10px; padding: 9px 12px; border-radius: var(--r);
  font-size: 12.5px; line-height: 1.55;
  display: flex; gap: 8px; align-items: flex-start;
}
.task__verdict.is-good { background: var(--teal-tint); color: #1F6E60; }
.task__verdict.is-bad  { background: var(--red-tint);  color: #8E332B; }
.task__verdict.is-quiet { background: #EEF1F6; color: var(--ink-mid); }

.task__notes { margin-top: 10px; border-top: 1px solid var(--line-soft); padding-top: 8px; }
.task__note { font-size: 12.5px; color: var(--ink-mid); margin-bottom: 4px; }
.task__controls {
  display: flex; gap: 9px; align-items: center; flex-wrap: wrap;
  margin-top: 11px; padding-top: 10px; border-top: 1px solid var(--line-soft);
}
.task__status { font-size: 12.5px; color: var(--ink-mid); }



/* THEIR SENTENCE, at the top of their summary — review item 2.8.
 *
 * Deliberately quiet. This is the one thing on the page AtmosIQ did not
 * produce, and dressing it up as a hero banner would turn somebody's own
 * words into a marketing device. A rule, a serif quote and an attribution.
 *
 * It prints: a board pack that opens on the executive's own sentence is the
 * single most useful line on the page, and the print block at the end of this
 * file keeps colour for exactly this kind of distinction. */
.exlede {
  margin: 0 0 26px;
  padding: 18px 20px 16px;
  border-left: 4px solid var(--teal);
  background: linear-gradient(90deg, rgba(47,168,148,0.06), transparent 70%);
  border-radius: 0 var(--r-lg) var(--r-lg) 0;
}

.exlede__quote {
  margin: 0; font-family: Cambria, Georgia, serif;
  font-size: 19px; line-height: 1.45; color: var(--ink);
  display: block;
}

.exlede__by {
  display: block; margin-top: 8px;
  font-family: Calibri, system-ui, sans-serif;
  font-size: 13px; font-style: normal; color: var(--ink-soft);
}

.exlede__outcomes {
  margin-top: 14px; padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}

.exlede__label {
  display: block; font-size: 11.5px; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-soft); margin-bottom: 6px;
}

/* Numbered, because the ORDER is the finding: the first number their regional
   asks about is not the same as the third. */
.exlede__list {
  margin: 0 0 8px; padding-left: 20px;
  display: flex; gap: 22px; flex-wrap: wrap;
  list-style: decimal;
}
.exlede__list li { font-weight: 600; font-size: 14.5px; }

.exlede__note { display: block; font-size: 12.5px; color: var(--ink-soft); }

/* The zone they named. One accent, not a second card style. */
.card--pinned { border-left: 3px solid var(--teal); }

/* THE ODOUR INVESTIGATION FLOW (review item 2.7).
 *
 * Numbered steps because the order is the point: this is modelled on what a
 * surveyor asks under F923, and a record made in the order the questions were
 * asked is one a reader can follow without reconstructing it. Quiet styling —
 * it sits inside a task row and must not read as a second page. */
.odorflow {
  margin-top: 12px; padding: 14px 16px;
  border: 1px solid var(--line-soft); border-radius: var(--r);
  background: #FAFBFD;
}

.odorstep { display: flex; gap: 12px; padding: 11px 0; }
.odorstep + .odorstep { border-top: 1px solid var(--line-soft); }

.odorstep__head {
  display: flex; gap: 9px; align-items: baseline;
  flex: 0 0 210px; max-width: 210px;
}
.odorstep__n {
  flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%;
  background: var(--teal); color: #fff; font-size: 11.5px; font-weight: 600;
  display: inline-flex; align-items: center; justify-content: center;
}
.odorstep__title { font-weight: 600; font-size: 13.5px; }
.odorstep__body { flex: 1 1 auto; min-width: 0; }

/* Stacks on a phone, which is where somebody standing in the corridor is. */
@media (max-width: 720px) {
  .odorstep { flex-direction: column; gap: 7px; }
  .odorstep__head { flex: 1 1 auto; max-width: none; }
}

@media print {
  /* Chrome and Safari strip backgrounds when printing unless told not to.
     Every tint on this page carries meaning, so every one of them is told. */
  *, *::before, *::after {
    -webkit-print-color-adjust: exact !important;
    print-color-adjust: exact !important;
  }

  @page { margin: 13mm 12mm; }

  /* Screen furniture. The card links go too: an underlined "Open the remedy
     plans" on paper is a control that does nothing. */
  .sidebar, .topbar, .pagehead__right, .needs, .exout, .stalebar,
  .chain__arrow { display: none !important; }

  .shell { display: block; }
  .content { padding: 0; max-width: none; }
  body { background: #fff; font-size: 11.5px; }

  /* ---- the cover band --------------------------------------------------
     Hidden on screen, because the sidebar and the top bar already say all of
     it and a second masthead would be noise. */
  .printhead { display: block !important; margin: 0 0 18px; }
  .printhead__bar {
    display: flex; align-items: center; gap: 12px;
    background: var(--navy); color: #fff;
    padding: 10px 16px; border-radius: 8px 8px 0 0;
  }
  .printhead__brand { display: flex; align-items: center; gap: 8px; }
  .printhead__mark {
    width: 22px; height: 22px; border-radius: 6px;
    background: var(--teal); color: #fff;
    font-family: var(--font-head); font-size: 13px;
    display: flex; align-items: center; justify-content: center;
  }
  .printhead__name { font-family: var(--font-head); font-size: 15px; }
  .printhead__kind {
    margin-left: auto; font-size: 10px; letter-spacing: .14em;
    text-transform: uppercase; color: rgba(255,255,255,.72);
  }
  .printhead__who {
    border: 1px solid var(--line); border-top: none;
    border-radius: 0 0 8px 8px; padding: 14px 16px 15px;
  }
  .printhead__customer {
    font-family: var(--font-head); font-size: 21px; color: var(--navy);
    line-height: 1.2;
  }
  .printhead__when {
    margin-top: 5px; font-size: 11px; color: var(--ink-mid);
    display: flex; gap: 7px; flex-wrap: wrap;
  }
  .printhead__dot { color: var(--ink-soft); }

  /* Redundant under a masthead that says the same thing — and ONLY there.
     Compliance and Costs print too, have no masthead, and would otherwise
     come out with no heading at all. The adjacency is the test: the masthead
     is inserted immediately before the page head it replaces. */
  .printhead + .pagehead { display: none !important; }
  /* Elsewhere it keeps the title and loses only the buttons. */
  .pagehead { margin-bottom: 14px; }
  .pagehead h1 { font-size: 20px; }
  .pagehead p { font-size: 11.5px; max-width: 78ch; }

  /* ---- the chain -------------------------------------------------------
     The diagram the business is sold on. It printed as five grey outlines,
     which is five ways of saying nothing. The arrows go because a wrapped
     row of pills with arrows pointing at line ends reads worse than the
     sequence alone. */
  /* The navy stays. Stripped to outlines it was five grey capsules; on a
     colour PDF it is the strongest thing on the page and the only part of the
     summary that states the whole argument at a glance. It wraps rather than
     scrolls, because paper does not scroll. */
  .chain {
    padding: 13px 15px; margin-bottom: 16px;
    flex-wrap: wrap; overflow: visible; gap: 6px;
    break-inside: avoid;
  }
  .chain__pill {
    padding: 7px 14px; font-size: 11.5px; box-shadow: none !important;
    color: #fff !important;
  }

  /* ---- sections --------------------------------------------------------
     Each stage keeps its colour, on the badge and as a rule under its head,
     so a reader flipping pages can tell where they are without reading. */
  .exsection { break-inside: auto; margin-bottom: 16px; }
  .exsection__head {
    break-after: avoid; break-inside: avoid;
    padding-bottom: 8px; margin-bottom: 10px;
  }
  .exsection__title { font-size: 15px; }
  .exsection__sub { font-size: 11px; }

  /* A card is the unit that must not split — a figure on one page and its
     caption on the next is worse than a short page. */
  .card, .extile, .exverdicts, .excard { break-inside: avoid; }
  .card { box-shadow: none; border-color: #D8DEE9; }
  .card__head { padding: 11px 14px; }
  .card__head h3 { font-size: 13px; }
  .card__body { padding: 14px; }
  table.tbl th, table.tbl td { padding: 7px 14px; }
  table.tbl { font-size: 11px; }

  /* Two columns rather than four: at print width four tiles of figures are
     four columns of about nine characters. */
  .exgrid { grid-template-columns: 1fr 1fr; gap: 12px; }
  .exverdicts { gap: 10px; }
  .kpi__value, .extile .kpi__value { font-size: 22px; }

  /* Nothing on paper is clickable, so nothing should look it. */
  .exrow, .exrec, .unitcard, tr.clickable, tr.tbl__go { cursor: default; }
  .exrow, .exrec { break-inside: avoid; }
  button { box-shadow: none !important; }

  /* The rails keep their state colours — six identical circles is exactly the
     thing the rail was drawn to replace. */
  .track { break-inside: avoid; background: none; }
  .track__rail { overflow: visible; flex-wrap: wrap; }

  /* ---- paper has no controls -------------------------------------------
     Every disclosure prints OPEN.

     A <details> survives printing in the sense that it does not break — it
     prints exactly as it sits on screen, which for a closed one is a line of
     blue text with a chevron and none of the argument under it. On a board
     pack that is the worst of both: the reader is told there is a reason and
     shown a control that does nothing.

     So the content is forced visible and the chevron goes. The summary line
     stays as a small heading, because "Why we are saying this" is a fair
     label for the paragraphs beneath it once they are there. */
  /* The OPENING is done in JavaScript — see openDisclosuresForPrint() in
     js/ui.js. It has to be: engines hide a closed disclosure's subtree with
     content-visibility, and overriding that from here leaves the box laid out
     with its text still unrendered, which measures as a fix and prints as a
     blank. These two lines stay as the floor for an engine that hides the old
     way and never fires beforeprint. */
  details > summary ~ * {
    display: block !important;
    content-visibility: visible !important;
  }
  details > summary { list-style: none; }
  details > summary svg { display: none !important; }
  .rec__whysum {
    color: var(--ink-soft) !important;
    font-size: 10px !important; letter-spacing: .07em; text-transform: uppercase;
  }
  /* On screen the label sits at the right end of the money row and floats so
     the opened detail can reclaim the width under it. On paper it is a
     heading for the paragraphs below it, and a heading belongs at the left. */
  .rec__foot > details[open] > .rec__whysum { float: none; }
  .rec__foot > details[open] { margin-top: 4px; }
  .rec__detail {
    background: #FBFCFD !important; border-left-color: #D8DEE9 !important;
    break-inside: avoid;
  }
  /* The estimate's own sections are already a block each; keep a job's case
     with the job it belongs to. */
  .step__brief, .plan__brief, .rec__why { break-inside: avoid; }

  /* ---- the pack ---------------------------------------------------------
     Everything waiting on a decision, in full, after the summary. Starts its
     own page: the summary is a thing somebody reads at a desk in two minutes
     and this is what they take into the meeting. */
  .printpack { display: block !important; break-before: page; }
  .printpack__head { margin-bottom: 14px; }
  .printpack__head h2 {
    font-size: 17px; color: var(--navy);
    padding-bottom: 7px; border-bottom: 2px solid var(--navy);
  }
  .printpack__head p {
    margin: 8px 0 0; font-size: 11px; color: var(--ink-mid); max-width: 78ch;
  }
  .printpack__part { margin-bottom: 18px; break-inside: auto; }
  .printpack__part h3 {
    font-size: 13.5px; color: var(--navy); margin-bottom: 3px;
    break-after: avoid;
  }
  .printpack__where { margin: 0 0 8px; font-size: 11px; color: var(--ink-soft); }
  .printpack__jobs { margin: 10px 0 0; padding-left: 1.3em; }
  .printpack__jobs > li { margin-bottom: 12px; break-inside: avoid; }
  .printpack__job { display: flex; gap: 9px; align-items: baseline; }
  .printpack__job b { color: var(--navy); font-size: 12px; }
  .printpack__trade {
    font-size: 9.5px; letter-spacing: .06em; text-transform: uppercase;
    color: var(--ink-soft);
  }
  .printpack__why {
    margin: 4px 0 0; font-size: 11px; color: var(--ink-mid);
    border-left: 2px solid var(--line); padding-left: 9px; max-width: 76ch;
  }

  /* And the last page should not end on a widow. */
  p, li { orphans: 3; widows: 3; }
}

/* Print-only, so they take no room on screen. The board pack is two tabs the
   reader can simply open while they are at the screen; it exists because
   paper cannot be clicked. */
.printhead, .printpack { display: none; }


/* ---------- answering a finding (review 2.2) ----------
   A released recommendation said "no answer yet" and offered nothing to
   answer it with. This is the block that does, at the bottom of the card
   where a decision belongs: after what it is, what to do, what it costs and
   why we are saying it. */
.recans {
  margin-top: 14px; padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}
.recans__lead {
  font-size: 12.5px; color: var(--ink-mid); margin-bottom: 10px;
}
.recans__row { display: flex; gap: 8px; flex-wrap: wrap; }

/* The four answers carry equal weight on purpose. Making "Add to a remedy
   plan" the primary button would be AtmosIQ putting a thumb on a scale it
   also profits from — the fee disclosure exists for the same reason. */
.recans__btn { white-space: nowrap; }

.recans__why { margin-top: 10px; }
.recans__reason {
  width: 100%; max-width: 520px; padding: 9px 11px;
  border: 1px solid var(--line); border-radius: 7px;
  font-family: var(--font-body); font-size: 13px; line-height: 1.5;
  color: var(--navy); background: var(--surface); resize: vertical;
}
.recans__reason:focus {
  outline: none; border-color: var(--blue);
  box-shadow: 0 0 0 3px var(--blue-tint);
}
.recans__act { margin-top: 8px; }

.recans__status { margin-top: 8px; font-size: 12.5px; color: var(--ink-soft); }
.recans__status.is-bad { color: var(--red); }
.recans__status:empty { display: none; }

/* Answered: the controls are gone, because the question has been settled and
   leaving them would invite somebody to answer it twice. */
.recans.is-answered {
  display: flex; gap: 10px; align-items: flex-start;
  color: var(--teal-dark);
}
.recans.is-answered b { color: var(--navy); font-size: 13px; }
.recans__meta { font-size: 12px; color: var(--ink-soft); }
.recans.is-answered .recans__reason {
  all: unset; display: block; margin-top: 5px;
  font-size: 12.5px; color: var(--ink-mid); line-height: 1.5; max-width: 62ch;
}

/* A viewer sees the finding and not the buttons. Absent rather than disabled:
   a control that refuses is a worse answer than no control. */
.recans.is-readonly { font-size: 12.5px; color: var(--ink-soft); }

@media (max-width: 640px) {
  .recans__row { flex-direction: column; align-items: stretch; }
  .recans__btn { white-space: normal; }
}

/* ---------- a price somebody may not approve (review 2.3) ----------
   The control is replaced, not disabled. A button that refuses teaches
   somebody the product is broken; a sentence naming who can teaches them what
   to do next. */
.track__needs {
  display: flex; gap: 8px; align-items: center;
  font-size: 13px; color: var(--navy); font-family: var(--font-head);
}
.track__needs svg { flex: 0 0 auto; color: var(--ink-soft); }
