/**
 * Molar Metrics — visual system.
 *
 * The idiom: a premium analytical document. Cool white paper, a deep ink-teal
 * for authority, brass for emphasis, and one signature form — the concentric
 * ring — carried through the whole product. That form is not decoration: the
 * analysis literally is rings drawn around a site, so the ring is the subject's
 * own geometry rather than a shape chosen to look nice.
 *
 * The ring appears in exactly four places, each carrying information:
 *   · the cover, at full size, as the site and its three radii
 *   · the section marker, where the swept arc is progress through the document
 *   · the grade gauge, where the swept arc is patients-per-office against the
 *     benchmark band
 *   · the office row, where the swept arc is how far that practice's type was
 *     actually verified and the filled center is whether it is open at all
 * If a fifth use appears that carries no information, delete it. The fourth
 * earned its place by replacing a numbered disc that carried none.
 *
 * Type is a deliberate pair, not a default. Newsreader — a low-contrast screen
 * serif — sets the cover, section titles and headline figures; it has an
 * editorial voice that a grotesque cannot give a document meant to be read
 * rather than scanned. Source Sans 3 sets everything functional: tables,
 * explanations, labels, findings. Serif for what you read, sans for what you
 * consult.
 *
 * Color rules, in force:
 *   · Data series: steel blue #31699F primary, violet #8A4FB8 and amber #B8791F
 *     as secondary marks. The original trio led with green, measured at
 *     ΔE ≥ 13.2 across deutan/protan/tritan axes; the steel blue replaced it
 *     for brand coherence and leans on lightness against the violet
 *     (protanopia drains violet toward blue). That is acceptable only because
 *     no chart lets color carry meaning alone — every series is also separated
 *     by shape or a printed label — and any future hue change should re-check
 *     the CVD separation rather than trust this note.
 *   · The copper accent (--brass) is emphasis and UI chrome only — never a
 *     series in a chart.
 *   · Chrome never borrows a data color: ring geometry, gauges, focus states
 *     and tile rules are brand navy or copper.
 *   · Status color (good/caution/serious/note) is reserved for findings and
 *     never reused as a series. Every finding also carries an icon and a word,
 *     so color is never the only signal.
 *
 * The document is a stack of `.sheet` elements. That one decision makes the
 * screen view and the printed view the same document rather than two layouts
 * that drift, and gives every section an honest page break.
 */

/* ------------------------------------------------------------------ tokens */

:root {
  /**
   * Declared explicitly, and this is load-bearing.
   *
   * Without it Chrome's automatic dark mode force-inverts the page: white paper
   * becomes black, the tinted finding panels invert into muddy blocks, and the
   * grade letter goes dark-on-dark. The design looked broken and the cause was
   * invisible in the CSS.
   *
   * `light` rather than `light dark` because this is a document. It is designed
   * to be printed, exported to PDF and forwarded, and paper has one appearance.
   * A dark theme here would have to be designed — chosen steps from the same
   * ramps, re-validated against a dark surface — not derived by inverting a
   * light one, which is exactly what the browser was doing badly.
   */
  color-scheme: light;

  /* Paper and the room it sits in. */
  --room: #e3e8ec;
  --room-2: #edf1f4;
  --sheet: #fbfcfd;
  --panel: #eff3f7;
  --panel-2: #e4ebf1;

  /* Deep ink — the cover, the topbar rule, inverted blocks. */
  --deep: #0c2338;
  --deep-2: #133753;
  --deep-ink: #c9d9e6;

  --rule: #d9dfe4;
  --rule-strong: #aebcc8;

  --ink: #14202b;
  --ink-2: #44586a; /* 8.2:1 on paper */
  --ink-3: #6c7d8d; /* 4.7:1 on paper */

  --brand: #1d4568;

  /* Data trio — see the color rules in the header before changing. */
  --data-primary: #31699f;
  --data-second: #8a4fb8;
  --data-third: #b8791f;
  --data-flat: #a9b6c2;

  /* Unswept arc on the certainty ring. Same value as --rule, named for its job
     so the ring's two strokes read as a pair at the call site. */
  --ring-track: #d9dfe4;

  /* Copper: emphasis and UI accent only, never a chart series. */
  --brass: #c47b4a;
  --brass-tint: #f9efe6;

  /* Status. Findings only. */
  --good: #1b7a55;
  --good-tint: #e7f4ee;
  --caution: #94590e;
  --caution-tint: #fbf0e1;
  --serious: #a5322f;
  --serious-tint: #fbeae9;
  --note: #3a5c78;
  --note-tint: #eaf1f6;

  /* Type. Ratio 1.2 from a 16px base. */
  --t-xs: 0.688rem;
  --t-sm: 0.813rem;
  --t-base: 1rem;
  --t-md: 1.188rem;
  --t-lg: 1.5rem;
  --t-xl: 1.875rem;
  --t-2xl: 2.25rem;
  --t-3xl: 3rem;
  --t-4xl: 3.75rem;

  --display: "Newsreader", ui-serif, Georgia, serif;
  --text: "Source Sans 3", ui-sans-serif, system-ui, -apple-system, sans-serif;

  /* Spacing, 4px series. */
  --s1: 0.25rem;
  --s2: 0.5rem;
  --s3: 0.75rem;
  --s4: 1rem;
  --s5: 1.5rem;
  --s6: 2rem;
  --s7: 3rem;
  --s8: 4rem;
  --s9: 6rem;

  --sheet-w: 48rem;
  --radius: 4px;
  --radius-lg: 10px;

  /* Depth. Two layers: a tight contact shadow and a wide ambient one. */
  --lift: 0 1px 1px rgba(7, 49, 44, 0.04), 0 6px 20px -10px rgba(7, 49, 44, 0.16);
  --lift-lg: 0 1px 2px rgba(7, 49, 44, 0.05), 0 18px 48px -20px rgba(7, 49, 44, 0.28);
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  background:
    radial-gradient(1200px 600px at 50% -10%, var(--room-2), transparent 70%),
    var(--room);
  color: var(--ink);
  font-family: var(--text);
  font-size: var(--t-base);
  line-height: 1.55;
  font-feature-settings: "tnum" 1;
  -webkit-font-smoothing: antialiased;
}

.num { font-variant-numeric: tabular-nums; }

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

a { color: var(--brand); text-underline-offset: 2px; }
a:hover { color: var(--deep-2); }

:where(a, button, input, summary):focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 2px;
}

/* ------------------------------------------------------------------ chrome */

/*
  The same header as the marketing pages: the deep navy bar, the ring mark,
  the serif wordmark, the same nav. The app is one page of one site, and a
  visitor arriving from the landing page should not feel a seam.
*/
.topbar {
  position: sticky; top: 0; z-index: 20;
  background: var(--deep);
  color: var(--deep-ink);
}

/* Identical metrics to the marketing header (.top__inner in site.css):
   72rem measure, same padding, same wordmark size — the bar must not shrink
   or shift when a visitor crosses from the landing page into the app. */
.topbar__inner {
  max-width: 72rem;
  margin: 0 auto;
  padding: 0 1.5rem;
  /* Same fixed height as .top__inner in site.css — content must not size it. */
  min-height: 4.25rem;
  display: flex; align-items: center; gap: 1.5rem;
}

/* Identical to .top__brand in site.css, digit for digit — same gap, no
   letter-spacing. The wordmark must not re-set when a visitor crosses from
   the landing page into the app. */
.brand {
  display: flex; align-items: center; gap: 0.6rem;
  margin: 0; color: #fff;
  font-family: var(--display);
  font-size: 1.25rem; font-weight: 500;
  /* Pinned, not inherited: the two stylesheets have different body
     line-heights, and inheriting them moved the wordmark half a pixel. */
  line-height: 1.6;
  text-decoration: none;
}
.brand svg { flex: none; }

.topbar__nav { display: flex; gap: 1.4rem; font-size: 0.95rem; flex-wrap: wrap; }
.topbar__nav a { color: var(--deep-ink); text-decoration: none; }
.topbar__nav a:hover { color: #fff; text-decoration: underline; }

.topbar__spacer { flex: 1; }

/* The landing header's ghost-button geometry, so the app's contextual
   actions sit in the bar exactly the way "Screen an address" does. */
.topbar__action {
  font: inherit; font-size: 1rem; font-weight: 600;
  color: var(--deep-ink);
  background: transparent;
  border: 1px solid rgba(201, 217, 230, 0.45);
  border-radius: 8px; padding: 0.7rem 1.3rem;
  cursor: pointer; white-space: nowrap;
  text-decoration: none;
  transition: border-color 120ms ease;
}
.topbar__action:hover:not(:disabled) { border-color: var(--brass); }
.topbar__action:disabled { cursor: not-allowed; opacity: 0.6; }

@media (max-width: 44rem) {
  .topbar__nav { display: none; }
}

/* ------------------------------------------------------------------ sheets */

.doc {
  max-width: var(--sheet-w);
  margin: 0 auto;
  padding: var(--s6) var(--s4) var(--s9);
  display: flex; flex-direction: column; gap: var(--s6);
}

.sheet {
  position: relative;
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  box-shadow: var(--lift);
  padding: var(--s7) var(--s7) var(--s8);
}

/* Running header: what this document is, and where you are in it. */
.sheet__running {
  display: flex; align-items: center; gap: var(--s3);
  padding-bottom: var(--s3);
  border-bottom: 1px solid var(--rule);
  margin-bottom: var(--s6);
  font-size: var(--t-xs);
  text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--ink-3);
}
.sheet__running b { font-weight: 700; color: var(--ink-2); }
.sheet__running .right { margin-left: auto; font-variant-numeric: tabular-nums; }

/* The section marker: ring glyph plus number. The glyph's arc is progress. */
.sheet__marker { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s3); }
.sheet__marker svg { flex: none; }
.sheet__no {
  font-size: var(--t-xs); font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--brass);
}

.sheet__title {
  margin: 0;
  font-family: var(--display);
  font-size: var(--t-2xl); font-weight: 500;
  letter-spacing: -0.02em; line-height: 1.12;
}

.sheet__standfirst {
  margin: var(--s3) 0 0;
  font-size: var(--t-md); color: var(--ink-2);
  line-height: 1.5; max-width: 34em;
}

.sheet__intro { margin: var(--s5) 0 0; max-width: 36em; }
.sheet__intro p { margin: 0 0 var(--s3); }
.sheet__intro p:last-child { margin-bottom: 0; }

.sheet__body { margin-top: var(--s6); }
.sheet__body > * + * { margin-top: var(--s6); }

/* ---------------------------------------------------------------- the cover */

/**
 * The first impression, and the only inverted sheet in the document. Dark
 * because a cover should feel like the front of something, and because the ring
 * figure reads best drawn in light on dark.
 */
.cover {
  position: relative; overflow: hidden;
  border: 0; border-radius: var(--radius-lg);
  box-shadow: var(--lift-lg);
  background:
    radial-gradient(900px 500px at 78% 12%, var(--deep-2), transparent 62%),
    linear-gradient(168deg, #113049, var(--deep) 62%);
  color: #fff;
  padding: var(--s8) var(--s7);
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s7); align-items: center;
}

.cover__eyebrow {
  margin: 0 0 var(--s4);
  font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.16em;
  color: #dfa878;
}

.cover h2 {
  margin: 0;
  font-family: var(--display);
  font-size: var(--t-3xl); font-weight: 500;
  line-height: 1.06; letter-spacing: -0.03em;
  text-wrap: balance;
}

.cover__meta {
  margin: var(--s5) 0 0;
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5);
  font-size: var(--t-sm); color: var(--deep-ink);
}
.cover__meta b { color: #fff; font-weight: 600; }

/*
  The grade, lifted out of the metadata run.

  It used to sit inline beside "16 sections", at the same size — the report's
  most consequential output typeset like a page tally. It is the first thing a
  buyer looks for and the first thing a lender asks about.

  A rule above it rather than a box around it: the cover already carries a heavy
  form in the ring figure, and a second panel would fight it. The letter takes
  the display face, the label stays in the sans, and the pair sits quiet enough
  for a document somebody forwards to their bank.
*/
.cover__grade {
  margin: var(--s5) 0 0;
  padding-top: var(--s4);
  border-top: 1px solid rgba(201, 217, 230, 0.22);
  display: flex; align-items: baseline; gap: var(--s3);
}

.cover__gradeLabel {
  font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.16em;
  color: #dfa878;
}

.cover__gradeLetter {
  font-family: var(--display);
  font-size: var(--t-2xl); font-weight: 500;
  line-height: 1; letter-spacing: -0.02em;
  color: #fff;
}

.cover__rings { flex: none; }

@media (max-width: 44rem) {
  .cover { grid-template-columns: 1fr; padding: var(--s6) var(--s5); }
  .cover__rings { justify-self: center; }
}

/* ------------------------------------------------------- metric explainers */

/**
 * The core pedagogical unit, and the reason this rebuild exists: what the number
 * measures, then why it matters, then the number. A reader who does not already
 * know what owner-occupancy implies for a dental practice meets the explanation
 * before the figure, not in a footnote after it.
 */
.metric { border-top: 1px solid var(--rule); padding-top: var(--s5); }

.metric__title {
  margin: 0 0 var(--s4);
  font-family: var(--display);
  font-size: var(--t-lg); font-weight: 500; letter-spacing: -0.015em;
}

.explain {
  display: grid; gap: var(--s4) var(--s6);
  grid-template-columns: 1fr 1fr;
  margin: 0 0 var(--s5);
}
.explain > div { min-width: 0; }

.explain dt {
  margin-bottom: var(--s1);
  font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.09em;
  color: var(--brass);
}

.explain dd { margin: 0; font-size: var(--t-sm); color: var(--ink-2); line-height: 1.55; }

/* "How to read this" sits under the chart, not above it. */
/*
  Guidance boxes, in two families a reader can tell apart at a glance.

  The rule: a tinted box talks about THIS market's data (findings, and the
  blue method notes below); an outlined white box is the manual — fixed text
  teaching you to read a figure, identical in every report. A copper tint was
  tried for the manual boxes and read as a caution warning, because the amber
  finding tint is nearly the same cream — the opposite of identifiable.
*/
.howto {
  margin: var(--s4) 0 0;
  padding: var(--s3) var(--s4);
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  border-left: 3px solid var(--brass);
  font-size: var(--t-sm); color: var(--ink-2);
}
.howto > b:first-child {
  display: block; margin-bottom: var(--s1);
  font-weight: 700; color: #8a5426;
  text-transform: uppercase; font-size: var(--t-xs); letter-spacing: 0.09em;
}
.howto--note {
  background: var(--note-tint);
  border-color: transparent;
  border-left-color: var(--note);
}
.howto--note > b:first-child { color: var(--note); }

/* --------------------------------------------------------- comparison bars */

/**
 * The workhorse chart. Bars because the job is comparing magnitudes across a few
 * named categories; anchored at zero and on one shared scale, so length is
 * proportional to value. Every bar is direct-labeled, so the chart needs
 * neither axis nor legend and survives being printed in grayscale.
 *
 * The fill carries a slight single-hue gradient. It stays one hue — the gradient
 * adds depth without implying a second encoding.
 */
.bars { display: flex; flex-direction: column; gap: var(--s2); }

.bars__row {
  display: grid;
  /*
    The label column is set per chart, not globally. 5.5rem fits "5 miles" and
    the state name, which is every chart in the report except the composition and
    language ones — those carry category names like "Native Hawaiian or Pacific
    Islander" and were truncating to "Native Hawaiian or…", which is not a label.
  */
  grid-template-columns: var(--bars-label-w, 5.5rem) minmax(0, 1fr) auto;
  align-items: center; gap: var(--s3);
}

/* Charts whose rows are named categories rather than radii. */
.bars--wide { --bars-label-w: 12rem; }

@media (max-width: 560px) {
  .bars--wide { --bars-label-w: 7rem; }
}

.bars__label {
  font-size: var(--t-sm); color: var(--ink-2); text-align: right;
  /*
    Wraps rather than truncates. Baseline rows carry labels like "Average
    Texas ground, 3 mi", and an ellipsis was cutting them to "Average Texa…" —
    a label the reader cannot check is worse than a taller row.
  */
  line-height: 1.25;
  overflow-wrap: break-word;
}
.bars__row[data-baseline="true"] .bars__label { color: var(--ink-3); }

.bars__track {
  position: relative; height: 1.5rem;
  background: var(--panel); border-radius: var(--radius);
}

.bars__fill {
  position: absolute; top: 0; bottom: 0; left: 0;
  background: linear-gradient(90deg, var(--brand), var(--data-primary));
  border-radius: var(--radius);
  min-width: 3px;
}
/* The baseline is reference, not a second series: recessive, never a new hue. */
.bars__row[data-baseline="true"] .bars__fill {
  background: var(--data-flat);
}

/**
 * Diverging bars, for the one measure that can go below zero: population change.
 *
 * Loss is drawn in the caution hue rather than the data green, because on this
 * chart the sign is the finding. It is not a second series — no chart here ever
 * shows growth and loss as two categories — so it does not violate the rule
 * against a second hue beside the primary. The zero line carries the meaning,
 * and the printed value on every bar states the sign regardless of color, so
 * nothing depends on seeing the difference.
 */
.bars[data-diverging="true"] .bars__fill[data-negative="true"] {
  background: linear-gradient(90deg, var(--caution), #b8791f);
}
.bars__row[data-baseline="true"] .bars__fill[data-negative="true"] {
  background: var(--data-flat);
}

.bars__zero {
  position: absolute; top: -2px; bottom: -2px;
  width: 1px; margin-left: -0.5px;
  background: var(--rule-strong);
  z-index: 1;
}

.bars__value {
  font-size: var(--t-sm); font-weight: 600;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.bars__row[data-baseline="true"] .bars__value { font-weight: 400; color: var(--ink-2); }

.bars__moe { font-weight: 400; color: var(--ink-3); }

/* ------------------------------------------------------------ hero figures */

.hero {
  display: grid; gap: var(--s4);
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}

.hero__cell {
  padding: var(--s5) var(--s4);
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border-radius: var(--radius);
  border-top: 2px solid var(--brand);
}
/*
  Every tile carries the same navy rule. A copper "lead" tile was tried —
  first cell by default, overridable per group — and read as an inconsistency
  rather than an emphasis: the reader had no way to know the color was
  deliberate. The prose and the tile order already say which figure leads.
*/

.hero__label {
  margin: 0 0 var(--s2);
  font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.09em; color: var(--ink-3);
}

.hero__value {
  margin: 0;
  font-family: var(--display);
  font-size: var(--t-xl); font-weight: 500;
  letter-spacing: -0.025em; line-height: 1.05;
  font-variant-numeric: tabular-nums;
}

.hero__sub { margin: var(--s2) 0 0; font-size: var(--t-sm); color: var(--ink-2); }

/* ---------------------------------------------------------------- findings */

/**
 * A finding is an interpretation, so it is set apart from the figures it rests
 * on. Icon plus word plus tint — three signals, so the meaning survives
 * grayscale printing and color-vision deficiency alike.
 */
/*
  One panel, not a pile. Findings used to render as separate floating tinted
  boxes, and a section with four or five of them ended in a wall of rounded
  rectangles that all shouted equally. Grouped into a single bordered panel
  with hairline dividers they read as one set of readings — scan the icons
  and labels down the rail, stop on the row that matters.
*/
.findings {
  display: flex; flex-direction: column; gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}

.finding {
  display: flex; gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-left: 3px solid var(--note);
  background: var(--note-tint);
  font-size: var(--t-sm);
}
.finding[data-kind="good"] { border-color: var(--good); background: var(--good-tint); }
.finding[data-kind="caution"] { border-color: var(--caution); background: var(--caution-tint); }
.finding[data-kind="serious"] { border-color: var(--serious); background: var(--serious-tint); }

.finding__icon { flex: none; margin-top: 3px; color: var(--note); }
.finding[data-kind="good"] .finding__icon { color: var(--good); }
.finding[data-kind="caution"] .finding__icon { color: var(--caution); }
.finding[data-kind="serious"] .finding__icon { color: var(--serious); }

.finding__body { min-width: 0; }

.finding__kind {
  display: block; margin-bottom: var(--s1);
  font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.09em;
  color: var(--note);
}
.finding[data-kind="good"] .finding__kind { color: var(--good); }
.finding[data-kind="caution"] .finding__kind { color: var(--caution); }
.finding[data-kind="serious"] .finding__kind { color: var(--serious); }

.finding p { margin: 0; color: var(--ink); line-height: 1.55; }

/* ------------------------------------------------------------------ tables */

.tableWrap { overflow-x: auto; }

.table { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }

.table th, .table td {
  padding: var(--s3);
  text-align: left; vertical-align: baseline;
  border-bottom: 1px solid var(--rule);
}
.table th:first-child, .table td:first-child { padding-left: 0; }
.table th:last-child, .table td:last-child { padding-right: 0; }

.table thead th {
  font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.09em;
  color: var(--ink-3);
  border-bottom: 1px solid var(--rule-strong);
  white-space: nowrap;
}

.table .num { text-align: right; font-variant-numeric: tabular-nums; }
.table tbody th { font-weight: 400; color: var(--ink); }
.table tbody tr:hover td, .table tbody tr:hover th { background: var(--panel); }

.table .v { font-weight: 600; }


/* The reading column: a word, tinted, never color alone. */
/* Names the comparison where it is not simply the state, e.g. an equal-sized
   circle of average state ground. Small, so it annotates rather than competes. */
.glance__sub {
  display: block;
  font-size: var(--t-xs); color: var(--ink-3); font-weight: 400;
  font-variant-numeric: normal; line-height: 1.3; margin-top: 1px;
}

.glance__read { font-size: var(--t-sm); color: var(--ink-2); }
.glance__read[data-tone="above"] { color: var(--good); font-weight: 600; }
.glance__read[data-tone="below"] { color: var(--caution); font-weight: 600; }
.glance__read[data-tone="level"] { color: var(--ink-2); }
.glance__read[data-tone="none"] { color: var(--ink-3); }

/* ------------------------------------------------------------------ charts */

.chartSvg { width: 100%; height: auto; display: block; overflow: visible; }

.chartSvg .bar { fill: var(--data-primary); }
.chartSvg .bar:hover { fill: var(--brand); }
.chartSvg .stateLine { fill: none; stroke: var(--ink-3); stroke-width: 1.5; stroke-dasharray: 3 2; }
.chartSvg .axis { stroke: var(--rule-strong); stroke-width: 1; }
.chartSvg .marker { stroke: var(--data-second); stroke-width: 1.5; }
.chartSvg .markerText { fill: var(--data-second); font-size: 10px; font-weight: 700; }
.chartSvg .tickText { fill: var(--ink-3); font-size: 10px; }

.legend {
  list-style: none; display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5);
  margin: var(--s3) 0 0; padding: 0;
  font-size: var(--t-sm); color: var(--ink-2);
}
.legend li { display: flex; align-items: center; gap: var(--s2); }
.legend .dot { width: 10px; height: 10px; border-radius: 2px; background: var(--data-primary); flex: none; }
.legend .dot--line {
  height: 0; width: 14px; border-radius: 0;
  border-top: 2px dashed var(--ink-3); background: none;
}

figure { margin: 0; }
figcaption { margin-top: var(--s3); font-size: var(--t-sm); color: var(--ink-3); line-height: 1.55; }

/* The three map figures (ground, population, competition). On screen the
   figure already fills the column, same as any other figure, so the map and
   its caption already share a width with no rule needed here — the print
   block below is where the figure gets constrained and centered. */
.figure--map { width: 100%; }

/* The apportionment figure — method illustration, not a headline chart. */
.figure { display: block; width: 100%; height: auto; background: var(--panel); border-radius: var(--radius); }
.figure__bg { fill: var(--data-primary); stroke: var(--sheet); stroke-width: 0.7; }
.figure__bg:hover { stroke: var(--ink); stroke-width: 1.4; }
.figure__ring { fill: none; stroke: var(--ink-3); stroke-width: 1; stroke-dasharray: 4 3; }
.figure__ring--focus { stroke: var(--ink); stroke-width: 2; stroke-dasharray: none; }
.figure__ringLabel { font-size: 10px; fill: var(--ink-2); font-weight: 700; }
.figure__site { fill: var(--data-second); stroke: var(--sheet); stroke-width: 2.5; }

/* --------------------------------------------------------------------- map */

.map {
  /* The three cached map images run approximately 1.49-1.52 (640x430 for two
     of them, 1280x840 for the CompetitionSheet site map — see the SiteMap
     call sites in Report.tsx). A fluid width with a fixed height can't track
     each image's exact ratio at every viewport width, so this box follows
     640/430 — the dominant case — instead of fighting it: full-width, height
     derived, never stretched. object-fit: contain (below) is what absorbs
     the few-percent difference for the map whose source isn't exactly this
     ratio, via slight letterboxing rather than distortion. */
  width: 100%; aspect-ratio: 640 / 430; height: auto;
  border: 1px solid var(--rule); border-radius: var(--radius);
  overflow: hidden; background: var(--panel);
  /* Backstop for the failed-image state below, which sets an explicit height
     instead of participating in the aspect-ratio box. */
  object-fit: contain;
}
.map--failed {
  display: flex; flex-direction: column; justify-content: center; gap: var(--s3);
  padding: var(--s6); text-align: center;
}
.map--failed p { margin: 0; font-size: var(--t-sm); color: var(--ink-2); max-width: 34em; align-self: center; }
.map__coords { color: var(--ink-3); font-size: var(--t-xs); }

/* ------------------------------------------------------------- grade gauge */

.grade {
  display: flex; align-items: center; gap: var(--s6);
  padding: var(--s6);
  border-radius: var(--radius-lg);
  background: linear-gradient(150deg, var(--panel), var(--panel-2));
  border: 1px solid var(--rule);
}
.grade__gauge { flex: none; }

.grade__headline {
  margin: 0 0 var(--s2);
  font-family: var(--display);
  font-size: var(--t-lg); font-weight: 500;
  line-height: 1.25; letter-spacing: -0.015em;
}
.grade__body { margin: 0; font-size: var(--t-sm); color: var(--ink-2); line-height: 1.6; }

/* A scale showing where the figure sits, with its reference band named. */
.scale { margin-top: var(--s5); }
.scale__track {
  position: relative; height: 0.625rem;
  background: linear-gradient(90deg, var(--panel-2), var(--brand));
  border-radius: 999px;
}
.scale__pin {
  position: absolute; top: -6px; width: 2px; height: 22px;
  background: var(--ink); border-radius: 1px;
}
.scale__ticks {
  display: flex; justify-content: space-between;
  margin-top: var(--s2); font-size: var(--t-xs); color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------- competitors */

/**
 * The competitor list.
 *
 * Three columns: a numbered pin that keys the row to the map above, the practice
 * itself, and a fixed statistics column. The statistics column is fixed-width and
 * right-aligned on purpose — distance and review counts are the two things a reader
 * compares down the list rather than within a row, and comparison needs a shared
 * baseline to run down.
 */
.offices { list-style: none; margin: 0; padding: 0; }

.office {
  display: grid;
  grid-template-columns: 1.875rem minmax(0, 1fr) 8.5rem;
  gap: var(--s4);
  padding: var(--s4) 0;
  border-bottom: 1px solid var(--rule);
  align-items: start;
}

/*
  The certainty ring, which replaced the numbered disc.

  The disc carried no information — a position in a list — while the row's two
  real facts were split between a green tag printed on nearly every row and a
  gray chip almost nobody saw. Geometry and both fills now live in rings.tsx;
  this only reserves the space the disc held, so the row grid is unchanged.
*/
.office__ring { width: 1.875rem; height: 1.875rem; display: block; }

.office__body { min-width: 0; }

.office__name {
  margin: 0;
  font-size: var(--t-base); font-weight: 600;
  line-height: 1.3;
}

/* The category, set as a label rather than as prose in a run-on line. */
.office__kind {
  margin: 3px 0 0;
  font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--brass);
}
/* General practices are the ones that drive the saturation figure; specialists
   are context. The distinction is carried by hue, and by the word itself. */
.office__kind[data-general="false"] { color: var(--ink-3); }

/* Marks a type we defaulted to rather than established. Quiet, because it is a
   caveat on one row and not a warning about the practice. */
/*
  What established the type, run straight on after it.

  Deliberately not a chip. The old gray pill read "inferred" and looked like a
  warning badge, which is the wrong tone for a row that is usually fine — and it
  never said what the judgement rested on. Set as quiet lowercase after a dot
  separator it reads as part of the label: "ORTHODONTICS · address only".
*/
.office__basis {
  color: var(--ink-3);
  font-weight: 600; letter-spacing: 0.02em;
  text-transform: none;
  cursor: help;
}
.office__basis::before {
  content: "\00b7";
  margin: 0 0.4em;
  color: var(--rule-strong);
}

.office__addr {
  margin: 3px 0 0;
  font-size: var(--t-sm); color: var(--ink-2);
}

.office__evidence {
  margin: var(--s2) 0 0;
  font-size: var(--t-xs); color: var(--ink-3);
  line-height: 1.5; max-width: 44em;
}

.office__stats {
  display: flex; flex-direction: column;
  align-items: flex-end; gap: 3px;
  text-align: right;
}

.office__distance {
  font-family: var(--display);
  font-size: var(--t-md); font-weight: 500;
  color: var(--ink); font-variant-numeric: tabular-nums;
  line-height: 1;
}
.office__distance abbr {
  font-family: var(--text);
  font-size: var(--t-xs); font-weight: 600;
  color: var(--ink-3); text-decoration: none;
  margin-left: 2px;
}

.office__reviews {
  font-size: var(--t-xs); color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.office__reviews b { font-weight: 700; color: var(--ink); }

.office__rating {
  display: inline-flex; align-items: center; gap: 2px;
  color: var(--brass); font-weight: 700;
}

.office__noreviews { color: var(--ink-3); font-style: italic; }



/* Narrow screens: the statistics column moves under the practice rather than
   squeezing the name into two words a line. */
@media (max-width: 560px) {
  .office { grid-template-columns: 1.875rem minmax(0, 1fr); }
  .office__stats {
    grid-column: 2;
    flex-direction: row; align-items: center;
    gap: var(--s3); flex-wrap: wrap;
    margin-top: var(--s2);
  }
}

.tag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.07em;
  padding: 3px var(--s3); border-radius: 999px;
  background: var(--panel-2); color: var(--ink-2);
  white-space: nowrap;
}
.tag[data-confidence="verified"] { background: var(--good-tint); color: var(--good); }
.tag[data-confidence="unconfirmed"] { background: var(--caution-tint); color: var(--caution); }
.tag[data-confidence="doubtful"] { background: var(--serious-tint); color: var(--serious); }

/* The confidence key: label, then what earns it. */
.confKey {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s2) var(--s4); margin: var(--s3) 0 0;
}
.confKey dt { margin: 0; }
.confKey dd { margin: 0; font-size: var(--t-sm); color: var(--ink-2); }

@media (max-width: 44rem) {
  .confKey { grid-template-columns: 1fr; gap: var(--s1); }
  .confKey dd { margin-bottom: var(--s3); }
}

/* ------------------------------------------------------- excluded listings */

/**
 * What was found nearby and left out, with the reason for each.
 *
 * Closed by default and opened in print. On most reports these are pharmacies
 * and dental labs that nobody needs to read; on some, one of them is a real
 * practice, and only a reader who knows the market can tell. The count alone
 * could not be checked, and every exclusion moves the office count.
 *
 * Styled as an aside, not a finding: the tinted-box vocabulary is reserved for
 * statements about this market's data, and this is the document explaining its
 * own working.
 */
.excluded {
  margin: var(--s5) 0 0;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--sheet);
}

.excluded__summary {
  cursor: pointer;
  padding: var(--s3) var(--s4);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2) var(--s3);
  font-size: var(--t-sm); color: var(--ink-2);
  border-radius: var(--radius);
}
.excluded__summary b {
  font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--brass);
}
.excluded__summary::-webkit-details-marker { display: none; }
/* The disclosure arrow, drawn rather than borrowed, so it turns with the state. */
.excluded__summary::after {
  content: "";
  margin-left: auto;
  width: 0.5rem; height: 0.5rem;
  border-right: 1.5px solid var(--ink-3);
  border-bottom: 1.5px solid var(--ink-3);
  transform: rotate(45deg);
  transition: transform 120ms ease;
}
.excluded[open] .excluded__summary::after { transform: rotate(-135deg); }
.excluded__summary:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 2px;
}

.excluded__lede {
  margin: 0;
  padding: 0 var(--s4) var(--s3);
  font-size: var(--t-sm); color: var(--ink-2);
  max-width: 52em;
}

.excluded__list {
  list-style: none;
  margin: 0; padding: 0;
  border-top: 1px solid var(--rule);
}
.excluded__list li {
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--rule);
}
.excluded__list li:last-child { border-bottom: 0; }

.excluded__name {
  margin: 0;
  display: flex; align-items: baseline; gap: var(--s3);
  font-size: var(--t-sm); font-weight: 600; color: var(--ink);
}
.excluded__dist {
  margin-left: auto;
  font-size: var(--t-xs); font-weight: 600; color: var(--ink-3);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.excluded__addr {
  margin: 2px 0 0;
  font-size: var(--t-xs); color: var(--ink-2);
}
.excluded__reason {
  margin: 3px 0 0;
  font-size: var(--t-xs); color: var(--ink-3);
  line-height: 1.5; max-width: 46em;
}

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

/* --------------------------------------------------------------- checklist */

/**
 * Numbered because this genuinely is a sequence — you stand at the road before
 * you read the lease. If the order stopped carrying meaning, the numbers should
 * go.
 */
.checklist { list-style: none; margin: 0; padding: 0; counter-reset: check; }

/**
 * Two columns: the number, then everything else.
 *
 * The heading and body must be wrapped in `.checklist__text` in the markup. As
 * separate children they became separate grid items, and the body wrapped into
 * the 2rem number column one word per line. `align-items: start` keeps the
 * number level with the heading instead of centered against the whole entry.
 */
.checklist li {
  counter-increment: check;
  display: grid; grid-template-columns: 2rem minmax(0, 1fr);
  align-items: start;
  gap: var(--s4);
  padding: var(--s4) 0;
  border-bottom: 1px solid var(--rule);
}
.checklist__text { min-width: 0; }
.checklist li::before {
  content: counter(check, decimal-leading-zero);
  font-size: var(--t-sm); font-weight: 700;
  color: var(--brass); font-variant-numeric: tabular-nums;
  padding-top: 2px;
}
.checklist h4 {
  margin: 0 0 var(--s1);
  font-family: var(--display);
  font-size: var(--t-md); font-weight: 500; letter-spacing: -0.01em;
}
.checklist p { margin: 0; font-size: var(--t-sm); color: var(--ink-2); max-width: 42em; line-height: 1.6; }

/* An unnumbered variant, for lists whose order carries nothing. */
.checklist--plain { counter-reset: none; }
.checklist--plain li { grid-template-columns: 1fr; }
.checklist--plain li::before { content: none; }

/* ---------------------------------------------------------------- appendix */

/**
 * Sourcing lives here and nowhere else. Every figure remains traceable — that is
 * non-negotiable and this appendix is complete — but a reader working out whether
 * to sign a lease should not have to walk past a wall of provenance between two
 * sections about their market.
 */
.appendix { font-size: var(--t-sm); color: var(--ink-2); }
.appendix h4 {
  margin: var(--s6) 0 var(--s3);
  font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.09em; color: var(--brass);
}
.appendix p { margin: 0 0 var(--s3); max-width: 42em; line-height: 1.6; }
.appendix ul { margin: 0; padding-left: var(--s4); }
.appendix li { margin-bottom: var(--s2); line-height: 1.55; }

.srcTable { font-size: var(--t-xs); }
.srcTable td, .srcTable th { vertical-align: top; }

/* The dataset's formal name, under the publisher. Present for citation, sized so
   it does not compete with the row it belongs to. */
.srcTable__set {
  display: block; margin-top: 2px;
  font-weight: 400; color: var(--ink-3);
  font-size: 0.92em; line-height: 1.4;
}

/**
 * Labeled notes: a term in the margin, its explanation beside it.
 *
 * The appendix used to run method prose in paragraphs and disclosures in a
 * bulleted list. Neither is scannable, and an appendix is read by someone looking
 * for one answer rather than reading start to finish — so the answers get
 * headings. Collapses to a single column where the margin term will not fit.
 */
.notes {
  display: grid;
  grid-template-columns: 12rem minmax(0, 1fr);
  gap: var(--s3) var(--s5);
  margin: 0 0 var(--s5);
}
.notes dt {
  font-weight: 700; color: var(--ink-1);
  font-size: var(--t-xs); text-transform: uppercase; letter-spacing: 0.07em;
  padding-top: 2px;
}
.notes dd { margin: 0; line-height: 1.6; max-width: 44em; }
/* A note with no term spans both columns rather than leaving a hole. */
.notes dt + dd { grid-column: 2; }
.notes dd:first-child, .notes dd + dd { grid-column: 1 / -1; }

@media (max-width: 640px) {
  .notes { grid-template-columns: 1fr; gap: var(--s1) 0; }
  .notes dd, .notes dt + dd { grid-column: 1; }
  .notes dd { margin-bottom: var(--s3); }
}

.appendix__stamp {
  margin-top: var(--s5); padding-top: var(--s3);
  border-top: 1px solid var(--rule);
  font-size: var(--t-xs); color: var(--ink-3);
}

/* ------------------------------------------------------- table of contents */


.toc {
  position: fixed;
  top: 5.5rem;
  /* Right edge of the column sits just left of the sheet. */
  left: calc(50% - (var(--sheet-w) / 2) - 1.5rem);
  width: 12rem;
  transform: translateX(-100%);
  max-height: calc(100vh - 8rem);
  overflow-y: auto;
  font-size: var(--t-sm);
  z-index: 10;
}

.toc__heading {
  margin: 0 0 var(--s3);
  font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--brass);
}

.toc__list { list-style: none; margin: 0; padding: 0; }

.toc__link {
  display: grid;
  grid-template-columns: 1.5rem minmax(0, 1fr);
  gap: var(--s2);
  padding: var(--s2) var(--s2) var(--s2) 0;
  color: var(--ink-3);
  text-decoration: none;
  border-left: 2px solid transparent;
  padding-left: var(--s3);
  transition: color 120ms ease, border-color 120ms ease;
}
.toc__link:hover { color: var(--ink); border-left-color: var(--rule-strong); }

/* The current section. aria-current carries the state, so the styling and the
   accessible name cannot disagree. */
.toc__link[aria-current="true"] {
  color: var(--ink);
  font-weight: 600;
  border-left-color: var(--brand);
}

.toc__no { color: var(--brass); font-weight: 700; font-size: var(--t-xs); padding-top: 2px; }
.toc__title { line-height: 1.35; }

/* No margin column to sit in: hide rather than crowd the document. */
@media (max-width: 78rem) {
  .toc { display: none; }
}

/* Exactly one contents list is visible at a time: the sidebar where there is
   room for it, the in-document list otherwise and always on paper. */
@media (min-width: 78.0625rem) {
  .contentsBlock { display: none; }
}
@media print {
  .contentsBlock { display: block !important; }
}

/* ----------------------------------------------------------------- landing */

.lede { padding: var(--s9) 0 var(--s8); }

.lede__eyebrow {
  margin: 0 0 var(--s4);
  font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.16em; color: var(--brass);
}

.lede h2 {
  margin: 0 0 var(--s5);
  font-family: var(--display);
  font-size: var(--t-4xl); font-weight: 500;
  letter-spacing: -0.032em; line-height: 1.02;
  max-width: 20em; text-wrap: balance;
}

.lede__sub {
  margin: 0 0 var(--s7);
  font-size: var(--t-md); color: var(--ink-2);
  max-width: 34em; line-height: 1.55;
}

.finder { display: flex; gap: var(--s2); flex-wrap: wrap; max-width: 36rem; }

.finder input {
  flex: 1 1 18rem; min-width: 0;
  padding: var(--s3) var(--s4);
  font: inherit; color: var(--ink);
  background: var(--sheet);
  border: 1px solid var(--rule-strong); border-radius: var(--radius);
  box-shadow: inset 0 1px 2px rgba(7, 49, 44, 0.04);
}
.finder input::placeholder { color: var(--ink-3); }
.finder input:focus { border-color: var(--brand); }

.btn {
  padding: var(--s3) var(--s5);
  font: inherit; font-weight: 600;
  color: #fff;
  background: linear-gradient(180deg, var(--deep-2), var(--deep));
  border: 1px solid var(--deep); border-radius: var(--radius);
  cursor: pointer; white-space: nowrap;
  transition: filter 120ms ease;
}
.btn:hover:not(:disabled) { filter: brightness(1.18); }
.btn:disabled {
  background: var(--data-flat); border-color: var(--data-flat);
  cursor: not-allowed; filter: none;
}

.btn--ghost {
  color: var(--brand); background: var(--sheet);
  border-color: var(--rule-strong);
}
.btn--ghost:hover:not(:disabled) { filter: none; border-color: var(--brand); background: var(--panel); }

.priceline { margin: var(--s4) 0 0; font-size: var(--t-sm); color: var(--ink-2); max-width: 40em; }
.priceline b { color: var(--ink); font-weight: 600; }

.contents { list-style: none; margin: 0; padding: 0; columns: 2; column-gap: var(--s7); font-size: var(--t-sm); }
.contents li {
  padding: var(--s3) 0; border-bottom: 1px solid var(--rule);
  break-inside: avoid; display: flex; gap: var(--s4);
}
.contents b { font-weight: 700; color: var(--brass); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------- states & messages */

.notice {
  padding: var(--s4);
  border-left: 3px solid var(--serious);
  background: var(--serious-tint);
  border-radius: var(--radius);
  font-size: var(--t-sm);
}
.notice strong { display: block; }
.notice span { color: var(--ink-2); }

/* The PDF download can fail on the report view itself, which has no other
   place to surface it — the topbar is too cramped and is hidden in print. */
.notice--pdf-wrap { padding-top: var(--s5); padding-bottom: 0; }

.working {
  display: flex; align-items: center; gap: var(--s4);
  padding: var(--s7) var(--s4); font-size: var(--t-sm); color: var(--ink-2);
}
.working i {
  width: 16px; height: 16px; flex: none;
  border: 2px solid var(--rule-strong); border-top-color: var(--brand);
  border-radius: 50%; animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* ---------------------------------------------------------------- footnote */

.foot {
  max-width: var(--sheet-w); margin: 0 auto;
  padding: var(--s5) var(--s4) var(--s8);
  font-size: var(--t-xs); color: var(--ink-3);
  display: flex; flex-wrap: wrap; gap: var(--s3) var(--s5);
}

/* ------------------------------------------------------------- small print */

@media (max-width: 44rem) {
  .sheet { padding: var(--s5) var(--s4) var(--s6); }
  .explain { grid-template-columns: 1fr; }
  .bars__row { grid-template-columns: 4.5rem minmax(0, 1fr) auto; }
  .contents { columns: 1; }
  .grade { flex-direction: column; align-items: flex-start; gap: var(--s5); }
  .lede { padding: var(--s6) 0 var(--s5); }
  .lede h2 { font-size: var(--t-2xl); }
  .sheet__title { font-size: var(--t-xl); }
  .office { grid-template-columns: 1.75rem minmax(0, 1fr); }
}

/*
  `.finder` is both the address form and the confirm screen's button row, and it
  had no rule at either width. Relying on flex-wrap left "Buy this report —
  $29.99" beside a ghost button on a 320px screen, and `.btn` is nowrap.
*/
@media (max-width: 30rem) {
  .finder { flex-direction: column; align-items: stretch; }
  .finder input,
  .finder .btn { width: 100%; }
}

/* ------------------------------------------------------------------- print */

/**
 * The same document, on paper. Because the report is already a stack of sheets,
 * printing removes the room and lets each sheet own a page — not a second layout
 * to keep in sync.
 */
/**
 * Print and PDF export.
 *
 * The controlling insight, and what was wrong before: a section is not a page.
 * Sections here run from roughly one to eight printed pages, and the old rules
 * asked every `.sheet` to `break-inside: avoid`. That request is unsatisfiable on
 * anything taller than a sheet of paper, so the engine discards it and breaks
 * wherever the page happens to end — mid-sentence, mid-chart, between a heading
 * and the figure it introduces. Asking for the impossible produced worse output
 * than asking for nothing.
 *
 * So: sections are allowed to flow across pages, and the atomic units are the
 * things that genuinely fit on one — a metric, a finding, a chart, a table row,
 * an office entry. Headings are glued to what follows them. Nothing else is
 * pinned, which leaves the engine free to break at the good places.
 */
@media print {
  @page { size: letter; margin: 0.5in 0.55in 0.55in; }

  html, body {
    background: #fff;
    font-size: 10pt;
  }

  .topbar, .finder, .btn, .toc, .notice--pdf-wrap { display: none !important; }

  .doc {
    display: block;
    /*
      The app's own measure, not the full paper width. Printing edge to edge
      scaled every chart up ~25% relative to its type and the page read as a
      stretched version of the screen; the whole point of sharing one layout
      is that the PDF is the app page, set on paper.
    */
    max-width: 42rem; margin: 0 auto; padding: 0;
  }

  /*
    Each section starts a page. `break-before` rather than `break-after`, so the
    last section does not push a blank final page — a detail that shows up in
    every PDF anyone forwards.
  */
  .sheet {
    border: 0; border-radius: 0; box-shadow: none;
    padding: 0;
    /*
      Sections flow. Forcing every section onto a fresh page was tried and
      re-tried with ever tighter packing, and the arithmetic never closes:
      sixteen forced breaks mean up to sixteen part-empty tail pages, and a
      reader flipping past them reads the whitespace as filler. Chapters in
      an analyst report run on; the section opener — marker, title,
      standfirst, running rule — is a stronger boundary than a page edge.
      Only the cover owns its page.
    */
    break-before: auto;
    break-inside: auto;
    break-after: auto;
  }

  /* Real air between one section's end and the next one's opener. */
  .sheet + .sheet { margin-top: 0.55in; }

  /* A section opener stays glued to the start of its own content. */
  .sheet__running, .sheet__marker { break-after: avoid; }

  .cover {
    padding: 0.9in 0.7in; border-radius: 0;
    min-height: 9.6in;
    break-after: page;
    /*
      The cover alone escapes the app-width column and takes the full paper
      width. Inside 42rem the display-size address set one word per line; a
      cover is a poster, not a column of text.
    */
    width: 7.4in;
    margin-inline: calc((100% - 7.4in) / 2);
  }

  /*
    Shrinking .map from 3.4in to 2.9in was tried here to reduce trailing
    whitespace before a forced page break, but it moved the page count by
    zero: page breaks in this document are pinned by other break-inside:
    avoid atoms (.metric, .finding, .explain, tables), not by map height, so
    a smaller map bought nothing and cost the reader detail on the artifact
    they're asked to trace ring geometry and road networks on. Left at 3.4in.
  */

  /* --- keep headings with their content --------------------------------- */

  h2, h3, h4,
  .sheet__title, .metric__title, .appendix h4, .checklist h4 {
    break-after: avoid;
    break-inside: avoid;
  }
  .sheet__title, .sheet__standfirst, .sheet__intro, .sheet__marker { break-before: avoid; }
  .sheet__running { break-after: avoid; }

  /* No single line stranded at the top or bottom of a page. */
  p, li, dd, td, th { orphans: 3; widows: 3; }

  /* --- atoms: things that fit on a page and must not be split ----------- */

  /*
    A metric is the explanation plus its chart plus how to read it. The damaging
    break is between the explanation and its chart, because the whole design
    premise is that the reader meets the explanation immediately before the
    number — so the title, explanation and bars are glued into one atom below.

    The "how to read" box, though, may fall to the next page. Keeping the whole
    metric atomic meant a metric that missed fitting by half an inch carried
    everything to a fresh page, and section-final pages printed more than half
    blank; a report is judged on paper by exactly those pages.
  */
  .metric { break-inside: auto; }
  .metric__title { break-after: avoid; }
  .explain { break-inside: avoid; break-after: avoid; }

  /*
    Print-only vertical compression. On screen the document breathes; on paper
    the same rhythm left two metrics per page at 9.9in where three need 9.8in —
    the near-miss behind most of the trailing whitespace. Roughly 12% tighter
    here turns two-metric pages into three-metric pages without changing type
    sizes or what any figure says.
  */
  .sheet__body { margin-top: var(--s5); }
  .sheet__body > * + * { margin-top: var(--s5); }
  .metric { padding-top: var(--s4); }
  .metric__title { margin-bottom: var(--s3); }
  .explain { margin-bottom: var(--s4); gap: var(--s3) var(--s6); }
  .howto { margin-top: var(--s3); }
  .table th, .table td { padding-block: var(--s2); }

  .explain,
  .bars,
  .hero,
  .hero__cell,
  .office,
  .grade,
  .scale,
  figure,
  .checklist li {
    break-inside: avoid;
  }

  /*
    Text-only guidance may flow across a page break like any paragraph — the
    tint and rail continue on the next page. Kept atomic, a callout that
    missed fitting by half an inch carried itself to a fresh page and left
    the section's last page nearly blank; a split paragraph is ordinary in a
    report, a blank page is not. Orphan/widow control above still applies.
  */
  .howto, .finding, .findings { break-inside: auto; }

  /* A caption belongs to the image above it. */
  figcaption { break-before: avoid; break-inside: avoid; }

  /*
    A disclosure has nothing to click on paper, and a reader given the exclusions
    on screen must not lose them in the PDF a lender reads.

    Chrome collapses a `<details>` by hiding its `::details-content` pseudo with
    `content-visibility: hidden`, which no amount of `display` on the children
    overrides — the first version of this rule used `display: revert` and put the
    summary line in the PDF with every row under it silently missing.

    This is the belt. The braces are in src/server/pdf.ts, which sets the `open`
    attribute before capture: that path does not depend on the Chromium build
    supporting `::details-content`, and the export runs on two different ones.
    Browser Cmd+P has no such hook, so the rule below is what serves it.
  */
  .excluded { break-inside: auto; }
  .excluded::details-content { content-visibility: visible !important; }
  .excluded__summary { list-style: none; cursor: auto; }
  .excluded__summary::after { display: none; }
  .excluded__list li { break-inside: avoid; }

  /* A term and its definition are one unit. */
  .notes dt { break-after: avoid; break-inside: avoid; }
  .notes dd { break-inside: avoid; }

  /* --- tables ----------------------------------------------------------- */

  /*
    Long tables may break, but the header repeats on each page and no single row
    is ever split — a half-row at a page edge is the clearest sign of a document
    that was never checked on paper.
  */
  .tableWrap { overflow: visible; }
  table { break-inside: auto; }
  thead { display: table-header-group; }
  tfoot { display: table-footer-group; }
  tr { break-inside: avoid; break-after: auto; }

  /* --- figures ---------------------------------------------------------- */

  /* The screen rule's fluid width would fill the whole print column (~7.4in),
     stretching the circular rings into ellipses again — see the map-shape
     fix. Cap the *figure* to the image's ratio (640/430 ≈ 1.49, ~5.06in at
     the 3.4in height .map already renders at) and center the figure, rather
     than centering the image and constraining the caption separately — the
     .map inside stays width: 100% (from the base rule) and the figcaption
     has no width rule of its own, so both simply inherit the figure's
     measure instead of two things that can drift apart. Scoped to
     .figure--map so the apportionment diagram and income histogram, which
     use plain <figure> at their own widths, are untouched. */
  .figure--map { width: 5.06in; margin-inline: auto; }
  img, svg { max-width: 100%; }

  /* A link is useless on paper unless its address is printed too. */
  .appendix a::after { content: " (" attr(href) ")"; font-size: 8pt; color: #555; }

  /*
    Backgrounds are information here, not decoration: the finding panels encode
    severity and the bars encode magnitude. Without this they print white and the
    charts become empty outlines.
  */
  .cover, .finding, .hero__cell, .grade, .tag, .bars__fill, .bars__track,
  .office__ring, .excluded, .excluded__list li, .howto, .glance__read, .sheet__marker {
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }
}
