/* ═══════════════════════════════════════════════════════════════════════════
   rzv.studio — bio.css   (the /bio/ bento rail)

   9 Sep 2026, Haseeb: "bio page i want to take insp from rzv dashboard so
   having bento widgets on the side the exact same way we have in the today
   page of rzv dashboard, but also the performance graphs etc."

   Every metric here is PORTED, not invented. Sources, one per rule block:
   · tile material + 2-col bento + footprints  → dashboard focus.html
     (.widget-tile / .widgets / .widget.w-1x1 / .w-2x1 / .w-2x2)
   · big numerals, labels, sparkline floor     → dashboard assets/perf-sections.css
     (.cell / .cell.xl / .cell-num / .cell-lbl / .cell-spark / .tl-spark / .spark-head)
   · donut + legend                            → assets/perf-sections.css
     (.donut-wrap / .donut-center-num / .donut-center-lbl / .donut-leg / .donut-leg-dot)
   · ranked rows                               → assets/perf-sections.css
     (.bk-row / .bk-row-k / .bk-row-bar / .bk-row-bar-fill / .bk-row-v)
   · ranked list w/ ordinal + track            → assets/perf-sections.css
     (.rank-bar / .rank-bar-kw / .rank-bar-track / .rank-bar-fill / .priority-num)
   · two-line row                              → assets/perf-sections.css
     (.bk-row-k over .bk-row-sub)
   · chip                                      → assets/perf-sections.css (.pill.neu)
   · sub-line under a number                   → assets/perf-sections.css (.tl-split)
   · odometer numerals                         → assets/perf-sections.css
     (.num-roll / .num-slot / .num-strip / .num-d) + perf-sections.js animateNumber()
   · compact spark floor                       → assets/perf-sections.css (.cell-spark, 32px)

   9 Sep 2026, second pass: twelve tiles, not seven. Five of them are marked
   PLACEHOLDER on their own face (.bw-ph) — the component is ported, the values
   are illustrative and are his to replace. See the rail's comment in
   bio/index.html for the line-by-line provenance of every real number.
   Dashboard token names are NOT imported — every value resolves against this
   site's own :root (--card, --border, --r-tile, --mono, --fs-*, --ease).

   DELIBERATE DIVERGENCE from the dashboard: the rail is NOT sticky and has no
   internal max-height/overflow. That pair exists on Today because that page
   does not scroll. This page is a 16-entry journey — the rail scrolls with the
   copy, and stops when it runs out. Everything else is the dashboard's.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── The split ──────────────────────────────────────────────────────────
   Dashboard: .today-grid{grid-template-columns:1fr 300px}. 340px here because
   the donut carries its legend on the same tile and 300 clips it. ── */
/* 10 Sep, Haseeb: "a lot of space to work with" + "allow widget to be on 2 columns
   bento effect some wide some small squares etc." The journey column was running half
   empty while the rail was starved at 340px; the rail now takes a real share, so its
   two columns are wide enough for the mix (some wide, some square). */
.bio-split{display:grid;grid-template-columns:minmax(0,1fr) clamp(380px,38vw,620px);
  align-items:start;column-gap:clamp(28px,3.4vw,56px)}
.bio-split-main{min-width:0}
.bio-split-rail{min-width:0;padding-top:clamp(16px,2.4vw,32px)}

/* ── The bento — focus.html .widgets, verbatim geometry ─────────────── */
.bw-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  grid-auto-flow:dense;gap:18px;
  /* THE RAIL'S COLOUR ROLES, declared once. The dashboard's performance
     graphics carry their ramp on --pos/--brand-color; nothing here has a
     direction to report, so the ramp is the site's own pearl instead —
     white → violet-white → ice. Every widget colour below resolves to these,
     which is what makes the light phase a three-line override. */
  --bw-line:#FFFFFF; --bw-iri:#DDD9FF; --bw-ice:#D5F6FF;
  --bw-glow:rgba(221,217,255,.9)}
body.phase-light .bw-grid{--bw-line:#17171A; --bw-iri:#4F46E5; --bw-ice:#0E7490;
  --bw-glow:rgba(79,70,229,.45)}
.bw{display:flex;flex-direction:column;min-width:0}
.bw.w-1x1{aspect-ratio:1/1}
.bw.w-2x1{grid-column:span 2;aspect-ratio:2/1}
.bw.w-2x2{grid-column:span 2;grid-row:span 2;aspect-ratio:1/1}

/* ── Tile material — focus.html .widget-tile, verbatim ──────────────────
   card bg, hairline, 24px corner (--r-tile is the same 24 here), 18px pad,
   blur(20), cursor-tracked sheen, soft outer glow on hover. ── */
.bw-tile{position:relative;flex:1;min-height:0;
  background:var(--card);border:1px solid var(--border);border-radius:var(--r-tile);
  padding:18px;overflow:hidden;
  backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
  transition:border-color .3s ease,background .3s ease;
  display:flex;flex-direction:column}
.bw-tile::before{content:'';position:absolute;inset:0;
  background:radial-gradient(150% 150% at var(--mx,50%) var(--my,50%),var(--tile-sheen),rgba(255,255,255,0.04) 28%,rgba(255,255,255,0.012) 55%,transparent 82%);
  opacity:0;transition:opacity .45s var(--ease);pointer-events:none}
.bw-tile::after{content:'';position:absolute;top:-1px;left:-1px;right:-1px;bottom:-1px;
  border-radius:var(--r-tile);box-shadow:0 0 32px var(--border-hi);
  opacity:0;z-index:-1;transition:opacity .5s var(--ease);pointer-events:none}
.bw-tile > *{position:relative;z-index:1}
.bw-tile:hover{border-color:var(--border-hi);background:var(--card-hi)}
.bw-tile:hover::before{opacity:.55}
.bw-tile:hover::after{opacity:.4}

/* Overline is the site's .overline (mono, uppercase) — this only sets the
   bento's own label rhythm (focus.html: .widgets .widget-label{margin-bottom:14px}). */
.bw-tile > .overline{margin-bottom:14px;flex:none;
  display:flex;align-items:center;flex-wrap:wrap;gap:4px 8px}
.bw-tile > .overline .bw-ol-sub{color:var(--dim)}
/* THE PLACEHOLDER TAG. Same chip as .bw-mark (perf-sections.css .fnl-step),
   held dim and pushed to the end of the overline row. Any tile wearing this is
   illustrative — shape only, not fact. It wraps to its own line rather than
   squeezing the label on a 1x1. */
.bw-ph{margin-left:auto;flex:none;font-family:var(--mono);font-size:8.5px;
  letter-spacing:0.1em;text-transform:uppercase;color:var(--dim);
  border:1px solid var(--border);border-radius:var(--r-pill);padding:2px 7px;
  line-height:1.4}

/* ── Numerals — perf-sections.css .cell-num / .cell.xl .cell-num ────────
   Space Mono is 400/700 only, so the dashboard's `300` resolves to 400 there
   too; declared honestly here. Tabular in both places. ── */
.bw-num{font-family:var(--mono);font-weight:400;color:var(--text);
  font-size:26px;letter-spacing:-0.02em;line-height:1;font-variant-numeric:tabular-nums}
.bw-tile.xl .bw-num{font-size:56px;letter-spacing:-0.035em}
.bw-lbl{font-family:var(--mono);font-size:10px;text-transform:uppercase;
  letter-spacing:0.09em;color:var(--muted);margin-top:9px;line-height:1.4;
  display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.bw-tile.xl .bw-lbl{margin-top:11px}

/* Sparkline floor — .cell-spark sits on the bottom of the tile whatever the
   row height (perf-sections.css), .tl-spark's svg rules. */
.bw-spark{height:58px;margin-top:auto;padding-top:16px;position:relative;flex:none;--hy:18.2px}
.bw-spark svg{width:100%;height:100%;display:block;overflow:visible}
/* .spark-head — the glow dot at the latest point. */
.bw-spark-head{position:absolute;left:100%;top:var(--hy,50%);margin:-4px 0 0 -4px;
  width:8px;height:8px;border-radius:50%;background:var(--bw-line);pointer-events:none;
  /* the dashboard's own travel head (travel.css .pl-head + plBreathe), re-tokenised */
  box-shadow:0 0 6px 2px rgba(255,255,255,.9),0 0 18px 6px var(--bw-glow);
  animation:bwBreathe 2.6s ease-in-out infinite}
@keyframes bwBreathe{50%{box-shadow:0 0 8px 3px rgba(255,255,255,.95),0 0 26px 10px var(--bw-glow);transform:scale(1.15)}}
html.rzv-no-motion .bw-spark-head{animation:none}
@media (prefers-reduced-motion:reduce){ .bw-spark-head{animation:none} }

/* the milestone nodes on the line — small, quiet, one per journey entry */
.bw-node{fill:var(--bw-line);opacity:.55}

/* films & shows: a proper list, the numeral quiet and the title carrying it */
.bw-favs li{grid-template-columns:2.4em minmax(0,1fr);gap:10px;padding:5px 0;
  border-top:1px solid var(--border)}
.bw-favs li:first-child{border-top:0;padding-top:0}
.bw-fav-n{opacity:.62}

/* the flight's ends carry their local time */
.bw-route-end{display:flex;flex-direction:column;gap:3px;font-family:var(--mono);
  font-size:var(--fs-label);letter-spacing:0.14em;color:var(--bw-dim,rgba(255,255,255,.5))}
.bw-route-end:last-child{align-items:flex-end;text-align:right}
.bw-route-t{font-size:var(--fs-sm);letter-spacing:-0.01em;color:var(--bw-key,#FAFAFA);
  font-variant-numeric:tabular-nums}
/* .cell-spark's own floor — 32px, the compact metric cell, not the xl one. */
.bw-spark.sm{height:32px;padding-top:11px;--hy:13px}

/* ── The three first numbers — a typographic stat stack ──────────────── */
.bw-stack{display:flex;flex-direction:column;justify-content:center;gap:12px;flex:1;min-height:0}
.bw-stack-row{display:flex;align-items:baseline;gap:10px}
.bw-stack-n{font-family:var(--mono);font-size:22px;font-weight:400;color:var(--text);
  line-height:1;font-variant-numeric:tabular-nums;min-width:2ch}
.bw-stack-k{font-size:11px;line-height:1.35;color:var(--muted);letter-spacing:-0.005em}

/* ── Donut — perf-sections.css .donut-* ─────────────────────────────── */
.bw-donut-wrap{display:flex;flex-direction:column;align-items:flex-start;
  justify-content:space-between;gap:16px;flex:1;min-height:0}
.bw-donut{position:relative;width:132px;height:132px;flex:none}
.bw-donut svg{display:block}
.bw-donut circle{transition:opacity .2s var(--ease)}
.bw-donut-c{position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;text-align:center;pointer-events:none}
.bw-donut-num{font-family:var(--mono);font-size:24px;font-weight:400;color:var(--text);
  letter-spacing:-0.02em;line-height:1;font-variant-numeric:tabular-nums}
.bw-donut-lbl{font-family:var(--mono);font-size:9.5px;color:var(--muted);
  letter-spacing:0.12em;text-transform:uppercase;margin-top:5px}
.bw-legend{display:flex;flex-direction:column;gap:9px;width:100%;max-width:200px}
.bw-leg{display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--muted);
  letter-spacing:-0.005em}
.bw-leg-dot{width:8px;height:8px;border-radius:2px;flex-shrink:0}
.bw-leg strong{color:var(--text);font-weight:500;margin-left:auto;
  font-family:var(--mono);font-variant-numeric:tabular-nums}

/* ── Ranked rows — perf-sections.css .bk-row / .bk-row-bar ──────────── */
.bw-rows{display:flex;flex-direction:column;justify-content:center;flex:1;min-height:0}
.bw-row{display:flex;align-items:center;gap:16px;padding:7px 0}
.bw-row:first-child{padding-top:0}
.bw-row:last-child{padding-bottom:0}
.bw-row-main{flex:1;min-width:0}
.bw-row-k{font-size:12px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bw-row-bar{position:relative;height:4px;background:var(--surface-2);
  border-radius:var(--r-pill);overflow:hidden;margin-top:5px}
.bw-row-fill{position:absolute;left:0;top:0;bottom:0;border-radius:var(--r-pill);
  width:var(--f,0%);
  background:linear-gradient(90deg,
    color-mix(in srgb,var(--bw-iri) 35%,transparent),
    color-mix(in srgb,var(--bw-line) 85%,transparent))}
.bw-row-v{flex:none;font-family:var(--mono);font-size:11px;color:var(--dim);
  font-variant-numeric:tabular-nums;letter-spacing:0.02em}

/* ── LDN → LAX — the marker, unexplained (VOICE.md: "LA stays cryptic") ── */
.bw-route{display:flex;align-items:center;justify-content:center;gap:14px;flex:1;min-height:0}
.bw-route-end{font-family:var(--mono);font-size:26px;font-weight:700;color:var(--text);
  letter-spacing:0.04em;line-height:1}
.bw-route-line{position:relative;flex:1;height:1px;background:var(--border-hi);min-width:24px}
.bw-route-line::after{content:'';position:absolute;right:-1px;top:-2.5px;
  width:6px;height:6px;border-radius:50%;background:var(--text);
  box-shadow:0 0 10px var(--bw-glow)}
.bw-route-dot{position:absolute;left:0;top:-2px;width:5px;height:5px;border-radius:50%;
  background:var(--dim)}
.bw-route-foot{font-family:var(--mono);font-size:9.5px;letter-spacing:0.12em;
  text-transform:uppercase;color:var(--dim);text-align:center;flex:none}

/* ── Marks — a plain list of what he does when nobody is paying ──────── */
.bw-marks{display:flex;flex-wrap:wrap;align-content:center;gap:7px;flex:1;min-height:0}
.bw-mark{font-family:var(--mono);font-size:9.5px;letter-spacing:0.08em;text-transform:uppercase;
  color:var(--muted);border:1px solid var(--border);border-radius:var(--r-pill);
  padding:4px 9px;transition:border-color .25s var(--ease),color .25s var(--ease)}
.bw-tile:hover .bw-mark{border-color:var(--border-hi);color:var(--text)}


/* ── Odometer — perf-sections.css .num-roll / .num-slot / .num-strip / .num-d
   with animateNumber()'s fresh-load plan (perf-sections.js). Every digit is a
   1em window over a 0-9 strip; symbols slide up. bio-widgets.js builds this at
   play time only — the element's own text is the truth until then, so with
   motion off the number is simply printed. ── */
.bw-num.num-roll,.bw-donut-num.num-roll{display:inline-flex;white-space:nowrap}
.num-slot{display:inline-block;overflow:hidden;height:1em;line-height:1;vertical-align:bottom}
.num-strip{display:block}
.num-strip .num-d{display:block;height:1em;line-height:1;text-align:center;
  font-variant-numeric:tabular-nums}

/* ── Sub-line under a number — perf-sections.css .tl-split ─────────────── */
.bw-split{font-family:var(--mono);font-size:10.5px;color:var(--dim);margin-top:6px;
  font-variant-numeric:tabular-nums;letter-spacing:0.04em}

/* ── Chip — perf-sections.css .pill, in its .neu state. There is no .pos/.neg
   here on purpose: nothing on this page has a direction to report. ── */
.bw-pill{display:inline-flex;align-items:center;gap:3px;font-family:var(--mono);
  font-size:9.5px;letter-spacing:0.06em;text-transform:uppercase;white-space:nowrap;
  padding:2px 8px;border-radius:var(--r-pill);color:var(--muted);background:var(--surface-2)}

/* ── Ranked list — perf-sections.css .rank-bar / .rank-bar-track /
   .rank-bar-fill / .rank-bar-kw, with .priority-num carrying the ordinal.
   6px track (not the 4px .bk-row-bar), same 100px cap, same left origin. ── */
.bw-ranks{display:flex;flex-direction:column;gap:10px;flex:1;min-height:0;justify-content:center}
.bw-rank{display:grid;grid-template-columns:22px minmax(0,1fr) auto;gap:12px;align-items:center}
.bw-rank-n{font-family:var(--mono);font-size:11px;color:var(--dim);letter-spacing:0.04em;
  font-variant-numeric:tabular-nums;align-self:start;padding-top:1px}
.bw-rank-main{min-width:0}
.bw-rank-kw{font-size:12.5px;color:var(--text);letter-spacing:-0.005em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bw-rank-track{position:relative;height:6px;background:var(--surface-2);
  border-radius:var(--r-pill);overflow:hidden;margin-top:5px}
.bw-rank-v{flex:none;font-family:var(--mono);font-size:11px;color:var(--dim);
  font-variant-numeric:tabular-nums;letter-spacing:0.02em}

/* ── Two-line row — perf-sections.css .bk-row-k over .bk-row-sub ───────── */
.bw-stack.bw-stack--tight{gap:0}
.bw-sub{font-family:var(--mono);font-size:10px;color:var(--dim);margin-top:6px;
  letter-spacing:0.07em;text-transform:uppercase;line-height:1.4}
.bw-read{display:flex;flex-direction:column;justify-content:center;flex:1;min-height:0}
.bw-read .bw-row-bar{margin-top:14px}

/* ── Reveal. CSS default IS the finished state; bio-widgets.js sets the start
   and plays it back, and only when html.rzv-anim. No JS, no motion, a print —
   the rail renders complete either way. ── */
html.rzv-anim .bw-grid:not(.bw-ready) .bw{opacity:0}

/* ═══ RESPONSIVE ═══════════════════════════════════════════════════════
   Below 1080 the rail cannot hold two 161px tiles beside a readable journey
   column, so it drops under the copy and lays out as a full-width bento —
   the same collapse the dashboard makes at html.ptl-split. */
@media (max-width:1080px){
  .bio-split{grid-template-columns:1fr;row-gap:clamp(40px,6vw,72px)}
  .bio-split-rail{padding-top:0}
  .bw-grid{grid-template-columns:repeat(4,minmax(0,1fr))}
  .bw.w-1x1{grid-column:span 2}
  .bw.w-2x1{grid-column:span 2}
  .bw.w-2x2{grid-column:span 2;grid-row:span 2}
}
@media (max-width:640px){
  /* Phones: one column, and the footprints give up their aspect ratios —
     a 2/1 box at 320px wide is 160px of nothing. Tiles size to content. */
  .bw-grid{grid-template-columns:minmax(0,1fr);gap:14px}
  .bw.w-1x1,.bw.w-2x1,.bw.w-2x2{grid-column:1 / -1;grid-row:auto;aspect-ratio:auto}
  .bw-tile{min-height:132px}
  .bw-tile.xl .bw-num{font-size:44px}
  .bw-donut-wrap{gap:18px}
  .bw-ranks{gap:12px}
  .bw-ph{margin-left:8px}
  .bw-legend{max-width:none}
  .bw-route-end{font-size:22px}
}

@media (prefers-reduced-motion:reduce){
  html.rzv-anim .bw-grid:not(.bw-ready) .bw{opacity:1}
}


/* ═══ 9 Sep 2026 — the flight, the clocks, the favourites ══════════════════
   "Show plane icon moving from LDN to LAX on loop. Make the journey an arch."
   The arc is one path; bio-widgets.js walks a plane along it and starts over.
   Reduced motion parks the plane at the top of the arc. ═══ */
.bw-flight{position:relative;margin:10px 0 4px}
.bw-arc{display:block;width:100%;height:auto;overflow:visible}
.bw-arc-line{fill:none;stroke:url(#bwArcG);stroke-width:1.25;
  stroke-dasharray:3 5;stroke-linecap:round}
.bw-arc-pin{fill:var(--bw-key,#FAFAFA)}
/* LAX is the live end — the spark head's breathing glow, in SVG */
.bw-arc-spark-halo{fill:url(#bwArcSpark);transform-box:fill-box;transform-origin:center;
  animation:bwArcBreathe 2.6s ease-in-out infinite}
@keyframes bwArcBreathe{0%,100%{opacity:.75;transform:scale(.86)}50%{opacity:1;transform:scale(1.12)}}
html.rzv-no-motion .bw-arc-spark-halo{animation:none}
@media (prefers-reduced-motion:reduce){ .bw-arc-spark-halo{animation:none} }
.bw-plane{fill:var(--bw-key,#FAFAFA)}
.bw-flight-ends{display:flex;justify-content:space-between;margin-top:2px;
  padding:0 8.67%}

.bw-clocks{display:flex;gap:clamp(16px,4vw,34px);margin-top:12px}
.bw-clock{display:flex;flex-direction:column;gap:5px;min-width:0}
.bw-clock-t{font-family:var(--mono);font-size:clamp(1.5rem,2.4vw,2rem);font-weight:400;
  letter-spacing:-0.01em;color:var(--bw-key,#FAFAFA);font-variant-numeric:tabular-nums}

.bw-favs{list-style:none;margin:12px 0 0;padding:0;display:flex;flex-direction:column;gap:7px;
  counter-reset:none}
.bw-favs li{display:grid;grid-template-columns:2.1em minmax(0,1fr);align-items:baseline;
  gap:8px;font-size:var(--fs-body);line-height:1.35;color:var(--bw-key,#FAFAFA)}
.bw-fav-n{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:0.14em;
  color:var(--bw-dim,rgba(255,255,255,0.45));font-variant-numeric:tabular-nums}


/* 10 Sep — the bento's mix. Wide tiles keep their 2:1; SQUARE tiles are a single
   column; TALL tiles take one column and two rows and let their content set the
   height (a ranked list or a top-seven has no business being forced to a ratio). */
.bw.w-1x2{grid-column:span 1;grid-row:span 2;aspect-ratio:auto}
.bw.w-1x2 .bw-tile{height:100%}
.bw.w-2x2{aspect-ratio:auto;min-height:340px}
.bw.w-1x1{aspect-ratio:1/1}
@media (max-width:1100px){
  .bw.w-1x2{grid-column:span 2;grid-row:auto}
}


/* 10 Sep, Haseeb: "the line is messed up. nodes look awful." Two faults, one cause.
   The spark was filling the whole xl tile, so a 300×58 viewBox drawn with
   preserveAspectRatio:none was stretched ~1.8× across and ~4.7× down — the curve
   flattened into a smear and every circular node deformed into a blob. The spark is
   a STRIP on the tile's floor again (the dashboard's own .cell.xl .cell-spark is
   58px), and the milestones are vertical ticks, which a stretched box cannot
   deform, with a non-scaling stroke so they stay hairlines at any width. */
.bw-tile.xl .bw-spark{height:76px;flex:0 0 76px;margin-top:auto;padding-top:0}
.bw-tile.xl .bw-spark svg{height:100%}
/* .bw-node retired 10 Sep — "not a fan of the 5 vertical lines" */

/* 10 Sep: 236px of dead air under the hero. The ribbon rule in ribbon.css gives
   any hero carrying one a 64vh floor, and the wrap centres inside it — bio's
   hero copy is short, so the floor was doing all the work and the journey list
   and rail sat a screen lower than they needed to. Bio's hero is sized by its
   own content; the ribbon band is shorter, which is all it ever was. */
body[data-page="bio"] .page-hero:has(> .ribbon-hero){min-height:calc(var(--vh,1vh) * 46)}
@media (max-width:760px){
  body[data-page="bio"] .page-hero:has(> .ribbon-hero){min-height:calc(var(--vh,1vh) * 42)}
}
