/* ─────────────────────────────────────────────────────────
   building-health — Runwise's building status component,
   rebuilt, and the rail that walks through its thirty-six
   versions.

   Same two-namespace split savings-card.css uses, for the
   same reason:

     .bh-*  is the PRODUCT. Every value came off the Figma
            frames, read through get_design_context rather
            than sampled from a screenshot. Where a number
            here looks arbitrary — 36px squares, a 0.5px
            edge, #BCEBA4 — it is arbitrary in the file too.

     .bj-*  is the SITE talking about the product. It uses
            style.css's tokens — --ink, --rule, --u — so the
            frame around the component reads as the same page
            as the prose above it.

   This case study is an argument about colour and position.
   If the chrome around the frames drifted red, a reader
   could no longer tell what the product looked like from
   what I chose to put around it.

   No inline styles anywhere, per _headers: geometry rides on
   SVG attributes and on properties written through
   element.style, which is a CSSOM write and not what
   style-src blocks.
   ───────────────────────────────────────────────────────── */

/* ── Codec Warm ───────────────────────────────────────────
   Runwise's face. The two subset faces already ship for
   /savings-card; this page reuses them rather than adding a
   second copy, and the licence note in savings-card.css
   applies here unchanged — if it turns out not to cover web
   distribution, delete the two @font-face blocks there and
   the preloads in both pages, and the Inter fallback below
   takes over.

   font-display: block, not swap. Codec Warm Bold is
   usWeightClass 500, so asking an installed copy of the
   family for 700 gets HEAVY; declaring the file at 700 pins
   it to the weight the Figma text styles name. */
@font-face {
  font-family: "Codec Warm";
  src: url("/figures/assets/codec-warm-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}
@font-face {
  font-family: "Codec Warm";
  src: url("/figures/assets/codec-warm-bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: block;
}

/* ── The product's tokens ─────────────────────────────────
   Names are Runwise's own (Grey/900, Red/600) so a value
   here can be checked against the file. */
.bh {
  --bh-ink: #191918; /* Grey/900 */
  --bh-ink-2: #5b5a59; /* Grey/700 */
  --bh-mute: #8e8d8c; /* Grey/500 */
  --bh-mute-2: #c9c9c8; /* Grey/300 */
  --bh-ghost: #e3e3e2; /* Grey/200 */
  --bh-hair: #f0f0f0; /* Grey/100 */
  --bh-surface: #fafafa; /* Grey/050 */

  --bh-red: #ff6262; /* Red/600 */
  --bh-red-lt: #ff8181; /* the top of the red square gradient */
  --bh-red-deep: #e83232; /* Red/700 */
  --bh-red-edge: #ce1717;
  --bh-red-100: #ffefef;
  --bh-red-200: #ffe0e0;
  --bh-red-300: #ffc0c0;

  --bh-green: #07ad00; /* Green/700 */
  --bh-green-100: #effae9;
  --bh-green-200: #dcfbcc;
  --bh-green-edge: #bceba4;

  /* Purple was the third signal colour for eleven frames,
     then it was cut. Kept here because those eleven are only
     legible as the argument against it. */
  --bh-purple: #6b41e3; /* Purple/600 */
  --bh-purple-100: #f0ecfc;
  --bh-purple-200: #cec1f5;
  --bh-purple-edge: #c4b3f4;

  /* Shadow/Base, both layers, and Shadow/SM. */
  --bh-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.1);
  --bh-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  /* Every square carries this one. It is a half-pixel of lift
     and it is in the file. */
  --bh-emboss: 0 1px 1.5px rgba(27, 37, 80, 0.14);

  font-family:
    "Codec Warm",
    Inter,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    sans-serif;
  color: var(--bh-ink);
  font-size: 16px;
  line-height: 24px;
  -webkit-font-smoothing: antialiased;
  text-align: left;
}

.bh *,
.bh *::before,
.bh *::after {
  box-sizing: border-box;
}
.bh p {
  margin: 0;
}

/* The one frame laid out by coordinate rather than flexbox. */
.bh-abs {
  position: relative;
  height: 218px;
  background: #fff;
}

/* Every frame is drawn at its Figma width and scaled to fit
   the space it is given. Scaling the whole frame rather than
   re-laying it out small keeps every proportion exactly as
   it is in the file, which is the only thing a recreation is
   for. --bh-w is the native width, --bh-k the scale; both are
   written from JS after measuring. */
.bh-fit {
  position: relative;
  width: 100%;
  overflow: hidden;
}
.bh-fit > .bh {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--bh-w, 1120px);
  transform: scale(var(--bh-k, 1));
  transform-origin: top left;
}
/* Below the legibility floor the frame scrolls rather than
   shrinking further. The pad is a zero-height block at the
   scaled width — it is what gives the box something to
   scroll, since the frame above it is absolutely positioned
   and scaled by a transform. */
.bh-fit-pad {
  height: 0;
  pointer-events: none;
}
.bh-fit--scroll {
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
}

@media (max-width: 45rem) {
  .figure-stage:has(.bh-fit--scroll)::before {
    color: var(--ink-50, var(--muted));
    content: "Swipe to see the full diagram \2192";
    display: block;
    font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
    font-size: 0.75rem;
    line-height: 1.4;
    margin-block-end: 1rem;
    width: max-content;
  }
}

/* ── Icons ────────────────────────────────────────────────
   THE ONE PLACE THIS RECREATION IS NOT 1:1.

   The frames set their icons in Font Awesome 6 PRO as live
   text — triangle-exclamation, check, chevron-right,
   square-arrow-up-right, circle-check, and the duotone
   buildings / wifi / list. Several are Pro-only. Runwise
   holds that licence; this domain does not, and shipping the
   outlines here would be redistribution.

   So the glyphs are drawn by hand to the same box, the same
   optical weight, and the same silhouette. They are the only
   marks on this page that did not come out of the file.

   currentColor throughout, so a glyph inherits the colour of
   whatever it sits in and the red/green pairing is set once
   at the card level rather than per icon. */
.bh-i {
  display: block;
  flex: none;
  fill: currentColor;
  width: 16px;
  height: 16px;
}

/* ── Rows and cards ───────────────────────────────────────
   Three row gaps, and which one a frame uses is a real part
   of its identity: 20 for the six earliest, 16 for the long
   middle, 12 for the three that put the count on the right. */
.bh-row {
  display: flex;
  align-items: stretch;
  width: 100%;
}
.bh-row--20 {
  gap: 20px;
}
.bh-row--16 {
  gap: 16px;
}
.bh-row--12 {
  gap: 12px;
  align-items: flex-start;
}
.bh-row--top {
  align-items: flex-start;
}
/* Version 14 pairs one tall card with two short ones, and the
   file centres the short pair against it rather than hanging
   them from the top. Worth its own class: at flex-start the
   two short cards sat 48 units high. */
.bh-row--mid {
  align-items: center;
}

.bh-card {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px;
  background: #fff;
  border-radius: 20px;
  box-shadow: var(--bh-shadow);
}
.bh-card--row {
  flex-direction: row;
  align-items: center;
  gap: 12px;
}
/* After --row deliberately. Both are single-class selectors, so source
   order is what decides, and version 6's third card wants 8. */
.bh-card--g8 {
  gap: 8px;
}
.bh-card--g20 {
  gap: 20px;
}
.bh-card--col20 {
  flex-direction: column;
  justify-content: center;
  gap: 20px;
}
/* Versions 10 and 22: the whole card red. It reads as one
   object rather than a card with a warning in it, which is
   exactly what killed it. */
.bh-card--alarm {
  background: var(--bh-red);
  color: #fff;
}
.bh-card--edge {
  box-shadow: inset 0 0 0 2px var(--bh-red);
}
.bh-card--alarm .bh-title,
.bh-card--alarm .bh-num,
.bh-card--alarm .bh-sub,
.bh-card--alarm .bh-eyebrow {
  color: #fff;
}

/* ── Type ─────────────────────────────────────────────────
   Seven styles, all Codec Warm, all straight out of the
   Figma text styles. The names in the comments are
   Runwise's. */
.bh-num {
  /* Text-3XL/400 */
  font-size: 32px;
  line-height: 38px;
  font-weight: 400;
  white-space: nowrap;
}
.bh-num--bold {
  /* Text-3XL/500 — the count went bold once it was the
     loudest thing left in the quiet strip. */
  font-weight: 700;
}
.bh-num--zero {
  /* A zero is good news, so it is Grey/200 and reads as
     absence rather than as a number to parse. */
  color: var(--bh-ghost);
}
.bh-num--dim {
  color: var(--bh-mute);
}
.bh-title {
  /* Text-Base/500 */
  font-size: 16px;
  line-height: 24px;
  font-weight: 700;
}
.bh-title--dim {
  color: var(--bh-mute);
}
.bh-sub {
  /* Text-S/400 */
  font-size: 14px;
  line-height: 20px;
  font-weight: 400;
  color: var(--bh-mute);
}
.bh-sub--alarm {
  color: var(--bh-red);
}
.bh-val {
  /* Text-Base/400 — the detail line once the category was
     demoted to a caption and the alert came up to body size. */
  font-size: 16px;
  line-height: 24px;
  font-weight: 400;
  color: var(--bh-ink);
  min-width: 100%;
}
.bh-val--mute {
  color: var(--bh-mute);
}
.bh-val--bad {
  color: var(--bh-red);
}
.bh-val--clip {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Version 15. Text-Base/500 allowed to wrap, with the
   hairline text-shadow the frame carries. It looks like a
   headline because it is three lines long, not because it is
   set any larger than the line above it. */
.bh-wrap {
  font-size: 16px;
  line-height: 24px;
  font-weight: 700;
  min-width: 100%;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
}
.bh-wrap--bad {
  color: var(--bh-red);
}
.bh-wrap--mute {
  color: var(--bh-mute);
}
/* Version 26. Text-3XL/400, clipped, same text-shadow. */
.bh-big {
  font-size: 32px;
  line-height: 38px;
  font-weight: 400;
  min-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
}
.bh-big--bad {
  color: var(--bh-red);
}
.bh-big--ghost {
  color: var(--bh-ghost);
}
.bh-eyebrow {
  /* Text-XS/500 Uppercase */
  font-size: 12px;
  line-height: 16px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--bh-mute);
}
.bh-u {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: from-font;
}

.bh-stack {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.bh-stack--4 {
  gap: 4px;
}
.bh-stack--8 {
  gap: 8px;
}
.bh-stack--12 {
  gap: 12px;
}
.bh-stack--20 {
  gap: 20px;
}
/* h-full on a flex column with no justify: fill the height, pack
   the children to the top. */
.bh-stack--top {
  align-self: stretch;
  justify-content: flex-start;
}
.bh-nowrap {
  white-space: nowrap;
}
.bh-full {
  width: 100%;
}
.bh-grow {
  flex: 1 1 0;
  min-width: 0;
}
.bh-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  width: 100%;
}
.bh-count {
  display: flex;
  align-items: center;
  gap: 4px;
  width: 100%;
}

/* ── Chip ─────────────────────────────────────────────────
   The uppercase pill that names what is inside a category.
   Thirteen colourways across the exploration, which is
   thirteen more than the final has — the final has none. */
.bh-chips {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}
.bh-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 2px 6px;
  border-radius: 6px;
  font-size: 12px;
  line-height: 16px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  white-space: nowrap;
}
.bh-chip .bh-i {
  width: 12px;
  height: 12px;
}
.bh-chip--red {
  background: var(--bh-red);
  color: #fff;
}
.bh-chip--red-edge {
  background: var(--bh-red);
  color: #fff;
  box-shadow: inset 0 0 0 0.5px var(--bh-red-deep);
}
/* Version 22 put an outlined white chip on the red card
   rather than a filled one — the only chip in the set that
   is a border and nothing else. */
.bh-chip--outline {
  background: transparent;
  color: #fff;
  box-shadow: inset 0 0 0 1px #fff;
}
.bh-chip--pink {
  background: var(--bh-red-100);
  color: var(--bh-red);
}
.bh-chip--pink-edge {
  background: var(--bh-red-100);
  color: var(--bh-red);
  box-shadow: inset 0 0 0 0.5px var(--bh-red-200);
}
.bh-chip--grey {
  background: var(--bh-hair);
  color: var(--bh-mute);
}
.bh-chip--grey700 {
  background: var(--bh-hair);
  color: var(--bh-ink-2);
}
.bh-chip--grey050 {
  background: var(--bh-surface);
  color: var(--bh-mute);
}
.bh-chip--grey-edge {
  background: var(--bh-surface);
  color: var(--bh-mute);
  box-shadow: inset 0 0 0 0.5px var(--bh-hair);
}
.bh-chip--green {
  background: var(--bh-green-100);
  color: var(--bh-green);
  box-shadow: inset 0 0 0 0.5px var(--bh-green-200);
}
.bh-chip--green-flat {
  background: var(--bh-green-100);
  color: var(--bh-green);
}
.bh-chip--purple {
  background: var(--bh-purple-100);
  color: var(--bh-purple);
}
/* "+ 6 others" has no fill at all in two frames. A chip that
   is only a word is still a chip if it sits in the chip row. */
.bh-chip--bare {
  background: transparent;
  color: var(--bh-mute);
  padding-inline: 0;
}

/* ── Icon square ──────────────────────────────────────────
   Three sizes; the Figma component names two of them, Small
   (36 / radius 10) and Large (44 / radius 12). The unnamed
   one is 24 / radius 7.

   One recipe: a fill, a half-pixel inner edge, and a 2px
   white inner highlight along the top. Runwise uses that trio
   everywhere — it is what stops a flat fill from reading as a
   coloured rectangle. The half-pixel is not a typo. At 1px
   the edge reads as an outline and the tile stops looking
   lit, which is exactly what version 20's flat #E83232 square
   does on purpose. */
.bh-sq {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  position: relative;
  overflow: hidden;
  color: #fff;
}
.bh-sq--24 {
  width: 24px;
  height: 24px;
  border-radius: 7px;
}
.bh-sq--36 {
  width: 36px;
  height: 36px;
  border-radius: 10px;
}
.bh-sq--44 {
  width: 44px;
  height: 44px;
  border-radius: 12px;
}
/* The frontend's IconSquare puts this on the glyph at every
   size, not only the two big tiles. */
.bh-sq .bh-i {
  filter: drop-shadow(var(--bh-emboss));
}
.bh-sq--red-grad {
  background: linear-gradient(180deg, var(--bh-red-lt) 0%, var(--bh-red) 100%);
  box-shadow:
    inset 0 0 0 0.5px var(--bh-red-edge),
    inset 0 2px 2px rgba(255, 255, 255, 0.25);
}
.bh-sq--red-solid {
  background: var(--bh-red-deep);
  box-shadow:
    inset 0 0 0 0.5px var(--bh-red-edge),
    inset 0 2px 2px rgba(255, 255, 255, 0.25);
}
/* Version 20's square: the same fill with the edge and the
   highlight taken off. On a red card there is nothing left
   for a lit edge to lift it away from. */
.bh-sq--red-flat {
  background: var(--bh-red-deep);
}
.bh-sq--purple-grad {
  background: linear-gradient(
    180deg,
    var(--bh-purple-100) 0%,
    var(--bh-purple-200) 100%
  );
  color: var(--bh-purple);
  box-shadow:
    inset 0 0 0 0.5px var(--bh-purple-edge),
    inset 0 2px 2px rgba(255, 255, 255, 0.25);
}
.bh-sq--green-grad {
  background: linear-gradient(
    180deg,
    var(--bh-green-100) 0%,
    var(--bh-green-200) 100%
  );
  color: var(--bh-green);
  box-shadow:
    inset 0 0 0 0.5px var(--bh-green-edge),
    inset 0 2px 2px rgba(255, 255, 255, 0.25);
}
.bh-sq--pink {
  background: linear-gradient(
    180deg,
    var(--bh-red-100) 0%,
    var(--bh-red-200) 100%
  );
  color: var(--bh-red);
  box-shadow:
    inset 0 0 0 0.5px var(--bh-red-300),
    inset 0 2px 2px rgba(255, 255, 255, 0.25);
}

/* ── Notification card ────────────────────────────────────
   The primary slot. One red bar, one alert, one chevron.
   Everything on this page is a long argument about where
   this thing goes. */
.bh-note {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px;
  border-radius: 12px;
  background: var(--bh-red);
  color: #fff;
}
.bh-note--p16 {
  padding: 16px;
}
.bh-note--px16 {
  padding: 12px 16px;
}
.bh-note--r16 {
  border-radius: 16px;
}
.bh-note--tall {
  min-height: 76px;
}
.bh-note--shadow {
  box-shadow: var(--bh-shadow);
}
/* An action item is not an emergency, so the same card in
   Grey/050 with a hairline. This is the two-tone rule stated
   as a component variant. */
.bh-note--quiet {
  background: var(--bh-surface);
  color: var(--bh-ink);
  box-shadow: inset 0 0 0 1px var(--bh-hair);
}
.bh-note-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 0;
  min-width: 0;
}
.bh-note-title {
  font-size: 16px;
  line-height: 24px;
  font-weight: 700;
}
.bh-note-meta {
  font-size: 14px;
  line-height: 20px;
  font-weight: 400;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bh-note--quiet .bh-note-meta {
  color: var(--bh-mute);
}
/* Version 21 fixes the alert at 818 of the frame's 1120 and
   lets the counts take what is left. The first time the two
   halves are sized by importance rather than split evenly. */
.bh-note--wide {
  width: 818px;
  flex: none;
  align-self: stretch;
}

/* The stack. Three real cards, not two ghost bars: the second
   and third are the same component at a quarter opacity,
   sixteen units narrower each, pulled up so only their bottom
   edges show. */
.bh-deck {
  display: flex;
  flex-direction: column;
  align-items: center;
  isolation: isolate;
  width: 100%;
}
.bh-deck-card {
  box-shadow: var(--bh-shadow-sm);
}
.bh-deck-card--0 {
  margin-bottom: -60px;
  z-index: 3;
}
.bh-deck-card--1 {
  width: 404px;
  margin-bottom: -60px;
  opacity: 0.25;
  z-index: 2;
}
.bh-deck-card--2 {
  width: 388px;
  opacity: 0.25;
  z-index: 1;
}

/* Seven days of alert volume, 96 × 32. Each bar is a
   bottom-to-top white fade with a half-pixel white edge —
   the same lit-edge recipe as the icon squares, at 8 units
   wide. */
.bh-spark {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  width: 96px;
  height: 32px;
  flex: none;
  overflow: hidden;
}
.bh-spark i {
  display: block;
  flex: 1 1 0;
  min-width: 0;
  border-radius: 2px;
  border: 0.5px solid rgba(255, 255, 255, 0.25);
  background: linear-gradient(
    0deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.25) 100%
  );
  height: var(--bh-h, 2px);
}

/* ── Tray and tiles ───────────────────────────────────────
   Where it landed. A Grey/050 tray with a hairline holds the
   three counts, and in the frame that shipped it holds the
   primary slot as well — so the alert is visibly one of the
   numbers above it rather than a separate report sitting on
   top of them. That single container is the fix to the
   complaint that ran through four reviews. */
.bh-shell {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  padding: 8px;
  background: #fff;
  border-radius: 20px;
  box-shadow: var(--bh-shadow);
}
.bh-shell--g12 {
  gap: 12px;
}
.bh-shell--g20 {
  gap: 20px;
}
.bh-shell--p12 {
  padding: 12px;
}
.bh-shell--p20 {
  padding: 20px;
}
.bh-shell--row {
  flex-direction: row;
  align-items: stretch;
}
.bh-tray {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
  padding: 8px;
  background: var(--bh-surface);
  box-shadow: inset 0 0 0 1px var(--bh-hair);
  border-radius: 12px;
}
.bh-tray--p4 {
  padding: 4px;
}
.bh-tray--g8 {
  gap: 8px;
}
/* The caption inside version 33's second tray is inset 12 from
   the tray, matching the notification card's own padding. */
.bh-eyebrow--pad {
  padding-left: 12px;
}
.bh-tiles {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  width: 100%;
}
.bh-tiles--gap {
  gap: 4px;
}
.bh-tile {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 12px;
  border-radius: 8px;
}
.bh-tiles--p8 .bh-tile {
  padding: 8px;
}
/* The active category. White and lifted; the other two sit
   flat on the tray. That is the entire selected state — no
   border, no accent, no colour. */
.bh-tile--on {
  background: #fff;
  box-shadow: var(--bh-shadow);
}
/* Versions 27 and 28 gave every tile its own card. */
.bh-tile--card {
  background: var(--bh-surface);
  box-shadow: inset 0 0 0 1px var(--bh-hair);
  border-radius: 12px;
}
/* Version 32 gave every tile its own hairline box instead of
   butting three of them up inside one tray. Same information,
   reads as three components — the mistake this whole project
   is about. */
.bh-tile--edge {
  box-shadow: inset 0 0 0 1px var(--bh-hair);
  border-radius: 12px;
}
.bh-tile--edge-on {
  background: var(--bh-surface);
  box-shadow: inset 0 0 0 1px var(--bh-ghost);
}
.bh-faint {
  opacity: 0.5;
}

/* ── Small button ─────────────────────────────────────────
   Runwise's White Button/Default: a Grey/050 fill with three
   inner shadows doing the work an outline would otherwise do.
   Reproduced rather than approximated, because at 32 units
   tall the difference is the whole button. Three frames use
   the flat version with no inner shadows at all. */
.bh-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 100%;
  height: 32px;
  padding: 6px 8px;
  border-radius: 10px;
  background: var(--bh-surface);
  font-size: 14px;
  line-height: 20px;
  font-weight: 700;
  letter-spacing: -0.25px;
  color: var(--bh-ink);
  overflow: hidden;
  box-shadow:
    inset 0 2px 0 #fff,
    inset 0 0 0 1px var(--bh-hair),
    inset 0 -1px 1px rgba(0, 0, 0, 0.05);
}
.bh-btn--flat {
  box-shadow: none;
}
.bh-btn--split {
  justify-content: space-between;
  padding-left: 12px;
  padding-right: 16px;
}

/* ── Tabs ─────────────────────────────────────────────────
   One version made the categories a literal tab bar. It is
   the clearest statement of what the component was trying to
   be, and also the version that admits the categories had
   stopped being information and become navigation. */
.bh-tabs {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  height: 28px;
  border-bottom: 1px solid var(--bh-hair);
}
.bh-tab {
  display: flex;
  align-items: center;
  gap: 4px;
  align-self: stretch;
  padding-bottom: 6px;
  font-size: 14px;
  line-height: 20px;
  font-weight: 700;
  letter-spacing: -0.25px;
  color: var(--bh-mute);
  white-space: nowrap;
}
.bh-tab--on {
  color: var(--bh-ink);
  border-bottom: 2px solid var(--bh-ink);
}
.bh-tab-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  padding: 2px 6px;
  border-radius: 6px;
  background: var(--bh-red);
  color: #fff;
  font-size: 12px;
  line-height: 16px;
  font-weight: 700;
  letter-spacing: 0;
}

/* ── Group ────────────────────────────────────────────────
   The version that put each category in its own bordered box
   with its alerts inside it. Honest about the hierarchy and
   three times too tall. */
.bh-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  padding: 8px 12px;
  background: var(--bh-surface);
  box-shadow: inset 0 0 0 1px var(--bh-ghost);
  border-radius: 12px;
}
.bh-group-head {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  font-size: 16px;
  line-height: 24px;
  font-weight: 700;
}
.bh-group-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 12px;
  box-shadow: inset 0 0 0 1px var(--bh-ghost);
  border-radius: 12px;
}
.bh-group-name {
  flex: 1 1 0;
  min-width: 0;
  font-size: 16px;
  line-height: 24px;
  font-weight: 700;
}
/* Both the category name and "View" are flex-1 in the file, so
   they split the row evenly and VIEW lands near the middle
   rather than hard right. Right-aligning it put it four units
   off. */
.bh-group-view {
  flex: 1 1 0;
  min-width: 0;
  font-size: 12px;
  line-height: 16px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--bh-mute);
  text-align: right;
}
.bh-viewall {
  width: 100%;
  text-align: center;
  font-size: 12px;
  line-height: 16px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--bh-mute);
}

/* ── The blob ─────────────────────────────────────────────
   Version 36 is one vector, not three rounded rectangles, so
   it is drawn as the path the file exports and the tabs are
   placed on top of it at their Figma coordinates. The frame
   flips the vector vertically; so does this. */
.bh-blob-flip {
  transform: scaleY(-1);
  transform-origin: center;
  pointer-events: none;
}
.bh-blob-svg {
  display: block;
}
.bh-blob-tab {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 12px;
  border-radius: 12px;
  box-shadow: inset 0 0 0 1px var(--bh-hair);
}
.bh-blob-tab--on {
  box-shadow: none;
  color: #fff;
}
.bh-blob-tab--on .bh-num,
.bh-blob-tab--on .bh-title {
  color: #fff;
}
.bh-blob-alert {
  display: flex;
  align-items: center;
  gap: 12px;
  color: #fff;
}
/* The text block is 299 wide inside a 178-wide container in
   the file. It overflows, and it lands just short of the
   chevron — so it is reproduced overflowing rather than
   tidied up. */
.bh-blob-text {
  width: 299px;
  flex: none;
}
.bh-blob-more {
  font-size: 16px;
  line-height: 24px;
  font-weight: 400;
  color: #fff;
  text-align: center;
}

.bh-rule {
  width: 100%;
  height: 1px;
  margin-block: -0.5px;
  background: var(--bh-hair);
  flex: none;
}

/* Version 25's list: a hairline above each row rather than
   between them, so the block reads as a list under the alert
   rather than as three things of equal weight beside it. */
.bh-vstack {
  display: flex;
  flex-direction: column;
  gap: 16px;
  width: 100%;
}
.bh-vrow {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
}

/* Version 21's compact list. */
.bh-minis {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  align-self: stretch;
}
.bh-mini {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
}
.bh-mini-right {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}
.bh-mini-i {
  width: 12px;
  height: 12px;
}

/* ── Glyph tones ──────────────────────────────────────────
   Icons carry no colour of their own, so these set the two
   hues that survived plus the greys. A chevron inside the red
   bar needs no rule at all: it inherits white from .bh-note. */
.bh-ok {
  color: var(--bh-green);
}
.bh-bad {
  color: var(--bh-red);
}
.bh-grey {
  color: var(--bh-mute);
}
.bh-grey3 {
  color: var(--bh-mute-2);
}
.bh-mute {
  color: var(--bh-mute);
}

/* ── Defending the glyphs from .diagram svg ───────────────
   style.css line 1113 is `.diagram svg { width: 100%;
   height: auto; min-width: 34rem }`, written for the one
   hand-drawn <svg> that IS the figure. Every icon inside a
   recreated frame is also an <svg> inside a .diagram, so
   without this block a 16px chevron becomes a 544px chevron.

   The README says this repo has got `.diagram svg` wrong
   twice already, both times by reaching for the descendant
   selector. This is the third occasion and the first where
   the icons are on the other side of it, so the fix is
   specificity rather than a rewrite of a rule seven other
   pages depend on: `.bh svg.bh-i` is (0,2,1) against
   `.diagram svg`'s (0,1,1).

   min-width and max-width are reset explicitly. width alone
   is not enough — a 34rem floor beats a 16px width. Sizes
   live here rather than beside their components for the same
   reason: split across two blocks, the lower-specificity half
   would silently lose inside a figure and win outside one. */
.bh svg.bh-i {
  display: block;
  width: 16px;
  height: 16px;
  min-width: 0;
  max-width: none;
  overflow: visible;
  fill: currentColor;
}
/* The Figma icon slots are not square, and the difference is
   load-bearing: a chip's glyph sits in a 15×12 box, a chevron
   in 20×16. Sizing them 12×12 and 16×16 pulled everything to
   their right three or four units left of where the file puts
   it. The viewBox is 16×16 and preserveAspectRatio defaults to
   xMidYMid meet, so a 15×12 box draws a 12×12 glyph centred in
   15 — which is exactly what the slot does in Figma. */
.bh svg.bh-chev,
.bh .bh-count svg.bh-i,
.bh .bh-group-head svg.bh-i,
.bh .bh-group-row svg.bh-ok {
  width: 20px;
  height: 16px;
}
.bh .bh-chip svg.bh-i,
.bh svg.bh-mini-i {
  width: 15px;
  height: 12px;
}
.bh svg.bh-chev-s,
.bh svg.bh-pop,
.bh .bh-sq--24 svg.bh-i {
  width: 12px;
  height: 12px;
}
.bh svg.bh-pop {
  color: var(--bh-mute);
}
.bh .bh-sq--36 svg.bh-i {
  width: 16px;
  height: 16px;
}
.bh .bh-sq--44 svg.bh-i,
.bh svg.bh-status {
  width: 20px;
  height: 20px;
}
/* Version 23's duotone glyphs sit in a 20-square box at a 20px
   font size, and a 640-wide glyph is 25 across at that size —
   it overflows the box in the file too. The slot is sized to
   the glyph rather than the box so wifi and buildings come out
   their real width; list is 512 and meets at 20, centred. */
.bh svg.bh-glyph20 {
  width: 25px;
  height: 20px;
}
.bh svg.bh-blob-svg {
  width: 413.5px;
  height: 190.5px;
  min-width: 0;
  max-width: none;
}

/* ═════════════════════════════════════════════════════════
   .bj-* — the site talking about the product
   ═════════════════════════════════════════════════════════ */

/* The rail band. Full-bleed grey, same recipe as .sj on the
   savings page, so the two case studies read as one system
   rather than two one-offs. */
.bj {
  width: 100vw;
  max-width: none;
  margin-inline: calc(50% - 50vw);
  padding-block: calc(var(--u) * 3);
  padding-inline: calc(50vw - 50%);
  background: #f4f3f0;
  border-block: 1px solid rgba(22, 21, 15, 0.1);
}
.bj:empty {
  display: none;
}
.bj-inner {
  margin-inline: 0 auto;
}

.bj-stage {
  display: grid;
  gap: calc(var(--u) * 2);
  align-items: start;
}
/* Two columns only when the frame is narrow enough to earn
   them. The wide family takes the band and drops the note
   below — see rail() for the switch. */
@media (min-width: 62rem) {
  .bj-stage--narrow {
    grid-template-columns: minmax(0, 1fr) 20rem;
    gap: calc(var(--u) * 3);
  }
}
.bj-stage--wide .bj-note {
  max-width: var(--measure);
}

/* The frame sits on white so a white card on a grey band does
   not read as a card with a grey border. */
.bj-slot {
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: 12px;
  padding: calc(var(--u) * 1.5);
  /* Tall enough that stepping through the twenty-four wide
     frames — 86 to 196 units, so 71 to 163 rendered — never
     moves the controls under the reader's cursor. The twelve
     vertical ones render near 1:1 and grow past it; a floor
     tall enough for those would leave 230px of dead air under
     every wide frame, which is the worse trade. */
  min-height: 220px;
  display: flex;
  align-items: center;
}
.bj-slot > * {
  width: 100%;
}

.bj-note {
  max-width: var(--measure);
}
.bj-count,
.bj-act {
  font-size: 14px;
  color: var(--ink-50);
  margin: 0 0 calc(var(--u) * 0.5);
}
.bj-title {
  font-size: 16px;
  font-weight: 600;
  margin: 0 0 calc(var(--u) * 0.5);
  line-height: var(--line);
}
.bj-why {
  margin: 0;
  line-height: var(--line);
}
.bj-verdict {
  margin: var(--u) 0 0;
  padding-top: calc(var(--u) * 0.75);
  border-top: 1px solid var(--rule);
  color: var(--ink-50);
}

.bj-controls {
  display: flex;
  align-items: center;
  gap: var(--u);
  margin-top: calc(var(--u) * 2);
  flex-wrap: wrap;
}
.bj-btn {
  font: inherit;
  padding: 6px 14px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: #fff;
  color: var(--ink);
}
.bj-btn[disabled] {
  color: var(--ink-50);
  opacity: 0.5;
}
.bj-btn:not([disabled]):hover {
  border-color: var(--underline);
}

.bj-ticks {
  display: flex;
  gap: 3px;
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
  list-style: none;
}
.bj-tick {
  width: 16px;
  height: 26px;
  padding: 0;
  border: 0;
  background: none;
  display: grid;
  place-items: center;
}
.bj-tick i {
  display: block;
  width: 100%;
  height: 4px;
  border-radius: 2px;
  background: rgba(22, 21, 15, 0.18);
}
.bj-tick:hover i {
  background: rgba(22, 21, 15, 0.4);
}
.bj-tick[aria-current="true"] i {
  background: var(--ink);
  height: 8px;
}
/* The frames a review turned. Marked in the product's own
   red, so the rail shows without being read that the journey
   has six load-bearing moments and thirty others. */
.bj-tick--key i {
  background: rgba(255, 98, 98, 0.6);
}
.bj-tick--key:hover i {
  background: rgba(255, 98, 98, 0.95);
}
.bj-legendkey {
  font-size: 14px;
  color: var(--ink-50);
  margin: var(--u) 0 0;
}

/* ── Contact sheet ────────────────────────────────────────
   Wider than the rail on purpose: the rail is one frame next
   to a paragraph and wants a reading measure, and the sheet's
   whole argument is that you can see all thirty-six at once. */
.bj-sheet {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: calc(var(--u) * 2);
  max-width: 82rem;
  margin-inline: 0 auto;
}
.bj-cell {
  margin: 0;
}
/* A 1120-unit frame and a 442-unit frame in the same grid
   render at 0.2 and 0.5 scale. The wide ones take two columns
   so the two families land within touching distance of each
   other and neither is unreadable. */
.bj-cell--wide {
  grid-column: span 2;
}
@media (max-width: 34rem) {
  .bj-cell--wide {
    grid-column: span 1;
  }
}
.bj-cell figcaption {
  margin-top: calc(var(--u) * 0.5);
  font-size: 13px;
  line-height: 18px;
  color: var(--ink-50);
}
.bj-cell-n {
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* ── The placement diagram ────────────────────────────────
   Three page layouts side by side. Deliberately grey blocks
   and not the real page: the argument is about where a
   rectangle sits, and any more fidelity than that invites the
   reader to evaluate the wrong thing. */
.bj-places {
  display: grid;
  gap: calc(var(--u) * 2);
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}
.bj-place {
  margin: 0;
}
.bj-page {
  display: grid;
  gap: 6px;
  padding: 8px;
  background: #f4f3f0;
  border: 1px solid var(--rule);
  border-radius: 6px;
  aspect-ratio: 4 / 3;
  grid-template-columns: 2fr 1fr;
  grid-auto-rows: minmax(0, 1fr);
}
.bj-page i {
  display: block;
  border-radius: 3px;
  background: rgba(22, 21, 15, 0.1);
}
.bj-page i.wide {
  grid-column: 1 / -1;
}
.bj-page i.tall {
  grid-row: span 2;
}
.bj-page i.on {
  background: #ff6262;
}
.bj-place figcaption {
  margin-top: calc(var(--u) * 0.75);
  font-size: 14px;
  line-height: 20px;
}
.bj-place figcaption b {
  display: block;
  font-weight: 600;
}
.bj-place figcaption span {
  color: var(--ink-50);
}

/* ── The colour ledger ────────────────────────────────────
   Five hues in, two out. These swatches are the only place on
   the site outside a recreated frame where a product hue
   appears at full strength, and they earn it: the section is
   an argument about which of them survived, and a reader
   cannot follow it from names alone. */
.bj-hues {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--u) * 1.5);
  margin: 0;
  padding: 0;
  list-style: none;
}
.bj-hue {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  min-width: 13rem;
  flex: 1 1 13rem;
}
.bj-hue i {
  display: block;
  width: 18px;
  height: 18px;
  border-radius: 5px;
  margin-top: 3px;
  flex: none;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);
}
.bj-hue b {
  display: block;
  font-weight: 600;
}
.bj-hue span {
  display: block;
  color: var(--ink-50);
  font-size: 14px;
  line-height: 20px;
}
.bj-hue--cut b {
  text-decoration: line-through;
  text-decoration-color: var(--rule);
}
.bj-hue-red {
  background: #ff6262;
}
.bj-hue-green {
  background: #07ad00;
}
.bj-hue-purple {
  background: #6b41e3;
}
.bj-hue-orange {
  background: #f08a24;
}
.bj-hue-grey {
  background: #8e8d8c;
}

/* ── Page furniture ───────────────────────────────────── */

.bj-fallback {
  margin: 0;
  color: var(--ink-50);
}

.bh-hero {
  margin: calc(var(--u) * 3) 0 calc(var(--u) * 4);
}
.bh-hero figcaption {
  margin-top: calc(var(--u) * 1.5);
  max-width: var(--measure);
  margin-inline-start: var(--indent);
  line-height: var(--line);
}

/* A host div between .bh-centre and .bh-fit is a flex item
   with no intrinsic width, so it collapses to zero and the
   frame measures zero and scales to nothing. Sizing the host
   rather than the .bh-fit inside it is what makes the markup
   survive the extra wrapper the fallback paragraph needs. */
.bh-centre {
  display: flex;
  justify-content: center;
}
.bh-centre > * {
  width: 100%;
  max-width: 461px;
}

/* The before/after pair gets its own band so the two frames
   read as one comparison rather than as two adjacent figures.
   Same grey as the rail band. */
.bj-pair-band {
  width: 100vw;
  max-width: none;
  margin-inline: calc(50% - 50vw);
  margin-block: calc(var(--u) * 3);
  padding-block: calc(var(--u) * 3);
  padding-inline: calc(50vw - 50%);
  background: #f4f3f0;
  border-block: 1px solid rgba(22, 21, 15, 0.1);
}
.bj-pair-band .bh-centre > * {
  max-width: 380px;
}
.bj-pair {
  display: grid;
  gap: calc(var(--u) * 2);
  align-items: start;
}
@media (min-width: 44rem) {
  .bj-pair {
    grid-template-columns: 1fr 1fr;
    gap: calc(var(--u) * 3);
  }
}
.bj-pair figcaption b {
  display: block;
  color: var(--ink);
  font-weight: 600;
}

/* `.diagram figcaption` is a descendant selector carrying the
   page's content-line indent and the 34rem measure. Both are
   right for the caption OF a figure and wrong for a caption
   inside one — a 208px indent in a 250px column leaves a
   42px-wide paragraph. Two of this page's figures nest
   captions, so both get reset here. */
.diagram .bj-place figcaption,
.diagram .bj-pair figcaption,
.bj-pair-band figcaption {
  max-width: none;
  margin: calc(var(--u) * 0.75) 0 0;
  font-size: 14px;
  line-height: 20px;
  color: var(--ink-50);
}
.bj-place figure,
.bj-pair figure {
  margin: 0;
}

/* Five decisions rather than the savings page's four, and
   this file's copy of the strip rather than an import — the
   two pages do not otherwise share a stylesheet, and a rule
   reached across a page boundary is how the diagram indent
   bug happened. */
.bj-decisions {
  display: grid;
  gap: var(--u);
  grid-template-columns: repeat(5, minmax(0, 1fr));
  list-style: none;
  margin: var(--u) 0 calc(var(--u) * 4);
  padding: 0;
}
.bj-decisions li {
  display: grid;
  gap: 2px;
  padding-block-start: 12px;
  position: relative;
}
.bj-decisions li::before {
  background: var(--rule);
  content: "";
  height: 1px;
  inset-inline: 0;
  position: absolute;
  top: 0;
}
/* The two that came from someone else in a review, marked in
   the product's red rather than in ink. */
.bj-decisions li:nth-child(3)::before,
.bj-decisions li:nth-child(5)::before {
  background: rgba(255, 98, 98, 0.7);
}
.bj-decisions span,
.bj-decisions small {
  color: var(--ink-50);
  font-size: 13px;
}
.bj-decisions strong {
  font-weight: 600;
}
@media (max-width: 56rem) {
  .bj-decisions {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
@media (max-width: 34rem) {
  .bj-decisions {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ── Page rhythm ──────────────────────────────────────────
   A full-bleed grey band is how this site frames a figure,
   and at twelve of them the page stopped reading as sections
   with figures in them and started reading as stripes. Six
   figures come out of the band and sit in the column on
   white; the five that stay are the turning points — the
   banner, the vertical, the before/after pair, the rail and
   the sheet — so a band now means "this is where it moved"
   rather than "here is another picture".

   Specificity: .diagram.bj-plain is (0,2,0) against
   .diagram's (0,1,0), so it wins without !important. */
.diagram.bj-plain {
  width: auto;
  max-width: none;
  margin-inline: 0;
  padding-inline: 0;
  padding-block: 0;
  background: transparent;
  border-block: 0;
  overflow-x: visible;
}
/* Off the band, the figure has no tint to separate it from
   the prose, so it borrows .block's rhythm instead: the same
   run-up a section gets, and a hairline only where one is
   already expected. */
.diagram.bj-plain {
  margin-block: calc(var(--u) * 3) calc(var(--u) * 4);
}
.block:has(+ .diagram.bj-plain) {
  padding-block-end: calc(var(--u) * 2);
}
.diagram.bj-plain + .block {
  border-top: 1px solid var(--rule);
  padding-block-start: calc(var(--u) * 2);
}

/* style.css gives a block caught between two bands its run-up
   and its landing, but the selector names .sj — the savings
   page's band. This page's band is .bj, and without these two
   rules the rail and the sheet were the only figures on the
   site a section did not make room for. */
.bj + .block {
  border-top: 0;
  padding-block-start: calc(var(--u) * 4);
}
.block:has(+ .bj),
.block:has(+ .bj-pair-band) {
  padding-block-end: calc(var(--u) * 4);
}
.bj-pair-band + .block {
  border-top: 0;
  padding-block-start: calc(var(--u) * 4);
}

/* Objects sit on the page line and text on the content line —
   style.css states that rule and every other figure on the
   site follows it. These were centred, which is neither, and
   put each 436-unit frame on an edge nothing else shared.
   Inside the before/after pair they stay centred: there the
   two frames are being compared to each other, not to the
   page. */
/* The panel is now the object, and it starts on the page line
   like every other figure. What sits inside it is a stage
   question, and a 436-unit frame hugging the left of a
   1040-wide grey field reads as a mistake — so the frame
   centres within its panel. */
.bh-centre {
  justify-content: center;
}

/* Off the band, a frame made of white cards had nothing to sit
   on and its shadows were doing all the work. The band's own
   ground comes back as a contained panel instead — same tint,
   same hairline, stopped at the column edge rather than run to
   the viewport. The page keeps its two edges and the cards get
   something to be white against.

   The panel goes on the host rather than on .bh-fit itself:
   measure() reads .bh-fit's own clientWidth, so padding there
   would be counted twice. On the host it is just less room,
   which is what it is. */
.diagram.bj-plain > div,
.diagram.bj-plain .bh-centre,
.bh-hero .bh-centre {
  background: #f4f3f0;
  border: 1px solid rgba(22, 21, 15, 0.1);
  border-radius: 12px;
  padding: calc(var(--u) * 1.5);
}
/* The centring wrapper is a flex row; the panel is on it, so
   the host inside must not fight it for width. */
.diagram.bj-plain .bh-centre > *,
.bh-hero .bh-centre > * {
  max-width: 461px;
}
