/* ==========================================================================
   Cubie Health — Section Components
   Layered after theme.css. Section-level building blocks shared across pages.
   ========================================================================== */

/* ---------- CORNER TEXTURE ----------
   Same tile as .cube-field, so the boxes join the site's one texture system. */
/* The mask peak was 0.10, copied from .cube-field where 0.045 effective is
   right for a whole hero. On a small white card that is below the visible
   threshold — the texture computed but never actually showed. 0.55 peak
   (~0.27 effective) registers; the 68px tile reads as texture rather than one
   stray corner mark. */
/* The declaration itself is unchanged from the original .pillar/.door rule —
   same 68px tile, same 0.5 opacity, same masks. Only the classes carrying it
   moved, to .panel-tr and .panel-br, as the cards became shared panels. */
.panel-tr::after, .panel-br::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='68' height='68'%3E%3Crect x='17' y='17' width='34' height='34' rx='10' fill='none' stroke='%236E4CE0' stroke-width='1'/%3E%3C/svg%3E");
  opacity: 0.5;
  mask-image: radial-gradient(78% 78% at 100% 0%, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0.22) 42%, transparent 72%);
  -webkit-mask-image: radial-gradient(78% 78% at 100% 0%, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0.22) 42%, transparent 72%);
}
/* Bottom-right, so a card whose action sits at the foot reads on a diagonal.
   .panel-tr keeps the original top-right origin. */
.panel-br::after {
  mask-image: radial-gradient(78% 78% at 100% 100%, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0.22) 42%, transparent 72%);
  -webkit-mask-image: radial-gradient(78% 78% at 100% 100%, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0.22) 42%, transparent 72%);
}


/* ---------- INFORMATION PANEL ----------
   The shared shell for textured information cards. Deliberately shadowless: the
   border and the texture already separate it from the ground, and a shadow here
   would make it compete with the product panels, which are the only surfaces on
   the site that are meant to read as lifted. */
.panel {
  position: relative; overflow: hidden;
  background: var(--ground); border: 1px solid var(--border);
  border-radius: 16px; padding: var(--panel-pad); box-shadow: none;
}
.panel > * { position: relative; z-index: 1; }
.panel h3 { margin-bottom: 10px; }
.panel p { color: var(--ink-soft); line-height: 1.65; max-width: 64ch; }
.panel p + p { margin-top: 12px; }
.panel-grid { display: grid; gap: var(--gap-grid); }

/* ---------- BANNER ---------- */
.banner {
  background: var(--ink); color: #fff; border-radius: 20px;
  padding: var(--banner-pad); text-align: center;
}
.banner h2 { color: #fff; margin-bottom: 14px; }
.banner .rule-accent { margin-bottom: 18px; }
.banner p { color: var(--navy-soft); max-width: 58ch; margin: 0 auto 28px; font-size: 1.03rem; line-height: 1.65; }
/* White text needs a darker fill than --on-navy, which carries it at only 2.54:1.
   --primary gives 5.57. That fill is quiet against navy (3.07:1), so the ring
   supplies the 3:1 component boundary WCAG 1.4.11 asks for. */
.banner .btn-primary {
  background: var(--primary); color: #fff; box-shadow: none;
  border: 1px solid var(--on-navy);
}
.banner .btn-primary:hover { background: #fff; color: var(--primary); border-color: #fff; }

/* ---------- FORM ---------- */
/* Plain white, no texture. Every other card on the site carries the rounded
   square; a form needs an uninterrupted input surface, and pattern running
   behind fields is noise exactly where accuracy matters. */
.form-card {
  background: var(--ground); border: 1px solid var(--border);
  border-radius: 20px; padding: var(--form-pad); box-shadow: var(--shadow-card);
}
/* Single column by default. Name and email pair up only when there is genuine
   room for two 48px fields side by side. */
.form-row { display: grid; grid-template-columns: 1fr; gap: 18px; }
@media (min-width: 1100px) { .form-row { grid-template-columns: 1fr 1fr; } }
.field { display: flex; flex-direction: column; gap: 7px; margin-bottom: 18px; }
.field label { font-size: var(--fs-meta); font-weight: 600; color: var(--ink); }
.field .hint { font-size: var(--fs-meta); color: var(--ink-faint); font-weight: 400; display: block; margin-top: 2px; }
.field input, .field textarea, .field select {
  font-family: inherit; font-size: 1rem; padding: 13px 15px; min-height: 48px;
  border-radius: 11px; border: 1px solid var(--border);
  background: var(--bg-soft); color: var(--ink); width: 100%;
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none; border-color: var(--primary);
  box-shadow: 0 0 0 4px rgba(110, 76, 224, 0.12);
}
.field textarea { resize: vertical; min-height: 144px; }
.field .err { font-size: var(--fs-meta); color: #B02440; font-weight: 500; display: none; }
/* The intro sentence that explained this convention is gone; the marker
   carries it visually, and `required` on every one of those inputs is what
   assistive tech announces — so the asterisk is aria-hidden. It is a glyph,
   not a colour cue, so 1.4.1 holds without the red. #B02440 is 6.62:1. */
.field label .req { color: #B02440; margin-left: 3px; font-weight: 700; }
.field.invalid input, .field.invalid textarea, .field.invalid select { border-color: #B02440; }
.field.invalid .err { display: block; }

/* honeypot — hidden from people, visible to bots */
.hp { position: absolute; left: -9999px; opacity: 0; height: 0; overflow: hidden; }

.form-card .btn-primary { width: 100%; }

/* Both outcomes render inside the same padded panel the form occupied, so the
   surface does not jump when one replaces the other. Neither is shown until the
   server has actually answered. */
.form-success, .form-error { display: none; text-align: center; padding: 30px 10px; }
.form-success.shown, .form-error.shown { display: block; }
.form-success:focus, .form-error:focus, .form-success h2:focus { outline: none; }
.form-success .tick, .confirm .tick {
  width: 46px; height: 46px; border-radius: 50%; margin: 0 auto 16px;
  background: #E2F1EA; color: #12734F;
  display: flex; align-items: center; justify-content: center; font-size: 1.3rem; font-weight: 700;
}
.form-success h2, .form-error h2 { font-size: var(--fs-h3); margin-bottom: 8px; }
.form-success p, .form-error p { color: var(--ink-soft); }
.form-success p + p { margin-top: 14px; }
/* #B02440 is 6.62:1 on white. The panel is the error's only colour cue besides
   the heading, so it has to carry contrast on its own. */
.form-error h2 { color: #B02440; }

.form-status { margin-top: 12px; }

/* ---------- CONFIRMATION PAGE ---------- */
.confirm-section { padding: var(--hero-top) 0 64px; }
.confirm { max-width: 640px; margin: 0 auto; text-align: center; }
.confirm h1 { font-size: var(--fs-h2); margin-bottom: 16px; }
.confirm p { color: var(--ink-soft); margin-bottom: 28px; }

/* Between the hero H1 and the section H2: this page is a single task, not a
   pitch, so the headline is sized to introduce a form rather than to open a
   page. 44 / 40 / 36 across the three ranges. */
.contact-section { padding-top: var(--hero-top); }
.contact-h1 { font-size: 2.75rem; line-height: 1.12; margin-bottom: 16px; }
@media (max-width: 1099px) { .contact-h1 { font-size: 2.5rem; } }
@media (max-width: 599px)  { .contact-h1 { font-size: 2.25rem; } }

/* ---------- CONTACT LAYOUT ----------
   .contact-detail and its emoji chip were removed with C1: a globe glyph next to
   "United States" and an envelope next to the address were decoration standing
   in for information. The email is now a plain link in running text. */
.contact-grid { display: grid; grid-template-columns: 0.85fr 1.15fr; gap: 48px; align-items: start; }
.contact-email { font-size: var(--fs-body); color: var(--ink-soft); margin-bottom: 18px; }
.contact-patient { font-size: var(--fs-body); color: var(--ink-soft); }

/* ---------- RESPONSIVE ---------- */
@media (max-width: 1099px) {
  .contact-grid { grid-template-columns: 1fr; gap: 28px; }
}

/* ==========================================================================
   V1.1 — visuals
   Figures that make an argument visible. Hand-authored SVG, themed by tokens.
   ========================================================================== */

/* ---------- SECTION WASH ---------- */
.section-soft {
  background: linear-gradient(180deg, var(--ground) 0%, var(--bg-soft) 8%, var(--bg-soft) 92%, var(--ground) 100%);
}

/* ==========================================================================
   V1.2
   ========================================================================== */

/* ---------- BANNER ----------
   The cropped logo watermark read as a slip rather than a choice. Depth now comes
   from a violet glow, and the corner lattice uses the same rounded-square
   vocabulary as the hero motif so the page's bookends rhyme. */
.banner {
  position: relative; overflow: hidden;
  display: grid; grid-template-columns: 1fr auto; gap: 40px;
  align-items: center; text-align: left;
}
.banner > * { position: relative; z-index: 1; }
.banner p { margin: 0; max-width: 54ch; }
/* One wash, not two. The second stop used to sit at 92% 100% — directly behind
   the button — which is what pushed the fill's contrast down to 1.78:1. */
.banner::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: radial-gradient(75% 130% at 10% 0%, rgba(133,101,251,0.22) 0%, transparent 66%);
}
/* Same 104px tile and same 52/14 geometry as .cube-field in theme.css — the CTA
   is the dark version of the site's one texture, not a second texture. */
.banner::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='104' height='104'%3E%3Crect x='26' y='26' width='52' height='52' rx='14' fill='none' stroke='%23ffffff' stroke-width='1'/%3E%3C/svg%3E");
  opacity: 0.5;
  /* Confined to the right of the panel: the copy occupies the left ~55%, and
     texture running under running text is what makes a panel feel noisy. */
  mask-image: radial-gradient(62% 130% at 100% 0%, rgba(0,0,0,0.34) 0%, rgba(0,0,0,0.13) 45%, transparent 76%);
  -webkit-mask-image: radial-gradient(62% 130% at 100% 0%, rgba(0,0,0,0.34) 0%, rgba(0,0,0,0.13) 45%, transparent 76%);
}
.banner .btn { flex: none; }

@media (max-width: 1099px) {
  .banner { grid-template-columns: 1fr; gap: 24px; }
}
@media (max-width: 599px) {
  .banner .btn { width: 100%; }
}
/* ---------- HERO MOTIF ----------
   One mechanism, read three ways: a thread runs through a lattice of the brand's
   rounded square, and where it is attended, that cell lights and pulses. The
   weave itself is paint order only — no masks on the artwork, which is what made
   the logo soft. Below 1100 the FRAME carries a horizontal alpha gradient so the
   lattice and the thread fade into the page at the left and right edges instead
   of being cut flush; it softens a boundary overflow: hidden was already drawing
   there, and the artwork is untouched at every width.
   Drift is four staggered CSS cycles on a 48s loop; no JS, no observers, and the
   first still frame is already a complete composition. */
.hero-motif { margin: 0; }
.hero-motif svg { width: 100%; height: auto; overflow: visible; }

.hm-cell { fill: var(--bg-soft); stroke: var(--border); stroke-width: 1.8; opacity: var(--o); }
/* Presence recedes by distance from the thread's row, so the field reads as a
   composition with a focus rather than a uniform wireframe grid. The tier value
   lives in --o so the shimmer can oscillate AROUND it instead of replacing it. */
.hm-r0 { --o: 1; }
.hm-r1 { --o: 0.55; }
.hm-r2 { --o: 0.26; }
.hm-thread {
  fill: none; stroke: var(--primary); stroke-width: 3; opacity: 0.62;
  stroke-linecap: round;
}
/* Softening lives on stroke-opacity, not opacity. Element opacity applies to the
   fill too, which made the white knot 62% transparent and let the thread show
   straight through it. */
.hm-knot   { fill: var(--ground); stroke: var(--primary); stroke-width: 2.4; stroke-opacity: 0.62; }

.hm-lit .lit-box   { fill: var(--primary); stroke: var(--primary); stroke-width: 1.8; }
.hm-lit .lit-heart { fill: #fff; }
.hm-lit .lit-ring  { fill: none; stroke: var(--primary); stroke-width: 1.8;
                     transform-box: fill-box; transform-origin: center; }

/* Four candidates share one 48s loop, each lit for its own quarter.
   The first carries opacity:1 as its RESTING style and a negative delay, so it
   is already lit in the very first painted frame — at 0% of the keyframes every
   cell is transparent, which would otherwise show a motif with no heart in it
   (and that is exactly what a thumbnail or a paused tab captures). */
.hm-lit { opacity: 0; animation: hmDrift 20s var(--ease) infinite; }
.hm-lit:nth-of-type(1) { opacity: 1; animation-delay: -2.5s; }
.hm-lit:nth-of-type(2) { animation-delay:  2.5s; }
.hm-lit:nth-of-type(3) { animation-delay:  7.5s; }
.hm-lit:nth-of-type(4) { animation-delay: 12.5s; }
/* Each cell holds for its quarter and hands over mid-fade, so there is never a
   frame with nothing lit. Delays are 0/25/50/75% of the loop; the 24-26% ramp
   overlaps the next cell's 0-2% ramp. */
@keyframes hmDrift {
  0%    { opacity: 0; }
  2%    { opacity: 1; }
  24%   { opacity: 1; }
  26%   { opacity: 0; }
  100%  { opacity: 0; }
}
.hm-lit .lit-ring   { opacity: 0; animation: hmPulse 20s var(--ease) infinite; animation-delay: inherit; }
.hm-lit .lit-ring-2 { animation-name: hmPulse2; }
@keyframes hmPulse  { 0%,3% { opacity: 0.5; transform: scale(1); } 14%,100% { opacity: 0; transform: scale(1.5); } }
@keyframes hmPulse2 { 0%,6% { opacity: 0.4; transform: scale(1); } 18%,100% { opacity: 0; transform: scale(1.9); } }

/* Freeze on a single lit cell, no pulse — still a complete composition. */
@media (prefers-reduced-motion: reduce) {
  .hm-lit { animation: none; opacity: 0; }
  .hm-lit:nth-of-type(1) { opacity: 1; }
  .hm-lit .lit-ring { animation: none; opacity: 0.35; }
}

/* ---------- MOTIF MOTION ----------
   Five layers, one 5s clock, one story: a pulse travels the thread, flares each
   knot as it passes, arrives at a cell which wakes its neighbours and lights,
   and the heart beats while care is happening there. Amplitudes stay low on
   purpose — this should read as a mechanism, not as an animated banner. */

/* 1. Travelling pulse. The dasharray and offset are matched to the path's real
   arc length (718.05, measured by flattening the béziers in build/home.py), so
   the dash traverses the whole thread across the whole 5s cycle. The previous
   values overshot by 480 units, which idled 1.95s of every loop and — because
   the knot delays were hand-spaced against them — put the last knot 0.96s late. */
.hm-pulse {
  fill: none; stroke: var(--primary); stroke-width: 3.4; stroke-linecap: round;
  stroke-dasharray: 30 1005;
  animation: hmTravel 5s linear infinite;
}
@keyframes hmTravel {
  0%   { stroke-dashoffset: 30;     opacity: 0; }
  5%   {                            opacity: 0.85; }
  92%  {                            opacity: 0.85; }
  100% { stroke-dashoffset: -718.1; opacity: 0; }
}

/* 2. Knot flare — fires in x-order, timed to when the pulse reaches each one. */
/* Delays are emitted inline by build/home.py from each knot's path distance, so
   they cannot drift out of sync with the pulse if the geometry changes. */
.hm-knot { animation: hmFlare 5s var(--ease) infinite; }
@keyframes hmFlare {
  0%, 100% { stroke-opacity: 0.62; r: 6; }
  6%       { stroke-opacity: 1;    r: 7.4; }
  16%      { stroke-opacity: 0.62; r: 6; }
}

/* 3. Shimmer — the grey fill, not opacity. Opacity swung the strongest row
   1 -> 0.55, which reads as blinking rather than surface. Each cell carries its
   own duration AND delay, emitted inline: six phase classes across a six-column
   grid gave every row the identical sequence, which produced synchronised
   vertical bands. Varying duration matters as much as delay — with equal
   durations any set of cells re-syncs no matter how the delays are scattered. */
.hm-cell { animation-name: hmShimmer; animation-timing-function: ease-in-out; animation-iteration-count: infinite; }
@keyframes hmShimmer {
  0%, 100% { fill: var(--bg-soft); }
  50%      { fill: #EFEFF6; }
}

/* 4. Neighbour wake — the thread's row firms up as the pulse arrives. Neutral,
   not violet: nothing in the lattice should animate toward a hue.
   It rides its own overlay rect rather than a second animation on .hm-cell,
   because each cell's shimmer duration is emitted inline and would override any
   duration the shorthand tried to set for a second animation. */
.hm-wake {
  fill: none; stroke: transparent; stroke-width: 1.8;
  animation: hmWake 5s var(--ease) infinite;
}
@keyframes hmWake {
  0%, 66%, 100% { stroke: transparent; }
  82%           { stroke: #CFC9E4; }
}

/* 5. Heartbeat — a real double-beat, not a sine pulse. 1.00 -> 1.06 only. */
.hm-lit .lit-heart {
  transform-box: fill-box; transform-origin: center;
  animation: hmBeat 1.1s ease-out infinite;
}
@keyframes hmBeat {
  0%   { transform: scale(1); }
  9%   { transform: scale(1.06); }
  18%  { transform: scale(1); }
  27%  { transform: scale(1.045); }
  38%  { transform: scale(1); }
  100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .hm-pulse { animation: none; opacity: 0; }
  .hm-knot,
  .hm-cell,
  .hm-wake,
  .hm-lit .lit-heart { animation: none; }
  .hm-wake { stroke: transparent; }
  .hm-cell { opacity: var(--o); }
}

/* ==========================================================================
   V1.6 — mobile
   ========================================================================== */

/* The V1.6 mobile treatment was removed here with section 4.3. It hid the
   thread, pulse, knots, wake and lit cells below 960, flattened every cell to
   --o:1, and masked the whole figure diagonally as an absolutely positioned
   backdrop. The motif is now foreground content at every width, cropped to its
   middle band by .motif-frame rather than stripped of its layers. */

/* ==========================================================================
   V2 — homepage
   ========================================================================== */

/* ---------- HERO ---------- */
.hero-home .wrap { grid-template-columns: 1.1fr 0.9fr; gap: 48px; align-items: center; }
.hero-copy { max-width: 620px; }
/* --fs-meta is the floor for supporting copy, and this line goes to it so it
   stops competing with the two primary buttons above. min-height stays on the
   anchor: it is what holds the 44px touch target, and it is the one thing a
   smaller font would otherwise quietly destroy. */
.hero-patient { margin-top: 14px; font-size: var(--fs-meta); color: var(--ink-soft); }
.hero-patient a {
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--primary); font-weight: 600;
}

/* ---------- HERO MOTIF ----------
   Desktop shows the whole 560x500 artwork in the right column. The SVG, its
   paths, its group order and every inline duration and delay are exactly as
   they were — only the frame around it changes below 1100. */
.hero-visual { display: flex; flex-direction: column; }
.motif-frame svg { width: 100%; height: auto; overflow: visible; }

@media (max-width: 1099px) {
  /* theme.css already stacks .hero .wrap here, but .hero-home .wrap is the same
     specificity and lands later in the cascade, so it would keep its two columns
     and leave the motif beside the headline instead of below the actions. */
  .hero-home .wrap { grid-template-columns: 1fr; gap: 32px; }

  /* The artwork is not redrawn — a 2:1 window shows the band from y115 to y395
     of the original viewBox, which keeps the thread, both heart positions, the
     rings and the knots, and crops only the outermost lattice rows.
     115/500 = 23% of the SVG's own height. */
  /* The artwork leads, then the headline. Done with order rather than by moving
     the markup: DOM order stays copy-then-visual, so a screen reader still hears
     the h1 first, and nothing inside the motif is focusable. */
  .hero-visual { align-items: flex-start; order: -1; }
  /* 620px is .hero-copy's own cap, so the figure and the copy agree at every
     width below 1100 — both fill the wrap when it is narrower, both stop at 620
     when it is not. flex-start rather than centre because a shared LEFT edge
     with the headline and the buttons is what makes the figure read as placed
     on the page; centred, it sat 42px right of them at 768.
     The 350px cap that used to land here below 600 is gone: it made the 480px
     case the worst of the lot, 440 of copy against 350 of figure. */
  .hero-motif { width: min(620px, 100%); margin: 0; }
  .motif-frame { position: relative; overflow: hidden; aspect-ratio: 2 / 1; }
  /* The vertical fade is paint — the --o row tiers dim the outer rows and the
     2:1 window lands in the gaps between them. The horizontal one cannot be:
     the thread runs x 36.6 to 547.4, so dimming only cells would leave it at
     full strength running into a flush cut. Hence a mask, and only here.
     The stops are NOT symmetric, because the artwork is not centred in its own
     viewBox: cells span x 28-556 of 560, so the left inset is 28 units and the
     right is 4. A mirrored 8%/92% ramp took the right column from solid to 0.09
     across its own width and left the left one essentially untouched. 4%/14%
     puts the left column's outer edge at the same 0.10 the right one reaches,
     so the two ends fade by the same amount rather than by the same distance. */
  .motif-frame {
    -webkit-mask-image: linear-gradient(to right, transparent 4%, #000 14%, #000 92%, transparent 100%);
            mask-image: linear-gradient(to right, transparent 4%, #000 14%, #000 92%, transparent 100%);
  }
  .motif-frame svg { position: absolute; top: 0; left: 0; transform: translateY(-23%); }
}

/* ---------- BETWEEN-VISIT TIMELINE ----------
   The old figure carried its labels as SVG <text>, which no screen reader read
   as prose and no reader could enlarge. Same vocabulary — endpoints, violet
   markers, a connecting line — rebuilt as an ordered list. */
.timeline {
  list-style: none; margin: 0; padding: 0; position: relative;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--gap-grid);
}
/* One connector per step rather than a single rail across the container: a rail
   spanning the full width runs past the final marker and trails off to the edge,
   which reads as an unfinished line rather than a sequence that lands. Each
   connector starts just past its own marker and stops at the next one — the
   negative right offset is exactly the grid gap. */
.tl-step:not(:last-child)::after {
  content: ""; position: absolute; left: 22px; right: calc(var(--gap-grid) * -1);
  top: 16px; border-top: 2px dashed var(--border);
}
.tl-step { position: relative; z-index: 1; padding-top: 44px; }

/* The interval itself. A grid container's ::before is a grid item, so the band
   can be placed on the track system rather than measured against it — which is
   what keeps it aligned to steps 2 and 3 at any gap or container width. It is
   decorative by construction, so it needs no aria treatment: step 2 is already
   titled "Between visits" and names what the band encloses. */
.timeline::before {
  content: ""; grid-area: 1 / 2 / 2 / 4; z-index: 0;
  margin: -16px -12px; border-radius: 16px; background: var(--tint);
}
/* Every item is placed explicitly. The band is a grid item too, so with any of
   them auto-placed it would consume a cell and push the steps onto a second
   row instead of sitting behind them. */
.tl-step:nth-child(1) { grid-area: 1 / 1; }
.tl-step:nth-child(2) { grid-area: 1 / 2; }
.tl-step:nth-child(3) { grid-area: 1 / 3; }
.tl-step:nth-child(4) { grid-area: 1 / 4; }
.tl-marker {
  position: absolute; top: 9px; left: 0; width: 14px; height: 14px;
  border-radius: 50%; background: var(--ground);
  border: 2px solid var(--primary); box-sizing: border-box; z-index: 1;
}
/* The final step is the decision — filled, so the sequence lands somewhere. */
.tl-step:last-child .tl-marker { background: var(--primary); }
/* The two visits are posts, as they were in the original figure. */
.tl-step:first-child::before, .tl-step:last-child::before {
  content: ""; position: absolute; left: 6px; top: 0; width: 2px; height: 32px;
  background: var(--ink);
}
.tl-title { font-size: 1.0625rem; font-weight: 600; line-height: 1.35; margin-bottom: 6px; }
.tl-step p { color: var(--ink-soft); }

@media (max-width: 1099px) {
  .timeline { grid-template-columns: 1fr; gap: 0; }
  .tl-step:not(:last-child)::after {
    left: 13px; right: auto; top: 22px; bottom: -6px; width: 0;
    border-top: 0; border-left: 2px dashed var(--border);
  }
  /* Stacked, the band becomes one vertical block over steps 2 and 3. Its left
     edge stops at 36px so the dashed rail and the markers stay outside the
     tinted area, and the steps take a right inset so text does not run into
     its rounded edge. */
  /* 28px of row padding sits below step 3's last line, so the band is pulled up
     by that much less the 12px it should overhang: 28 - 12 = 16. At -12px it
     cleared the text by 40px and ran 12px into step 4. */
  .timeline::before { grid-area: 2 / 1 / 4 / 2; margin: -12px 0 16px 36px; }
  .tl-step:nth-child(1) { grid-area: 1 / 1; }
  .tl-step:nth-child(2) { grid-area: 2 / 1; }
  .tl-step:nth-child(3) { grid-area: 3 / 1; }
  .tl-step:nth-child(4) { grid-area: 4 / 1; }
  .tl-step { padding: 0 12px 28px 48px; }  /* 28px marker column + 20px gap */
  .tl-step:last-child { padding-bottom: 0; }
  .tl-marker { top: 4px; left: 7px; }
  .tl-step:first-child::before, .tl-step:last-child::before {
    left: 0; top: 10px; width: 28px; height: 2px;
  }
}

/* ---------- PRODUCT SURFACES ----------
   The shared shell behind every product illustration: the homepage Atlas and
   Compass pair, and the provider hero. Phase 3's dead-CSS sweep removed these
   base rules while REDESIGN-REVIEW recorded them as kept (V12), which left the
   scoped `flex-direction: column` on .mock-head a no-op — nothing established
   the flex context it needed. The role label and the product name rendered on
   one line and the cards had no card at all.

   Only product surfaces carry the stronger resting shadow; information panels
   (.panel) stay shadowless, so the two vocabularies read as different things. */
.mock {
  background: var(--ground); border: 1px solid var(--border);
  border-radius: 20px; padding: var(--mock-pad);
  box-shadow: var(--shadow-soft);
  /* .mock-row's and .mock-patient's badges need THIS card's width, not the
     window's — see the @container rules further down, after .resp-col. */
  container-type: inline-size;
}
.mock-head {
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  padding-bottom: 16px; margin-bottom: 20px;
  border-bottom: 1px solid var(--border);
}
.mock-title {
  font-family: var(--font-display); font-weight: 700;
  display: flex; align-items: center; gap: 8px;
}
/* 1em, not 24px: .mock-title is --fs-h3 inside .wf-pair and .hero-panel and
   base size elsewhere, so a fixed box would be wrong in two of three places. */
.mock-icon { flex: none; width: 1em; height: 1em; color: var(--primary-deep); }
/* Was the accent bar, which the patient block now carries. A tinted block
   separates itself, so the border-top that used to divide this from the card
   body goes with it. */
.mock-foot {
  margin-top: 16px; padding: 12px; border-radius: 12px;
  background: var(--tint);
  font-size: var(--fs-meta); line-height: 1.5; color: var(--ink-soft);
}

/* A label the interior is grouped under, not a second product name. */
.mock-sub {
  font-size: var(--fs-meta); line-height: 1.5; font-weight: 600;
  color: var(--ink-soft); margin-bottom: 4px;
}

/* One static status vocabulary. Nothing here is live, so nothing animates,
   nothing responds to a pointer, and the text carries the meaning on its own —
   colour only reinforces it. */
.mock-status {
  display: inline-flex; align-items: center;
  background: var(--tint); color: var(--primary-deep);
  border-radius: 999px; padding: 4px 10px;
  font-size: var(--fs-meta); font-weight: 600; line-height: 1.5;
}

/* Patient rows. The status is the third track, centred against the name and
   task rather than stacked under them — but only where there is room for it:
   see the 479 breakpoint below. */
.mock-row {
  display: grid; grid-template-columns: 32px 1fr auto; gap: 12px;
  align-items: center; padding: 16px 0;
  border-bottom: 1px solid var(--border);
}
.mock-row:last-of-type { border-bottom: 0; }
.mock-person { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; min-width: 0; }
.mock-avatar {
  width: 32px; height: 32px; border-radius: 50%; box-sizing: border-box;
  background: var(--tint); color: var(--primary-deep);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-meta); font-weight: 700; line-height: 1;
}
.mock-name { font-size: 1rem; font-weight: 600; color: var(--ink); }
.mock-task { font-size: 1rem; color: var(--ink-soft); }

/* Compass opens on the same patient the Atlas row selected, and says where that
   patient stands — the status belongs beside them, not at the foot of the card.
   The fill became the accent bar and .mock-foot took the fill: on the provider
   hero, which shows both blocks, two accent rules on one card read as the same
   device used twice.

   The ::before IS a grid item, which is why the 4px bar is the first track and
   why all four are declared — bar, avatar, name block, badge. */
.mock-patient {
  display: grid; grid-template-columns: 4px 32px 1fr auto; gap: 12px;
  align-items: center; padding: 12px 0; margin-bottom: 4px;
}
.mock-patient::before {
  content: ""; border-radius: 4px; align-self: stretch;
  background: linear-gradient(180deg, var(--accent-stops));
}
.mock-patient > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.mock-group { padding: 14px 0; border-bottom: 1px solid var(--border); }
.mock-group:last-of-type { border-bottom: 0; }
.mock-k {
  display: block; font-size: var(--fs-meta); line-height: 1.5;
  font-weight: 600; color: var(--ink); margin-bottom: 2px;
}
.mock-v { display: block; font-size: 1rem; color: var(--ink-soft); line-height: 1.55; }

/* ---------- NUMBERED STEP CIRCLE ----------
   One object, four sequences: the homepage workflow captions, the providers
   worked example, the partnership stages and the measurement steps. They were
   four near-copies of the same declarations, which is exactly how the ring came
   to sit on one of them and none of the others.

   The ring is static. The measurement circles it came from originally pulsed,
   all five forever, which animated an evaluation methodology without explaining
   anything about it. */
.step-n, .stage-n, .mstep-n, .wf-caps .wf-num, .wf-final .wf-num {
  width: 32px; height: 32px; border-radius: 50%; box-sizing: border-box;
  border: 2px solid var(--primary); background: var(--ground); color: var(--primary);
  display: flex; align-items: center; justify-content: center;
  font-size: 1rem; font-weight: 700; font-family: var(--font-display);
  box-shadow: 0 0 0 6px rgba(110, 76, 224, 0.08);
}
/* The last item is filled, so each sequence lands somewhere. */
.step:last-child .step-n, .stage:last-child .stage-n,
.mstep:last-child .mstep-n, .wf-final .wf-num {
  background: var(--primary); color: #fff;
}

/* ---------- CONNECTED WORKFLOW ---------- */
.workflow { margin: 0; }
.wf-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap-grid); align-items: stretch; }

/* Each surface is card + its own captions, so the captions travel with the card
   when the pair stacks rather than collecting at the bottom. */
.wf-surface { display: flex; flex-direction: column; min-width: 0; }
.wf-surface .mock { flex: 1; }

/* The four beats the product example is showing. The explanatory paragraphs
   that used to hang off each one are gone — the cards carry that now. */
.wf-caps { list-style: none; margin: 20px 0 0; padding: 0;
           display: flex; flex-direction: column; gap: 20px; }
/* --ink and 600, not --ink-soft: with the circle these are steps, and at
   --ink-soft they read as footnotes hanging off the card. 17.08:1. */
.wf-caps li { position: relative;
              display: grid; grid-template-columns: 28px 1fr; gap: 12px;
              align-items: center; font-size: var(--fs-meta); line-height: 1.5;
              font-weight: 600; color: var(--ink); }
/* The compact size .step-n already uses when its row is tight. */
.wf-caps .wf-num { width: 28px; height: 28px; font-size: 0.9rem; }
/* Anchored to its own row and stopping at the next circle — the same device the
   timeline, the worked example, the stages and the measurement steps all carry.
   13px centres a 2px line on a 28px circle. It runs circle edge to circle edge
   and crosses both rings, which are 8% alpha: stopping at the rings instead left
   a 2px stub that measured correct and rendered as nothing. */
.wf-caps li:not(:last-child)::after {
  content: ""; position: absolute; left: 13px; top: 28px; bottom: -20px;
  width: 0; border-left: 2px dashed var(--border);
}
.wf-role { font-size: var(--fs-meta); line-height: 1.5; color: var(--ink-soft); }
.wf-pair .mock-title { font-size: var(--fs-h3); letter-spacing: -0.015em; }
.wf-row { display: grid; grid-template-columns: 28px 1fr; gap: 12px; align-items: start;
          padding: 14px 0; border-bottom: 1px solid var(--border); }
.wf-row:last-child { border-bottom: 0; }
/* The bare numeral, for any .wf-num outside the two sequences above. */
.wf-num { color: var(--primary); font-weight: 700; font-family: var(--font-display); }
.wf-name { display: block; font-weight: 600; margin-bottom: 2px; }
.wf-row p, .wf-final p { color: var(--ink-soft); font-size: var(--fs-meta); line-height: 1.55; }
.wf-final {
  display: grid; grid-template-columns: 32px 1fr auto; gap: 12px; align-items: start;
  margin-top: 24px; padding: 20px; border-radius: 12px;
  background: var(--tint); border-left: 3px solid var(--primary);
}
/* Centred against the two-line block; the panel's other two items stay at the
   top, so the link overrides only itself. */
.wf-final .wf-more { margin-top: 0; align-self: center; }
/* The row needs 513px of panel content — 32px circle, 24px of gaps, a 260px
   floor for the paragraph and the link's 197px. Below 768 there is not that
   much, and at 375 the paragraph collapsed to a 38px column and wrapped to 17
   lines. 768 is the site's existing tablet boundary rather than a new number. */
@media (max-width: 767px) {
  .wf-final { grid-template-columns: 32px 1fr; }
  .wf-final .wf-more { grid-column: 2; margin-top: 14px; align-self: start; }
}
.workflow figcaption { margin-top: 16px; font-size: var(--fs-meta); color: var(--ink-faint); }
.wf-more { margin-top: 20px; }
/* min-height matches .hero-secondary a and .hero-patient a. This link never
   carried one — it measured 21.7px — which was a gap before it moved here.
   --primary is 4.94:1 on the panel's --tint, measured on the rendered page. */
.wf-more a { color: var(--primary); font-weight: 600; display: inline-flex;
             align-items: center; gap: 8px; min-height: 44px; }
.wf-more a svg { transition: transform 0.25s var(--ease); }
.wf-more a:hover svg { transform: translateX(5px); }

@media (max-width: 1099px) {
  .wf-pair { grid-template-columns: 1fr; }
}

/* ---------- INTERVENTION CARDS ----------
   These inherit the bottom-right texture and the arrow-hover that used to live
   on the late audience cards. */
.icards { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap-grid); }
.icard { display: flex; flex-direction: column; color: inherit;
         transition: transform 0.25s var(--ease), border-color 0.25s var(--ease), box-shadow 0.25s var(--ease); }
.icard:hover { transform: translateY(-3px); border-color: var(--primary); box-shadow: var(--shadow-card); }
.icard p { margin-bottom: 18px; }
/* --primary-deep, not --primary. The bottom-right texture reaches 0.562 of the
   card's width in from the corner, and on a narrow card the end of this label
   sits inside that: --primary measures 3.72 against the texture at peak, which
   fails AA. --primary-deep measures 4.91 there and 7.35 on the plain card. */
.icard .go {
  margin-top: auto; display: inline-flex; align-items: center; gap: 9px;
  color: var(--primary-deep); font-weight: 600; font-size: var(--fs-meta);
}
.icard .go svg { flex: none; transition: transform 0.25s var(--ease); }
.icard:hover .go svg { transform: translateX(5px); }
@media (max-width: 599px) { .icards { grid-template-columns: 1fr; } }
@media (prefers-reduced-motion: reduce) {
  .icard, .icard:hover { transform: none; }
  .icard .go svg, .icard:hover .go svg { transition: none; transform: none; }
  .wf-more a svg, .wf-more a:hover svg { transition: none; transform: none; }
}

/* ---------- RESPONSIBILITIES ----------
   Open columns with rules between them: three roles in one program, not three
   separate offers, so they are deliberately not cards. */
/* One panel with internal divisions rather than three floating columns: these
   are parts of a single shared program, so they should read as one object.
   Shadowless — it is an information panel, not a product surface. */
.resp-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 0;
  background: var(--ground); border: 1px solid var(--border); border-radius: 20px;
}
.resp-col { padding: 24px; min-width: 0; }
.resp-col + .resp-col { border-left: 1px solid var(--border); }
/* No arrows between these — a shared responsibility is not a handoff. The
   marker is the site's rounded-square motif at its smallest. */
.resp-col::before {
  content: ""; display: block; width: 12px; height: 12px;
  border-radius: 4px; background: var(--primary); margin-bottom: 12px;
}
.resp-col p { color: var(--ink-soft); }
.resp-close { margin-top: 28px; color: var(--ink-soft); max-width: 64ch; }
@media (max-width: 1099px) {
  .resp-grid { grid-template-columns: 1fr; }
  .resp-col + .resp-col { border-left: 0; border-top: 1px solid var(--border); }
}
@media (max-width: 599px) {
  .resp-col { padding: 20px; }
}

/* A right-hand badge needs room, and the room it needs is the CARD's, not the
   window's. Between 1100 and roughly 1160 the pair splits into two columns and
   each card is narrower than it was at 1099 — a viewport breakpoint cannot say
   that, and the 479px one this replaces was only ever a proxy for it.

   Both thresholds are measured natural widths plus margin, not guesses:
   the widest Atlas row (Morgan P.) needs 370px, the patient block 418px. */
@container (max-width: 400px) {
  .mock-row { grid-template-columns: 32px 1fr; align-items: start; }
  .mock-row .mock-status { grid-column: 2; justify-self: start; margin-top: 4px; }
}
@container (max-width: 440px) {
  .mock-patient { grid-template-columns: 4px 32px 1fr; align-items: start; }
  .mock-patient .mock-status { grid-column: 3; justify-self: start; margin-top: 4px; }
}

/* ==========================================================================
   V3 — provider and health-plan pages
   ========================================================================== */

/* ---------- HERO SECONDARY LINK + PANEL ---------- */
.hero-secondary { margin-top: 16px; }
.hero-secondary a {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px;
  color: var(--primary); font-weight: 600;
}
.hero-secondary a svg { transition: transform 0.25s var(--ease); }
.hero-secondary a:hover svg { transform: translateX(5px); }
.hero-panel { margin: 0; }
.hero-panel figcaption { font-size: var(--fs-meta); color: var(--ink-faint); margin: 10px 0 0; }
/* The product name is the panel's heading, so it takes the H3 scale. */
.hero-panel .mock-title { font-size: var(--fs-h3); letter-spacing: -0.015em; }

/* Stacked, the card would run the full column width and dwarf the copy above
   it. Capped and left-aligned to the text column instead, full width only once
   the viewport is narrow enough that a cap would leave an odd margin. */
@media (max-width: 1099px) {
  .hero .wrap { gap: 32px; }
  .hero-panel { max-width: 640px; }
}
@media (max-width: 599px) {
  .hero-panel { max-width: none; }
}


/* ---------- RESPONSIBILITY / SETUP PANELS ---------- */
.resp-panels { grid-template-columns: repeat(3, 1fr); }
.setup-grid  { grid-template-columns: 1fr 1fr; }
@media (max-width: 1099px) {
  .resp-panels { grid-template-columns: 1fr; }
  /* One card per row at this width, so the heading gets its own column rather
     than sitting on a line of its own above a very wide measure. */
  .split-panel { display: grid; grid-template-columns: 220px 1fr; gap: 28px; }
  .split-panel h3 { margin-bottom: 0; }
}
@media (max-width: 767px) {
  .split-panel { grid-template-columns: 1fr; gap: 10px; }
}
@media (max-width: 599px) { .setup-grid { grid-template-columns: 1fr; } }

/* ---------- WORKED EXAMPLE (numbered rows) ---------- */
.steps { list-style: none; margin: 0; padding: 0; }
.step {
  position: relative;
  display: grid; grid-template-columns: 44px 240px 1fr; gap: 0 20px;
  padding: 28px 0; align-items: start;
}
/* A background layer, not a border: both pseudo-elements are the rail. It
   starts at the rail's centre line — left 15 plus half its 2px — so the two
   meet as a T rather than the divider running out past the circle. Print and
   forced colors strip backgrounds, so theme.css gives it back as a border. */
.step:not(:last-child) {
  background: linear-gradient(var(--border), var(--border))
              bottom right / calc(100% - 16px) 1px no-repeat;
}
.step-h { font-size: var(--fs-h3); margin: 0; }
.step p { color: var(--ink-soft); }

/* Wide layout: the label column centres and the rail runs the whole list.
   Unlike the timeline — a closed sequence, where a line past the last marker
   reads as unfinished — the overrun here is the point: work precedes step 1 and
   continues after step 5, so the rail enters above the first circle and leaves
   below the last.

   Both halves are measured from their OWN row's centre, so unequal rows still
   join at the boundary. Anchoring to a fixed offset from the row top, as the
   stacked rule does, only works while the circle is a known distance from it. */
@media (min-width: 768px) {
  /* Self-cancelling: align-self does nothing to whichever item is tallest in
     its row. At wide desktop the heading wraps to two lines and is tallest, so
     this is a no-op there; on tablet a one-line heading centres against a
     three-line paragraph, which is what made it read as a mistake. */
  .step-n, .step-h, .step p { align-self: center; }
  /* 22px = half the 32px circle + its 6px ring. */
  .step::before, .step::after {
    content: ""; position: absolute; left: 15px; width: 0;
    border-left: 2px dashed var(--border);
  }
  .step::before { top: 0; bottom: calc(50% + 22px); }
  .step::after  { top: calc(50% + 22px); bottom: 0; }
}

/* Stacked: circles at the top of their row, and the line stops at the last one.
   This rule lives here rather than at the top level because it outranks the
   wide-layout rail on specificity — (0,2,1) against (0,1,1) — and a media query
   adds none. 56px clears the 28px row padding plus the 28px circle. */
@media (max-width: 767px) {
  .step { grid-template-columns: 32px 1fr; gap: 0 16px; }
  .step:not(:last-child) { background-size: calc(100% - 14px) 1px; }
  .step-h { grid-column: 2; }
  .step p { grid-column: 2; margin-top: 6px; }
  .step-n { width: 28px; height: 28px; font-size: 0.9rem; }
  .step:not(:last-child)::after {
    content: ""; position: absolute; left: 13px; top: 56px; bottom: -28px;
    width: 0; border-left: 2px dashed var(--border);
  }
}

/* ---------- ENGINE STRIP (provider page) ----------
   The compact, static counterpart to the engine factory. It lives here rather
   than in css/engine.css because the provider page loads neither that
   stylesheet nor the animation controller — this strip never moves. */
.engine-strip {
  display: grid; grid-template-columns: 1fr auto 1fr auto 1fr;
  gap: 12px; align-items: center;
  background: var(--tint); border: 1px solid var(--border);
  border-radius: 12px; padding: 20px; margin-bottom: 16px;
}
.engine-strip-step {
  font-size: 1rem; font-weight: 600; color: var(--ink);
  text-align: center; min-width: 0; overflow-wrap: break-word;
}
.engine-strip-arrow { display: flex; align-items: center; justify-content: center; color: var(--primary); }
.engine-strip-arrow svg { width: 24px; height: 8px; display: block; }
.engine-strip-copy { color: var(--ink-soft); max-width: 68ch; }
.engine-strip-link { margin-top: 10px; margin-bottom: 20px; }
.engine-strip-link a {
  color: var(--primary-deep); font-weight: 600;
  display: inline-flex; align-items: center; gap: 8px;
}
@media (max-width: 599px) {
  .engine-strip { grid-template-columns: 1fr; gap: 10px; }
  .engine-strip-arrow svg { width: 8px; height: 24px; transform: rotate(90deg); }
}

/* ---------- CALLOUT ----------
   The violet left rule on a tinted ground. One treatment, used where a section
   needs to say something plainly about its own limits. No icon, no motion. */
.callout {
  background: var(--tint); border-left: 3px solid var(--primary);
  border-radius: 12px; padding: 24px; max-width: 68ch;
}
.callout h3 { font-size: 1.125rem; font-weight: 700; margin-bottom: 8px; }
.callout p { color: var(--ink-soft); }
.callout p + p { margin-top: 10px; }
.steps + .callout, .covers-grid + .callout { margin-top: 28px; }
@media (max-width: 599px) { .callout { padding: 20px; } }

/* ---------- ASM FACT TILES ----------
   Three plain tiles. No isolated numerals and no percentage emphasis: the
   section is context near the foot of the page, not the page's argument. */
.facts {
  list-style: none; margin: 32px 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px;
}
.facts li {
  background: var(--ground); border: 1px solid var(--border);
  border-radius: 16px; padding: 24px; min-width: 0;
}
.fact-h {
  font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 700;
  letter-spacing: -0.015em; line-height: 1.25; margin-bottom: 8px;
}
.facts p { color: var(--ink-soft); }
.asm-close { margin-top: 32px; max-width: 68ch; }
.asm-close p { color: var(--ink-soft); }
@media (max-width: 1099px) { .facts { grid-template-columns: 1fr; } }

/* ---------- CARE-MODEL ICONS ----------
   Beside the product name, not above the role label: the icon labels the
   product, so it belongs on the same line as the thing it names. */
.care-grid h3 { display: flex; align-items: flex-start; gap: 12px; }
/* Optically centred on the FIRST line, not between both — one of the three
   headings wraps, and centring pulled its icon halfway down the card.
   The h3 line box is 1.25 x 24px, so 3px centres a 24px icon on it. */
.panel-icon { flex: none; width: 24px; height: 24px; margin-top: 3px; color: var(--primary-deep); }

/* ---------- REGULATORY PROSE LINK ---------- */
.prose-link { margin-top: 20px; }
.prose-link a { color: var(--primary); font-weight: 600; display: inline-flex; align-items: center; gap: 8px; }
.prose-link a svg { transition: transform 0.25s var(--ease); }
.prose-link a:hover svg { transform: translateX(5px); }
.source { font-size: var(--fs-meta); color: var(--ink-faint); margin-top: 20px; }

/* ---------- OPPORTUNITY PANEL ---------- */
.opp-title { font-size: var(--fs-h3); margin-bottom: 4px; }
.opp-row { display: grid; grid-template-columns: 12px 1fr; gap: 14px; align-items: start;
           padding: 16px 0; border-bottom: 1px solid var(--border); }
.opp-row:last-child { border-bottom: 0; }
/* Decorative: the heading carries the meaning, the square only marks the row. */
.opp-mark { width: 12px; height: 12px; border-radius: 4px; background: var(--primary); margin-top: 7px; }
.opp-row h3 { font-size: 1.0625rem; font-weight: 600; margin-bottom: 2px; }
.opp-row p { color: var(--ink-soft); font-size: var(--fs-meta); }

/* ---------- CARE MODEL ---------- */
.care-grid { grid-template-columns: repeat(3, 1fr); }
/* Plain text, not a pill: these name what each card is, and three chips in a row
   read as filters you could click. */
.panel-label { display: block; font-size: var(--fs-meta); color: var(--ink-soft); margin-bottom: 6px; }
@media (max-width: 1099px) { .care-grid { grid-template-columns: 1fr; } }

/* ---------- PARTNERSHIP STAGES ---------- */
.stages {
  list-style: none; margin: 0; padding: 0; position: relative;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gap-grid);
}
.stage:not(:last-child)::after {
  content: ""; position: absolute; left: 40px; right: calc(var(--gap-grid) * -1);
  top: 16px; border-top: 2px dashed var(--border);
}
.stage { position: relative; padding-top: 52px; }
.stage-n { position: absolute; top: 0; left: 0; z-index: 1; }
.stage p { color: var(--ink-soft); }
@media (max-width: 1099px) {
  .stages { grid-template-columns: 1fr; gap: 0; }
  .stage:not(:last-child)::after {
    left: 15px; right: auto; top: 40px; bottom: -12px; width: 0;
    border-top: 0; border-left: 2px dashed var(--border);
  }
  .stage { padding: 0 0 28px 52px; }
  .stage:last-child { padding-bottom: 0; }
  .stage-n { top: 0; left: 0; }
}

/* ---------- MEASUREMENT ----------
   The old chain hid its descriptions below 960 to keep five columns. Nothing
   substantive may be hidden at a breakpoint, so below 1100 it becomes a
   vertical list and every explanation stays on the page. */
.msteps {
  list-style: none; margin: 0; padding: 0; position: relative;
  display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--gap-grid);
}
.mstep:not(:last-child)::after {
  content: ""; position: absolute; left: 40px; right: calc(var(--gap-grid) * -1);
  top: 16px; border-top: 2px dashed var(--border);
}
.mstep { position: relative; padding-top: 52px; }
.mstep-n { position: absolute; top: 0; left: 0; z-index: 1; }
.mstep h3 { font-size: 1.0625rem; font-weight: 600; margin-bottom: 6px; }
.mstep p { color: var(--ink-soft); font-size: var(--fs-meta); line-height: 1.55; }
@media (max-width: 1099px) {
  .msteps { grid-template-columns: 1fr; gap: 0; }
  .mstep:not(:last-child)::after {
    left: 15px; right: auto; top: 40px; bottom: -10px; width: 0;
    border-top: 0; border-left: 2px dashed var(--border);
  }
  .mstep { padding: 0 0 26px 52px; }
  .mstep:last-child { padding-bottom: 0; }
  .mstep p { font-size: var(--fs-body); line-height: 1.65; }
}

.covers { margin-top: 40px; padding-top: 40px; border-top: 1px solid var(--border); }
.covers-title { font-size: var(--fs-h3); margin-bottom: 24px; }
.covers-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 32px; }
.cover-col h3 { font-size: 1.0625rem; font-weight: 600; margin-bottom: 6px; }
.cover-col p { color: var(--ink-soft); }
.covers-note { margin-top: 28px; font-size: var(--fs-meta); color: var(--ink-faint); max-width: 64ch; }
@media (max-width: 1099px) {
  .covers-grid { grid-template-columns: 1fr; gap: 0; }
  .cover-col + .cover-col { border-top: 1px solid var(--border); padding-top: 22px; margin-top: 22px; }
}

/* ==========================================================================
   V4 — utility documents
   ========================================================================== */

/* Privacy and accessibility are read, not scanned: one column, no cards, no
   texture, and a measure that does not run past what the eye tracks. */
.doc-section { padding-top: var(--hero-top); }
.doc { max-width: 800px; }
.doc h1 { font-size: var(--fs-h2); margin-bottom: 16px; }
.doc-lede { font-size: var(--fs-lede); color: var(--ink-soft); line-height: 1.6; }
.doc h2 { font-size: var(--fs-h3); margin: 32px 0 12px; }
.doc p { color: var(--ink-soft); max-width: 64ch; margin-bottom: 12px; }
.doc a { color: var(--primary); text-decoration: underline; text-underline-offset: 3px; }
.doc-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.doc-list li { position: relative; padding-left: 22px; color: var(--ink-soft); max-width: 64ch; }
.doc-list li::before {
  content: ""; position: absolute; left: 0; top: 11px;
  width: 8px; height: 8px; border-radius: 50%; background: var(--primary);
}

.notfound { padding-top: var(--hero-top); padding-bottom: 64px; text-align: center; }
.notfound .wrap > * { margin-left: auto; margin-right: auto; }
.notfound h1 { font-size: var(--fs-h2); max-width: 18ch; margin-bottom: 16px; }
.notfound .lede { max-width: 46ch; margin-bottom: 32px; }
