/* card.css — the public player card (Phase 5, plan §5 + §8).
 *
 * The chassis is the owner-approved spec, and each part of it was chosen against a specific
 * defect rather than to taste:
 *   Style 2  legibility comes from the LETTERFORMS (a stroke on the head rail), not from
 *            scrims over the picture — a stroke costs the plate zero pixels.
 *   E3       the nameplate is CUT AND FILLED: engraved, channel darkened. The only emboss that
 *            keeps ink contrast while reading as struck metal.
 *   P1       the brass gradient is regraded so its trough sits behind the NAME's baseline. The
 *            stock ramp troughs at 58%, which is exactly where the sub line sits — dark ink on
 *            the darkest part of the metal, on all 87 cards.
 *   C1       an IVORY TRIM. The card had no trim at all: .bcard was transparent, so the page
 *            showed through its border. A bright stock border is also the exposure fix — the
 *            plates are high-key chromium and a dim frame around them reads as muddy.
 *
 * No page-local :root aliases: everything references --pld-* directly, so the drift guard's
 * reserved-name rule has nothing to catch and the ramps cannot fork.
 */

/* The same reset index.css carries. Without it this page was content-box while the landing page
   (which shows the SAME card and sheet in a popover) was border-box, so one component rendered at
   two widths — the head-to-head sheet was 472px here and 430px there, and only the second matched
   the drawn mockup. */
*{ box-sizing: border-box; }

.wrap { max-width: 1080px; margin: 0 auto; padding: 0 18px 80px; }
.toprow { display: flex; align-items: baseline; gap: 14px; padding: 22px 2px 6px; }
.toprow h2 { font-family: 'Oswald', sans-serif; font-size: 26px; color: var(--pld-gold-bright); margin: 0; }
.toprow .s { color: var(--pld-faint); font-size: 13px; }
/* .loading/.errnote are Layer-2 components — tokens.css owns them (drift guard). */

/* CSP: style-src is strict, so nothing may carry an inline style attribute. These two exist
   because the markup used to. */
.grainDefs { position: absolute; width: 0; height: 0; }
/* (.bserial .sn span is gone — the population it dimmed now has its own line, .sp. The dimming
   was the whole reason the plate looked off-centre: a centred string whose bright half sits
   left reads as misaligned, however correct the arithmetic.) */

/* ── the card ── everything is a fraction of --cw so it scales as one unit.
   THE FLIP IS THE CARD, not a crossfade (mockup): .bcard is a perspective STAGE, .binner turns
   in 3D over .72s, and each .bside is a full piece of ivory stock — front and back are both
   real card faces, exactly like the object in your hand. ── */
/* --cw lives on the STAGE, not the card. The head-to-head sheet is the card's SIBLING, and a
   custom property only reaches descendants — declared on .bcard, every calc(var(--cw) …) in the
   sheet was invalid and silently computed to 0, so the tuck, the padding and the die-cut radius
   all vanished while the comment below claimed they were shared. One declaration here is what
   actually makes the card and its insert one die. */
/* The stage is as wide as its WIDEST child — the head-to-head sheet — and says so. On the card
   page it sat inside a wide wrap and nobody noticed; in the popover it is a centred flex item, so
   it shrink-wrapped to the CARD and the sheet's `100%` cap clamped it to exactly the card's width.
   Same shape of bug as --cw: a value that silently depends on an ancestor's sizing.
   The cap is 92vw rather than 100%, deliberately — the popover's body shrink-wraps to THIS
   element, so a percentage asks the parent for a width the parent is asking us for, and the
   circle resolves at the card's size. A viewport unit has no such dependency. */
.cardstage { --cw: min(3.5in, 88vw); --mono-x: 52%;
  width: min(calc(var(--cw) * 1.44), 92vw); margin: 0 auto;
  display: flex; flex-direction: column; align-items: center; gap: 0; padding: 40px 0 0; }
.bcard {
  width: var(--cw); height: calc(var(--cw) * 1.4);
  position: relative; z-index: 3; cursor: pointer;
  font-family: 'Oswald', sans-serif;
  perspective: calc(var(--cw) * 5);
}
/* NO FOCUS RING ON THE CARD (owner, 2026-08-14). It drew a hard-cornered gold rectangle around a
   rounded card, because .bcard is the perspective stage and carries no radius of its own. The
   card is not the only way to reach anything — the flip has a keyboard handler, and every fact on
   it is also on the page behind — so suppressing the ring costs no route, only the box.
   The radius stays: it keeps any future outline, and the browser's own hit shape, on the card. */
.bcard { border-radius: calc(var(--cw) * .041); }
.bcard:focus, .bcard:focus-visible { outline: none; }
.binner { position: absolute; inset: 0; transform-style: preserve-3d;
  transition: transform .72s cubic-bezier(.35,.1,.24,1); }
.bcard.flipped .binner { transform: rotateY(180deg); }
@media (prefers-reduced-motion: reduce) { .binner { transition: none; } }
.bside {
  position: absolute; inset: 0; backface-visibility: hidden; -webkit-backface-visibility: hidden;
  border-radius: calc(var(--cw) * .041); overflow: hidden; isolation: isolate;
  padding: calc(var(--cw) * .032);
  background: linear-gradient(158deg, #fdf7e9 0%, #f3e8cf 46%, #e4d5b2 100%);   /* C1 ivory stock */
  box-shadow: 0 1px 0 rgba(255,255,255,.22), 0 2px 3px rgba(0,0,0,.5),
              0 14px 26px rgba(0,0,0,.55), 0 34px 60px rgba(0,0,0,.5);
}
.bside.back { transform: rotateY(180deg); }
/* The stock's own edge: a white hairline and the top-light sheen, nothing more. Every ring that
   used to sit inside it is gone (owner) — a card's edge is where the paper stops, not a border
   someone drew on it. */
.bside::after {
  content: ''; position: absolute; inset: 0; border-radius: calc(var(--cw) * .041);
  pointer-events: none; z-index: 20;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.5),
              inset 0 calc(var(--cw) * .065) calc(var(--cw) * .12) rgba(255,255,255,.06);
}
/* The C1 cream stock band stays (owner): it is rounded, it is the card's own paper showing at the
   edge, and it was never the rectangle in question — that was the focus ring, above. */
.bface {
  position: absolute; inset: calc(var(--cw) * .032); border-radius: calc(var(--cw) * .018);
  overflow: hidden; display: flex; flex-direction: column; background: var(--pld-claret-deep);
}
.layer { position: absolute; inset: 0; pointer-events: none; }

/* The chassis burst, visible only where no plate covers it — a faceless card is not blank. */
.l-burst { z-index: 1; background: conic-gradient(from 200deg at 50% 60%,
  #8d1a22 0deg, #c04a3c 12deg, #5a0d14 24deg, #a8452f 36deg, #6e1019 48deg, #96262c 60deg,
  #5a0d14 72deg, #b2333a 84deg, #6e1019 96deg, #c04a3c 108deg, #5a0d14 120deg, #8d1a22 132deg,
  #6e1019 144deg, #a8452f 156deg, #5a0d14 168deg, #96262c 180deg, #6e1019 192deg, #b2333a 204deg,
  #5a0d14 216deg, #c04a3c 228deg, #6e1019 240deg, #8d1a22 252deg, #5a0d14 264deg, #a8452f 276deg,
  #6e1019 288deg, #96262c 300deg, #5a0d14 312deg, #b2333a 324deg, #6e1019 336deg, #8d1a22 360deg); }
.l-photo { z-index: 2; background: 50% 46% / cover no-repeat; }
.l-mono { z-index: 2; display: flex; align-items: center; justify-content: center;
  padding-bottom: calc(var(--cw) * .17); }
.l-mono span { font-size: calc(var(--cw) * .44); font-weight: 700; letter-spacing: -.04em;
  line-height: 1; color: var(--pld-gold-wash-2); }
.l-legible { z-index: 3; background:
  radial-gradient(ellipse 46% 9% at 27% 4.6%, rgba(10,2,5,.55), transparent 74%),
  radial-gradient(ellipse 16% 8% at 88% 4.6%, rgba(10,2,5,.55), transparent 74%); }
.l-grain { z-index: 18; opacity: .5; mix-blend-mode: overlay; }

/* ── chrome: identical on every card. That is what makes 87 of them a set. ── */
.bhead { position: relative; z-index: 5; display: flex; align-items: center;
  justify-content: space-between; padding: calc(var(--cw) * .026) calc(var(--cw) * .035) calc(var(--cw) * .020); }
.bhead .wordmark { font-size: calc(var(--cw) * .0265); letter-spacing: .19em; font-weight: 600;
  text-transform: uppercase; color: var(--pld-gold-bright); }
.bhead .yr { font-size: calc(var(--cw) * .0324); font-weight: 700; letter-spacing: .08em;
  color: var(--pld-gold-bright); }
/* Style 2: the stroke IS the legibility, so the picture keeps its pixels. */
.bhead .wordmark, .bhead .yr { paint-order: stroke fill;
  -webkit-text-stroke: calc(var(--cw) * .0035) rgba(20,3,7,.85);
  text-shadow: 0 1px 1px rgba(0,0,0,.9), 0 0 7px rgba(20,3,7,.55); }
.brule { position: relative; z-index: 5; height: 2px; margin: 0 calc(var(--cw) * .035);
  background: linear-gradient(90deg, transparent, #fff6de, var(--pld-gold) 62%, #7e5c1c); }
/* .btier's badge styling moved to .bst (the PHASE 2 block below) when it stopped being one
   badge and became the column several sit in. The old block is DELETED rather than shadowed:
   two rules for one class at equal specificity, 170 lines apart, is the twin rule 6 forbids —
   and it was live, because the later block only resets four of its properties. Drop
   `background: none` from that reset and the gold pill reappears behind the chip stack. */
/* CENTRED, not right-aligned. The block is sized by its WIDEST child — the "LEAGUE RANK" label —
   and the number above it is far narrower, so right-aligning both hung the number off the right
   edge of its own plate and it read as misplaced rather than as a serial. Centring is what makes
   the two read as one stamped unit whatever the number's width ("5" and "52/125" both). */
.bserial { position: absolute; top: calc(var(--cw) * .147); right: calc(var(--cw) * .035); z-index: 6;
  text-align: center; background: linear-gradient(rgba(16,3,6,.72), rgba(16,3,6,.5));
  border: 1px solid rgba(240,220,168,.55); border-radius: calc(var(--cw) * .015);
  padding: calc(var(--cw) * .015) calc(var(--cw) * .024) calc(var(--cw) * .014); }
.bserial .sn { font-size: calc(var(--cw) * .056); font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums;
  background: linear-gradient(180deg, #fff4d2, var(--pld-gold-bright) 42%, #b8892a 60%, #f0dca8);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  filter: drop-shadow(0 1px 1px rgba(0,0,0,.7)); }
.bserial .sl { font-size: calc(var(--cw) * .0191); letter-spacing: .17em; text-transform: uppercase;
  color: #e2c48a; margin-top: calc(var(--cw) * .009); font-weight: 600; }
/* The population, on its own line so the RANK is what centres over the label. */
.bserial .sp { font-size: calc(var(--cw) * .0175); letter-spacing: .08em; font-weight: 600;
  color: rgba(226,196,138,.62); margin-top: calc(var(--cw) * .002); line-height: 1.15; }
.bspacer { flex: 1; }

/* The fact strip — three TRUE facts, so a one-night player never sees a blank row. */
.bfacts { position: relative; z-index: 6; margin: 0 calc(var(--cw) * .035) calc(var(--cw) * .022);
  display: flex; flex-direction: column; gap: calc(var(--cw) * .008); }
.bfacts div { font-size: calc(var(--cw) * .0245); letter-spacing: .04em; font-weight: 500;
  color: #f4e8d6; background: rgba(16,3,6,.55); border-left: 2px solid var(--pld-gold);
  padding: calc(var(--cw) * .008) calc(var(--cw) * .016); border-radius: 0 3px 3px 0;
  text-shadow: 0 1px 2px rgba(0,0,0,.8); }

/* P1: the trough moved UP behind the name's baseline. E3: cut and filled. */
.bplate { position: relative; z-index: 6; margin: 0 calc(var(--cw) * -.024) -1px;
  transform: skewY(-2deg);
  padding: calc(var(--cw) * .026) calc(var(--cw) * .056) calc(var(--cw) * .032);
  background: linear-gradient(178deg, #fff6de 0%, #f2e0ad 10%, #d8b85f 24%, #b8892a 33%,
    #8a6618 39%, #c79b34 51%, #e8d49e 68%, #f4e4b4 86%, #c69a33 100%);
  border-top: 1px solid #fffaea; border-bottom: 1px solid #4a3810;
  box-shadow: 0 -2px 5px rgba(30,4,8,.42), inset 0 1px 0 rgba(255,255,255,.75); }
.bplate .inner { transform: skewY(2deg); }
.bplate .n { font-size: calc(var(--cw) * .0765); font-weight: 700; line-height: 1;
  letter-spacing: -.015em; color: #2b2005;
  text-shadow: 0 1px 0 rgba(255,250,226,.72), 0 -1px 0 rgba(0,0,0,.30), 0 0 1px rgba(43,32,5,.5); }
.bplate .sub { display: flex; justify-content: space-between; gap: 8px;
  margin-top: calc(var(--cw) * .012); font-size: calc(var(--cw) * .032); letter-spacing: .08em;
  text-transform: uppercase; color: #241a04; font-weight: 700;
  text-shadow: 0 .5px 0 rgba(255,250,226,.62), 0 -.5px 0 rgba(0,0,0,.22); }

/* ── the back: pure data, so it ALWAYS works ── */
.bback { background: var(--pld-vellum); color: #2a2418;
  padding: calc(var(--cw) * .041) calc(var(--cw) * .044); display: flex; flex-direction: column;
  gap: calc(var(--cw) * .030); }
/* the felt back's facts table sits directly under the name — no section rule above it */
.bt.bfx td { border-top: none; border-bottom: 1px solid rgba(120,105,70,.22); }
/* A section is heading + table as ONE trimmable unit: dropping the last row of a table must
   take its heading with it, or the card keeps a title over nothing. */
.bsecwrap { display: flex; flex-direction: column; gap: calc(var(--cw) * .014); }
.bbhead { flex: none; }
.bbname { font-size: calc(var(--cw) * .066); font-weight: 700; line-height: 1; }

/* ══ PHASE 2 · THE BACK'S TWO ZONES (IA review, 2026-08-15) ═════════════════════════════════
   The furniture is identical on every card in the set and only the ROWS vary — so a zone is a
   real container with a rule at its head, and the trim is not allowed to remove one. */
/* flex: none IS LOAD-BEARING. .bback is a flex column, so a zone is a flex ITEM and shrinks by
   default — and a table inside a shrunk item overflows the ZONE, not the card. That keeps
   .bback's scrollHeight equal to its clientHeight, which is the exact question fitBack asks, so
   the trim never fired and the rows just overlapped the footer. Zones size to their content and
   let the overflow reach the card, where it is measured. */
.bzone { display: flex; flex-direction: column; gap: calc(var(--cw) * .012); flex: none; }
/* THE SUBHEADING: the year, then the marks. It used to narrate the card's own layout ("The
   season line", "The splits" — the internal style names), which made three cards in one set
   look like three products. */
.bbmeta { display: flex; align-items: baseline; gap: calc(var(--cw) * .016); flex-wrap: wrap;
  margin-top: calc(var(--cw) * .010); }
.bbyr { font-size: calc(var(--cw) * .042); font-weight: 700; line-height: 1; color: #6b4f12;
  letter-spacing: .02em; font-variant-numeric: tabular-nums; }
.bbmk { font-size: calc(var(--cw) * .0215); letter-spacing: .13em; text-transform: uppercase;
  font-weight: 700; color: #8d7c50; }
/* The denominator under a rate — a rate without its n is not a fact (IA review). */
.bstats .n { font-size: calc(var(--cw) * .0185); letter-spacing: .04em; color: #9c8a5c;
  margin-top: calc(var(--cw) * .003); line-height: 1.15; }
.bstats.rv .n { color: #a2916a; }
/* The honest sentence, demoted to a footnote under the table it qualifies. */
.bnote { font-size: calc(var(--cw) * .0225); }
/* (.bbmeta's single-line rule is DELETED, not shadowed — the year+marks strip above replaces it.
   Left in place it sat AFTER the new one at equal specificity and won on source order, which is
   the same twin the .btier block already taught this file.) */
.bsec { font-size: calc(var(--cw) * .0261); letter-spacing: .18em; text-transform: uppercase;
  font-weight: 700; color: var(--pld-gold-deep); border-bottom: 1px solid #cbbb90;
  padding-bottom: calc(var(--cw) * .008); }
.bt { width: 100%; border-collapse: collapse; font-size: calc(var(--cw) * .0305);
  font-variant-numeric: tabular-nums; }
.bt th { font-size: calc(var(--cw) * .0246); letter-spacing: .1em; text-transform: uppercase;
  color: #8d7c50; font-weight: 600; text-align: right; padding: calc(var(--cw) * .008) 0; }
.bt th.l, .bt td.l { text-align: left; }
.bt td { text-align: right; padding: calc(var(--cw) * .0085) 0;
  border-top: 1px solid rgba(120,105,70,.22); font-weight: 500; }
.bt td.pl { font-weight: 700; }
.bt td.pts { font-weight: 700; color: #6b4f12; }
.bt tr.tot td { border-top: 1.5px solid #a89468; font-weight: 700; color: #2a2418; }
/* WHATEVER BLOCKS EXIST FILL THE ROW, EVENLY (owner, 2026-08-16). `repeat(4, 1fr)` was a fixed
   FOUR columns, so a band with fewer cells left dead track on the right — a player with only
   Tournament and Sanctioned ranks got two blocks and half an empty row, and the ragged edge read
   as a rendering fault rather than as "this player has no casual play". Column auto-flow makes
   the track count follow the cell count and `1fr` keeps every one the same width, so two cells
   are two half-width blocks and four are four quarter-width ones.
   ALL THREE BANDS, not just the ranks: the headline four, the reveal rates and the rank band are
   one component with three skins, and each can come up short (a scope not played, a rate with no
   denominator). Fixing only the band that was reported would leave the same hole in its twins —
   which is the duplication rule, applied to a grid template. Nothing here has ever had more than
   four cells, so no band can spill past the width it used to occupy. */
.bstats { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: calc(var(--cw) * .016); }
.bstats > div { background: rgba(138,102,24,.09); border: 1px solid rgba(138,102,24,.22);
  border-radius: calc(var(--cw) * .012); padding: calc(var(--cw) * .014) calc(var(--cw) * .008);
  text-align: center; }
.bstats .k { font-size: calc(var(--cw) * .0231); letter-spacing: .11em; text-transform: uppercase;
  color: #8d7c50; font-weight: 600; }
.bstats .v { font-size: calc(var(--cw) * .044); font-weight: 700; line-height: 1.1; color: #2a2418;
  font-variant-numeric: tabular-nums; }
/* The reveals band — the second four, sitting directly under the headline four as one block of
   eight. Quieter ground and a % marker on the value, because these are RATES where the row above
   is mostly counts: same shape, different kind of number. */
/* NO NEGATIVE TOP MARGIN. It existed to pull this band up against the headline four when the
   two sat adjacent as one eight-cell block. The rates are their own ZONE now, under their own
   rule, and the same -.010 that closed a gap then opens a crowding problem now: the blocks sat
   hard against the divider above them (owner). The zone's own gap does the spacing. */
.bstats.rv { margin-top: 0; }
.bstats.rv > div { background: rgba(138,102,24,.045); }
.bstats.rv .k { color: #97865a; }
.bstats.rv .v { font-size: calc(var(--cw) * .038); }
.bstats.rv .v::after { content: '%'; font-size: .58em; margin-left: .08em; color: #8d7c50;
  vertical-align: .12em; }
/* an em dash is not a rate: no % marker on a figure that has no denominator */
.bstats.rv .v.na::after { content: ''; }
/* the section rule can carry a scope note on its right — the band's honest fine print */
.bsec { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.bsec .bsx { font-size: calc(var(--cw) * .0201); letter-spacing: .06em; text-transform: none;
  color: #9b8a5e; font-weight: 500; }
.bfoot { margin-top: auto; display: flex; justify-content: space-between;
  font-size: calc(var(--cw) * .0231); letter-spacing: .12em; text-transform: uppercase;
  color: #8d7c50; font-weight: 600; }
.flipnote { font-family: 'Oswald', sans-serif; font-size: var(--pld-fs-8); letter-spacing: .18em;
  text-transform: uppercase; color: var(--pld-faint); margin-top: 14px; text-align: center; }

/* ── head to head: the card's OWN BACK STOCK, cut with the same die ──
 * Owner's pick. A related material rather than the same one, so it reads as an insert that came
 * in the pack. The radius is the card's own expression with --cw inherited, so the two are cut by
 * one die by construction and cannot drift. It is a SHEET: real edge, its own shadow, no
 * dissolve — a rounded corner needs a visible edge to read as a corner. */
/* WIDER THAN THE CARD, always (owner) — and by the mockup's own proportion. It was drawn at
   430px against a 3.1in card: 1.44x. Kept as a fixed 430 while the card grew to 3.5in, that
   ratio silently fell to 1.28 and the sheet stopped reading as the larger object underneath.
   Expressed against --cw it cannot drift again, whatever size the card becomes. */
.h2h { position: relative; z-index: 2; width: min(calc(var(--cw) * 1.44), 100%);
  margin-top: calc(var(--cw) * -.07); padding: calc(var(--cw) * .10) 20px 14px;
  border-radius: calc(var(--cw) * .041); font-family: 'Oswald', sans-serif; color: #6a5f42;
  background: var(--pld-vellum); border: 1px solid var(--pld-vellum-line); border-top-color: #c0ad7e;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.7), inset 0 32px 34px -30px rgba(90,70,30,.34),
              0 12px 26px rgba(0,0,0,.45); }
/* the contact shadow: darkest exactly where the card occludes the light */
.h2h::before { content: ''; position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: var(--cw); height: 24px; pointer-events: none; border-radius: calc(var(--cw) * .041);
  background: radial-gradient(ellipse 56% 100% at 50% 0, rgba(80,62,26,.40), transparent 74%); }
.h2h-eb { display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  font-size: var(--pld-fs-8); font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
  color: #8d7c50; line-height: 11px; }
.h2h-eb b { color: #2a2418; font-weight: 600; font-variant-numeric: tabular-nums; }
.h2h-h { margin: 7px 0 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 26px; font-weight: 400; line-height: 26px; letter-spacing: -.012em; color: #6a5f42;
  font-variant-numeric: lining-nums proportional-nums; }
.h2h-h b { color: #2a2418; font-weight: 600; }
.h2h-h i { font-style: normal; opacity: .5; padding: 0 .015em; }
.h2h-t { width: 100%; border-collapse: collapse; margin-top: 13px; font-variant-numeric: tabular-nums; }
.h2h-t th { font-size: var(--pld-fs-8); font-weight: 600; letter-spacing: .14em;
  text-transform: uppercase; color: #8d7c50; text-align: right; padding: 0 0 6px; width: 23%;
  border-bottom: 1px solid #cbbb90; }
.h2h-t th.l { text-align: left; width: 54%; }
.h2h-t td { font-size: 15px; font-weight: 500; text-align: right; padding: 4px 0; line-height: 17px;
  color: #6a5f42; border-top: 1px solid rgba(120,105,70,.20); }
.h2h-t td.l { text-align: left; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 12.5px; font-weight: 400; color: #2a2418; }
/* the better value carries more ink — never a colour. Green-good and red-bad would turn a record
 * between friends into a verdict, and claret is structure here, not a judgement. */
.h2h-t td.w { color: #2a2418; font-weight: 700; }
.h2h-t tr.grp td { padding: 11px 0 3px; font-size: var(--pld-fs-8); font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase; color: #9c8a5c; text-align: left; border: 0; }
.h2h-t tr.grp td span { float: right; opacity: .75; letter-spacing: .12em; }
.h2h-foot { margin-top: 11px; font-size: var(--pld-fs-8); font-weight: 600; letter-spacing: .18em;
  text-transform: uppercase; color: #9c8a5c; }

@media (max-width: 520px) {
  .h2h { width: 100%; padding-left: 16px; padding-right: 16px; }
  .h2h-h { font-size: 23px; line-height: 23px; }
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
 * PHASE 2 — SEASON CARDS.
 *
 * A card is now a PERSON AND A YEAR, and a year picks a CHASSIS: "a new nameplate shape and
 * position and a new information layout", the way a trading-card set changes every year. The
 * classes below are set by mountCard (web/cardview.ts) and nowhere else:
 *
 *   .c-base  the league's own chassis — 2026 onward, and the all-time card
 *   .c-ov    2025's OVERPRINT: no nameplate object at all
 *   .f-plate the founders' treatment on the 2025 chassis — brushed metal, one ink separation
 *   .is-base an ordinary card on a chassis that also has a treated version
 *   .fb-felt / .fb-blue  the BINDING, and the whole of the 2026 founder card
 *   .s-felt / .s-prizm / .s-mosaic  the style the server chose (a hook; nothing hangs on it yet)
 *
 * Ported from the approved mockups — docs/mockups/gen_matrix.py + assemble_matrix.py. Where a
 * mockup and docs/PHASE2_PLAN.md disagree, the plan wins: it carries the owner's later
 * corrections (a blue border takes a BLUE scrim; Overprint + Plate for 2025).
 * ═══════════════════════════════════════════════════════════════════════════════════════════ */

/* ── THE MARKS. Four independent facts, so a stack rather than one badge. `.btier` used to BE
      the badge; it is now the column those badges sit in, and the plate moved to .bst. ── */
.btier { position: absolute; top: calc(var(--cw) * .147); left: calc(var(--cw) * .035); z-index: 6;
  display: flex; flex-direction: column; align-items: flex-start; gap: calc(var(--cw) * .010);
  background: none; border: 0; box-shadow: none; padding: 0; }
.bst { font-size: calc(var(--cw) * .0221); letter-spacing: .15em; text-transform: uppercase;
  font-weight: 700; color: var(--pld-gold-ink); border-radius: calc(var(--cw) * .012);
  padding: calc(var(--cw) * .012) calc(var(--cw) * .024);
  background: var(--pld-gold-grad); border: 1px solid var(--pld-gold-border);
  box-shadow: var(--pld-gold-bevel); white-space: nowrap; }
/* FOUNDER and INAUGURAL are not achievements you accumulate, so they are not struck in the
   league's brass — they are cut into the card's own ground, which is what an origin mark is. */
.bst-founder, .bst-inaugural { background: linear-gradient(180deg, rgba(45,6,12,.9), rgba(30,4,8,.95));
  color: #f2dfa8; border: 1px solid rgba(230,200,120,.55); box-shadow: none; }
.fb-blue .bst-founder, .fb-blue .bst-inaugural {
  background: linear-gradient(180deg, rgba(13,23,48,.9), rgba(8,14,28,.95));
  color: #eaf0ff; border-color: rgba(205,225,255,.5); }
/* ROOKIE is a plain card of stock, because a rookie card is plain stock. */
.bst-rookie { background: linear-gradient(180deg, #fdf7e9, #e4d5b2); color: #4a3810;
  border: 1px solid rgba(120,105,70,.5); box-shadow: none; }

/* ── THE HONEST LINE. An attested season has final standings and no dice at all; the stat bands
      are omitted and this stands where they were (owner: "omit, plus one honest line"). ── */
.bnote { font-size: calc(var(--cw) * .0285); line-height: 1.45; color: #6a5f42;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: rgba(138,102,24,.07); border-left: calc(var(--cw) * .008) solid #a89468;
  border-radius: calc(var(--cw) * .008);
  padding: calc(var(--cw) * .022) calc(var(--cw) * .026); }

/* ══ CHASSIS · THE OVERPRINT (2025) ═════════════════════════════════════════════════════════
   The nameplate stops being an OBJECT: no plate, no bar, no slip. The name is printed straight
   onto the photograph along the foot, over a rule — which is why nothing on this card fights the
   metal of a treatment. Type can be re-inked; a plate has to be rebuilt. */
/* THE FRONT ONLY. `.c-ov .bface` (0,2,0) also matched `.bback`, and it beat `.bback`'s own
   `background: var(--pld-vellum)` (0,1,0) — so a 2025 card's BACK lost its cream stock and let
   the claret felt binding show through its own dark-red type. The whole stat line, unreadable,
   on the showpiece; caught by screenshotting staging rather than by any assertion, which is
   what a visual check is for. The Overprint is a FRONT treatment and now says so. */
.c-ov .bfront { background: none; padding: calc(var(--cw) * .026); }
.c-ov .ov-mat { position: absolute; inset: calc(var(--cw) * .026); overflow: hidden;
  border-radius: calc(var(--cw) * .008); background: #4a0b11;
  box-shadow: inset 0 0 0 1px rgba(46,34,6,.45); }
.c-ov .l-photo { background-position: 50% 26%; }
.c-ov .y25-vig { position: absolute; inset: 0; z-index: 3;
  background: linear-gradient(180deg, rgba(24,4,8,.55) 0 16%, transparent 34%,
              rgba(18,3,6,.62) 62%, rgba(12,2,4,.94) 92%); }
.c-ov .ov-top { position: absolute; top: calc(var(--cw) * .026); left: calc(var(--cw) * .030);
  right: calc(var(--cw) * .030); z-index: 6; display: flex; justify-content: space-between;
  align-items: baseline; padding: calc(var(--cw) * .006) calc(var(--cw) * .010); border-radius: 3px;
  background: linear-gradient(180deg, rgba(10,2,4,.42), rgba(10,2,4,0)); }
.c-ov .ov-top span { font-size: calc(var(--cw) * .0195); letter-spacing: .28em; text-transform: uppercase;
  font-weight: 700; color: #fff2d6; text-shadow: 0 1px 2px rgba(10,2,4,.95), 0 0 10px rgba(10,2,4,.6); }
.c-ov .ov-top b { font: 700 calc(var(--cw) * .026)/1 ui-serif, Georgia, serif; color: #fff2d6;
  text-shadow: 0 1px 2px rgba(10,2,4,.95); }
/* THE MARKS. A column, because a 2025 card can carry two (Founder AND Inaugural season) and
   the corner is where they go — the chassis has no other furniture to compete with them. */
.c-ov .ov-mark { position: absolute; top: calc(var(--cw) * .086); right: calc(var(--cw) * .030); z-index: 6;
  display: flex; flex-direction: column; align-items: flex-end; gap: calc(var(--cw) * .008); }
.c-ov .ovm { font-size: calc(var(--cw) * .0175); letter-spacing: .2em; text-transform: uppercase;
  font-weight: 700; white-space: nowrap;
  color: #f0dca6; border: 1px solid rgba(240,220,168,.55); border-radius: 2px;
  padding: calc(var(--cw) * .006) calc(var(--cw) * .014); background: rgba(12,2,4,.4); }
.c-ov .ov-foot { position: absolute; left: 0; right: 0; bottom: 0; z-index: 7;
  padding: calc(var(--cw) * .16) calc(var(--cw) * .034) calc(var(--cw) * .040);
  /* the type IS this chassis, so it gets a guaranteed ground rather than trusting the picture */
  background: linear-gradient(180deg, rgba(12,2,4,0) 0%, rgba(12,2,4,.72) 42%, rgba(10,2,4,.94) 100%); }
.c-ov .ov-n { font-size: calc(var(--cw) * .086); font-weight: 700; line-height: .95; letter-spacing: -.02em;
  color: #fff6de; text-shadow: 0 1px 0 rgba(10,2,4,.9), 0 2px 10px rgba(10,2,4,.85); }
.c-ov .ov-rule { height: 2px; margin: calc(var(--cw) * .014) 0 calc(var(--cw) * .010);
  background: linear-gradient(90deg, #fff6de, #c0922f 46%, rgba(192,146,47,0) 100%); }
.c-ov .ov-s { font-size: calc(var(--cw) * .0215); letter-spacing: .16em; text-transform: uppercase;
  font-weight: 700; color: #f4e6c4; text-shadow: 0 1px 3px rgba(10,2,4,.9); }
/* THE BASELINE MUST NOT LOOK TREATED. The chassis ground carries a lit wash that reads as a
   finish the moment a founder card sits beside it, so the ordinary 2025 card gets a flat ground
   and a plain vignette — the card everyone else actually has. */
.is-base .ov-mat { background: #5a0d14; }
.is-base .l-burst { background:
  repeating-linear-gradient(90deg, rgba(255,240,205,.03) 0 1px, transparent 1px 6px),
  linear-gradient(180deg, #6b1119 0%, #5a0d14 58%, #4a0b11 100%); }
.is-base .y25-vig { background:
  linear-gradient(180deg, rgba(30,4,8,.45) 0 20%, transparent 42%, rgba(30,4,8,.8) 90%); }

/* ══ TREATMENT · THE PLATE ══════════════════════════════════════════════════════════════════
   Brushed metal with registration marks at the corners, and the portrait as a SINGLE INK
   SEPARATION. The plate is the same object whoever owns it — the only thing the person changes
   is the ink, claret for Josh and blue for Ben. Two earlier attempts got this wrong in the same
   way: a palette rule repainted the ground and took the registration marks with it, so Ben's
   plate stopped being a plate. The marks live on .ov-mat because .l-photo MULTIPLIES over it. */
.c-ov.f-plate .ov-mat {
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26'%3E%3Cpath d='M13 2v22M2 13h22' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3Ccircle cx='13' cy='13' r='6' fill='none' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3C/svg%3E") 6px 6px no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26'%3E%3Cpath d='M13 2v22M2 13h22' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3Ccircle cx='13' cy='13' r='6' fill='none' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3C/svg%3E") calc(100% - 6px) 6px no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26'%3E%3Cpath d='M13 2v22M2 13h22' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3Ccircle cx='13' cy='13' r='6' fill='none' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3C/svg%3E") 6px calc(100% - 6px) no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26'%3E%3Cpath d='M13 2v22M2 13h22' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3Ccircle cx='13' cy='13' r='6' fill='none' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3C/svg%3E") calc(100% - 6px) calc(100% - 6px) no-repeat,
    repeating-linear-gradient(96deg, rgba(255,255,255,.18) 0 1px, transparent 1px 3px),
    linear-gradient(158deg, #e9ebee, #c8ccd2 48%, #aeb3ba 100%); }
.c-ov.f-plate .l-photo { filter: grayscale(1) contrast(1.3); mix-blend-mode: multiply; }
/* …AND ON .l-burst, WHICH IS THE LAYER THAT ACTUALLY SHOWS. This is the one correction the
   mockup had already made and the port undid, so it is worth stating plainly: `.l-burst` is an
   OPAQUE conic gradient at z-index 1, painted INSIDE .ov-mat — so metal on the mat alone is
   metal behind a red starburst, and nothing of the Plate is visible. The portrait then
   multiplies over that starburst instead of over pale metal, which turns the showpiece into a
   dark duotone and drops the top line to ~3:1 contrast.
   The mockup's own words, at docs/mockups/assemble_matrix.py: "painting them on the mat
   underneath (my last attempt) put them behind an opaque burst, which is why nothing changed."
   The mat keeps its copy underneath — belt and braces, exactly as the mockup left it. */
.c-ov.f-plate .l-burst {
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26'%3E%3Cpath d='M13 2v22M2 13h22' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3Ccircle cx='13' cy='13' r='6' fill='none' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3C/svg%3E") 6px 6px no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26'%3E%3Cpath d='M13 2v22M2 13h22' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3Ccircle cx='13' cy='13' r='6' fill='none' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3C/svg%3E") calc(100% - 6px) 6px no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26'%3E%3Cpath d='M13 2v22M2 13h22' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3Ccircle cx='13' cy='13' r='6' fill='none' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3C/svg%3E") 6px calc(100% - 6px) no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26'%3E%3Cpath d='M13 2v22M2 13h22' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3Ccircle cx='13' cy='13' r='6' fill='none' stroke='rgba(20,3,7,.5)' stroke-width='1'/%3E%3C/svg%3E") calc(100% - 6px) calc(100% - 6px) no-repeat,
    repeating-linear-gradient(96deg, rgba(255,255,255,.18) 0 1px, transparent 1px 3px),
    linear-gradient(158deg, #e9ebee, #c8ccd2 48%, #aeb3ba 100%); }
/* THE INK. Claret by default; blue where the binding is blue — a claret separation under a navy
   edge was two materials arguing (owner's correction: a blue border takes a BLUE scrim). */
.c-ov.f-plate .y25-vig { background: #6e1019; mix-blend-mode: color; }
.fb-blue.c-ov.f-plate .y25-vig { background: #22407e; mix-blend-mode: color; }
/* Dark type on pale metal, for BOTH founders. The equal-specificity ordering bug this replaces:
   a .fb-blue rule sat after the .f-plate one and gave Ben a dark navy scrim UNDER dark type. */
.c-ov.f-plate .ov-foot { background: linear-gradient(180deg, rgba(236,238,241,0) 0%,
  rgba(232,235,239,.82) 42%, rgba(212,217,225,.97) 100%); }
.c-ov.f-plate .ov-n { color: #23272d; text-shadow: 0 1px 0 rgba(255,255,255,.8); }
.c-ov.f-plate .ov-s { color: #3f454d; text-shadow: none; }
.c-ov.f-plate .ov-rule { background: linear-gradient(90deg, #5c636c, #aeb3ba 50%, rgba(174,179,186,0)); }
.c-ov.f-plate .ov-top { background: linear-gradient(180deg, rgba(236,238,241,.5), rgba(236,238,241,0)); }
.c-ov.f-plate .ov-top span, .c-ov.f-plate .ov-top b { color: #31363d; text-shadow: none; }
.c-ov.f-plate .ovm { color: #2a2f36; border-color: rgba(60,66,74,.6); background: rgba(255,255,255,.45); }

/* ══ THE TWO BINDINGS ═══════════════════════════════════════════════════════════════════════
   The site's OWN felt and leather (assets/felt.jpg, assets/leather.jpg, the same plates
   --pld-felt-room and --pld-leather-room use), so a founder card is bound in the material the
   whole site is bound in. A generated substitute read as pebble rather than hide.
   This is the WHOLE of the 2026 founder card: cover the two edges and it is an ordinary card. */
.fdr .bside { padding: calc(var(--cw) * .046); }
.fdr .bface { inset: calc(var(--cw) * .046); }
.fb-felt .bside { background:
  linear-gradient(rgba(20,4,7,.30), rgba(8,1,3,.46)),
  url(assets/felt.jpg) repeat,
  #6e1019;
  background-size: auto, 300px 300px, auto; }
.fb-blue .bside { background:
  linear-gradient(rgba(9,15,28,.34), rgba(6,10,20,.5)),
  url(assets/leather.jpg) repeat,
  #13203c;
  background-size: auto, 210px 210px, auto; }
.fdr .bside::after {
  box-shadow: inset 0 0 0 1px rgba(240,220,168,.42),
              inset 0 calc(var(--cw) * .065) calc(var(--cw) * .12) rgba(255,255,255,.03); }
.frail { position: absolute; top: 0; bottom: 0; width: calc(var(--cw) * .046); z-index: 19;
  display: flex; align-items: center; justify-content: center; overflow: hidden; pointer-events: none; }
.frail.fl { left: 0; } .frail.fr { right: 0; }
.frail span { writing-mode: vertical-rl; text-orientation: mixed; white-space: nowrap;
  font-size: calc(var(--cw) * .0165); font-weight: 700; letter-spacing: .40em; text-transform: uppercase;
  background: var(--pld-gold-grad); -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; filter: drop-shadow(0 1px 0 rgba(20,3,7,.85)); }
.frail span::before { content: 'Founder \00b7  Founder \00b7  '; }
.frail span::after { content: ' \00b7  Founder \00b7  Founder'; }

/* THE LEFT RAIL IS MIRRORED, SO BOTH BASELINES FACE THE PICTURE (owner, 2026-08-16).
   `.frail span` is vertical-rl with no rotation, which puts BOTH baselines on the left — inward on
   the right rail, OUTWARD on the left, so the left one read backwards. A frame is a MIRRORED pair,
   not the same element twice, and mirroring also puts the rails in step with any vertical name set
   in a left-hand column: one rule for every piece of vertical type on the card — every baseline
   faces the picture.

   AND THE ROTATION MUST NOT TAKE THE LIGHT WITH IT. This card is lit from above and says so
   several ways over (--pld-gold-grad is 180deg pale-to-dark, the plate and node shadows all fall,
   the bindings darken downward). A 180-degree turn carries the drop-shadow AND the gradient round,
   so a naively rotated rail has a shadow that rises and metal brightest at its foot. The offset is
   written negative — which comes out falling once rotated — and the gradient is respecified at
   0deg so its pale stop lands at the card's top, matching the untouched right rail. */
.frail.fl span {
  transform: rotate(180deg);
  filter: drop-shadow(0 -1px 0 rgba(20,3,7,.85));
  background-image: linear-gradient(0deg,#ead7a0 0%,#d8b85f 20%,#c0922f 48%,#9c7322 74%,#7e5c1c 100%);
}

/* ══ THE YEAR PICKER — COVER FLOW ═══════════════════════════════════════════════════════════
   The mockup's finding, and the reason the stage widens: at the popover's natural 1.44 card
   widths ONE cover fills the frame and nobody would guess to drag it. A rack needs room.
   THE ROOM COMES FROM THE VIEWPORT, NOT FROM THE CARD. The first version also shrank --cw so the
   whole rack would fit, and that was a real cost paid by the wrong thing: the back trims to fit
   (fitBack), so a narrower card is a card with fewer rows on it — e2e caught the standalone page
   losing content it had room for. --cw therefore stays exactly what it is without a deck, the
   stage takes what the viewport allows, and where that is not enough the rack simply scrolls,
   which is what Cover Flow does on a narrow screen anyway.
   The SHEET needs no rule at all: it is sized off --cw, which has not moved. */
/* 92vw, the SAME cap the deckless stage uses, and for the same unglamorous reason: card.html
   wraps the stage in `.wrap { padding: 0 18px }`, so a stage sized at 96vw hangs 10px off the
   right of a 390px screen and takes the head-to-head sheet with it. e2e measures the sheet's
   right edge against the viewport, which is what caught it. */
/* 3.2 CARD WIDTHS, NOT 2.9 — the stack needs room to BE a stack (owner, 2026-08-16). A shoulder
   sits .975cw out and every cover past it adds a flat .085cw, so with the lit card at one END of
   the rack the outermost cover's far edge lands ~1.53cw from centre: 3.06cw of stage, against the
   2.9 there was. Measured with Josh's 2025 lit, the all-time cover ran [863,1154] inside a rack
   ending at 1127 — 27px of it cut off, and the two shoulders only 27px apart, so the pile read as
   one card with a damaged edge rather than as a stack.
   The 92vw cap is UNCHANGED and still the binding limit on a phone, for its own reason below. */
.cardstage.hasdeck { width: min(calc(var(--cw) * 3.2), 92vw); padding-top: 18px; }
/* NO SCRIM. The rack sits on the page, not on a lit black shelf — the covers carry their own
   depth (turn, scale, falling light) and a backdrop was doing that job twice. What the wrap does
   is RANK: z-index 3 over the sheet's 2, matching .bcard over .h2h, so the lit cover's foot
   paints in front of the vellum and nothing else covers the sheet's top edge. */
.cfwrap { position: relative; z-index: 3; width: 100%; }
.cf { position: relative; z-index: 1; overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory; scrollbar-width: none;
  /* NO BOTTOM PADDING beyond this: the sheet tucks up by -.07cw expecting to meet the CARD, and
     any more here means it tucks under a dark band and leaves a gap. */
  padding: calc(var(--cw) * .09) 0 calc(var(--cw) * .05); }
.cf::-webkit-scrollbar { display: none; }
/* THE PERSPECTIVE LIVES ON THE TRACK, not on each card: one shared vanishing point (moved by JS
   to follow the visible centre) is what makes each cover a true TRAPEZOID. It cannot go on .cf,
   because overflow-x:auto flattens 3D. */
.cf-track { display: flex; align-items: center; width: max-content;
  padding-inline: calc(50% - var(--cw) / 2); perspective: 1100px; transform-style: preserve-3d; }
.cf-item { flex: 0 0 var(--cw); scroll-snap-align: center; position: relative;
  transform-style: preserve-3d; }
.cf-inner { transform-origin: 50% 50%; will-change: transform; transform-style: preserve-3d; }
/* every cover carries a hairline, so a PILE of slivers reads as separate cards rather than one
   mass — it is what you actually see of a card turned nearly side-on. */
.cf-item .bcard { box-shadow: 0 0 0 1px rgba(58,26,14,.30), 0 10px 26px rgba(0,0,0,.34); }
/* NO reduced-motion rule here, and that is not an omission. The rack's animation comes from
   `cf.scrollTo({ behavior: 'smooth' })`, and an explicit behavior in ScrollToOptions wins over
   the CSS property outright — so `scroll-behavior: auto` here would set a property nothing
   reads to a value it already had, in both media states. cardview.ts checks the media query
   itself, which is the only place that can actually decide it. */

/* ── the per-year callout: the ONE line of the sheet that follows the card's year, kept visually
      apart from the lifetime numbers so the two can never be mistaken for each other ── */
.h2h-season { display: flex; align-items: baseline; gap: 10px; margin: 2px 0 10px;
  padding: 7px 10px; border-radius: 5px; background: rgba(120,96,40,.10);
  border-left: 3px solid var(--pld-claret); font-size: 12.5px; color: #6a5f42; }
.h2h-season.empty { background: rgba(120,96,40,.05); border-left-color: #cbbb90; color: #8a7c58; }
.h2h-season b { color: #3d1116; letter-spacing: .06em; text-transform: uppercase; font-size: 11px; }
.h2h-season i { font-style: normal; opacity: .5; padding: 0 .015em; }
.h2h-h em { font-style: normal; font-size: 11px; letter-spacing: .12em; text-transform: uppercase;
  color: #8a7c58; margin-left: 6px; }


/* The mid-rule between the headline four and the reveal four: one block, two populations.
   IT CARRIES ITS OWN TYPE. The first version reused `.bsx`, whose rule is `.bsec .bsx` — a
   DESCENDANT selector, so outside a section heading it applied nothing at all and the caption
   rendered at body size, wrapped to two lines and dominated the card. */
/* (.bmid is gone. It styled a band caption, and both bands that had one lost it: the online
   zone's heading names its own population, and the rank band's cells say "35.0% wins" — the
   caption was saying underneath what the numbers already said.) */

/* A VALUE NEVER BREAKS MID-FIGURE. "47g · 14w · 29.8%" wrapping between the parts reads as two
   facts rather than one, and a narrow card is exactly where that happens. The LABEL column may
   wrap — it is prose — so the row still fits; only the numbers are held together. */
.bt.bfx td.pl { white-space: nowrap; }
.bt.bfx td.l { white-space: normal; }

/* THE RANK BAND — the same k/v/n cell as the other two bands, at a smaller value size because
   "16th of 20" is a phrase where those carry a number. Four columns in ~285px is ~66px each. */
/* flex: none FOR THE SAME REASON .bzone HAS IT, and it became load-bearing the moment the owner
   moved this band OUT of the sanctioned zone (2026-08-16): as a bare child of .bback it is now a
   flex item in a column, and a flex item shrinks by default. A shrunk grid overflows ITSELF, not
   the card, which leaves .bback's scrollHeight equal to its clientHeight — the exact question
   fitBack asks — so the trim would never fire and the rows would silently overlap the footer.
   Inside a zone it was a grid item and this could not happen; outside one it can. */
.bstats.rk { margin-top: 0; flex: none; }
.bstats.rk > div { background: rgba(138,102,24,.045); }
.bstats.rk .k { color: #97865a; }
.bstats.rk .v { font-size: calc(var(--cw) * .0285); letter-spacing: -.01em; white-space: nowrap; }



/* ═══════════════════════════════════════════════════════════════════════════════════════════
   THE CAREER FRONT — PORTED VERBATIM from the settled study (docs/mockups/line-bands.html).
   Hand-rewriting it produced six divergences the owner had to find one at a time; this is the
   study's own CSS with three mappings and nothing else re-derived:
     · .d7            -> .bcareer
     · the study's local gold/bone tokens -> the shipped --pld-* ones
     · the study's BINDING (--bind/.b-felt/.b-stock) is dropped: production paints the binding on
       .bside and the rails are .frail, both already correct on this card.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */



/* ══ III · THE LINE ═════════════════════════════════════════════════════════
   One gold rule, entering at the top edge and leaving at the bottom, with a node
   for every night played. The die cuts the line; the line does not end. */
.bcareer{background:#0a0d18}

.bcareer .ln-ph{position:absolute; inset:0; z-index:1; background-size:cover; background-position:50% 42%;
  filter:contrast(1.05) saturate(.9)}

.bcareer .ln-grade{position:absolute; inset:0; z-index:2;
  background:
    linear-gradient(90deg,rgba(6,3,8,.94) 0 24%,rgba(6,3,8,.80) 34%,rgba(6,3,8,.42) 58%,rgba(6,3,8,.28) 100%),
    linear-gradient(180deg,rgba(6,3,8,.72) 0 10%,transparent 26%,transparent 66%,rgba(6,3,8,.86) 100%)}

.bcareer .ln-grade{mix-blend-mode:normal}

.bcareer .ln-rule{position:absolute; left:26.5%; top:calc(var(--cw)*-.02); bottom:calc(var(--cw)*-.02);
  width:calc(var(--cw)*.008); z-index:4;
  background:linear-gradient(180deg,#7e5c1c,#ead7a0 8%,#c0922f 50%,#ead7a0 92%,#7e5c1c);
  box-shadow:0 0 calc(var(--cw)*.03) rgba(230,200,120,.35)}

.bcareer .ln-name{position:absolute; left:0; top:0; bottom:0; width:26.5%; z-index:5;
  display:flex; align-items:center; justify-content:center}

.bcareer .ln-name span{writing-mode:vertical-rl; transform:rotate(180deg); white-space:nowrap;
  font-size:calc(var(--cw)*.098); line-height:1; letter-spacing:.035em; text-transform:uppercase;
  color:var(--pld-bone); text-shadow:0 1px 0 rgba(6,3,8,.95),0 0 calc(var(--cw)*.04) rgba(6,3,8,.9)}

.bcareer .ln-rail{position:absolute; top:calc(var(--cw)*.034); left:32%; right:calc(var(--cw)*.040);
  z-index:6; display:flex; flex-direction:column; align-items:flex-end; gap:calc(var(--cw)*.004)}

.bcareer .ln-rail span{font-size:calc(var(--cw)*.030); letter-spacing:.26em; text-transform:uppercase;
  color:var(--pld-gold-bright)}

.bcareer .ln-rail b{font-size:calc(var(--cw)*.0195); letter-spacing:.20em; text-transform:uppercase;
  color:rgba(246,238,217,.60)}

.bcareer .ln-nodes{position:absolute; inset:0; z-index:6}

.bcareer .ln-node{position:absolute; left:26.5%; transform:translate(-50%,-50%)}

.bcareer .ln-node o{display:block; width:calc(var(--cw)*.030); height:calc(var(--cw)*.030);
  border-radius:50%; background:var(--pld-gold-grad); border:1px solid var(--pld-gold-border);
  box-shadow:0 0 0 calc(var(--cw)*.008) rgba(6,3,8,.75),0 1px 2px rgba(0,0,0,.6)}

.bcareer .ln-node.win o{width:calc(var(--cw)*.052); height:calc(var(--cw)*.052);
  box-shadow:0 0 0 calc(var(--cw)*.008) rgba(6,3,8,.8),
             0 0 0 calc(var(--cw)*.013) rgba(230,200,120,.85),
             0 0 calc(var(--cw)*.05) rgba(230,200,120,.5)}

.bcareer .ln-node u{position:absolute; left:calc(var(--cw)*.052); top:50%; transform:translateY(-50%);
  text-decoration:none; white-space:nowrap; display:block}

.bcareer .ln-node.win u{left:calc(var(--cw)*.062)}

.bcareer .ln-node u b{display:block; font-size:calc(var(--cw)*.0305); letter-spacing:.08em;
  text-transform:uppercase; color:#fff6de; line-height:1.05;
  text-shadow:0 1px 2px rgba(6,3,8,.95)}

.bcareer .ln-node u s{display:block; text-decoration:none; font-size:calc(var(--cw)*.0245);
  letter-spacing:.10em; text-transform:uppercase; color:var(--pld-gold-bright);
  text-shadow:0 1px 2px rgba(6,3,8,.95)}

.bcareer .ln-node u em{display:block; font-style:normal; font-size:calc(var(--cw)*.0205);
  letter-spacing:.18em; text-transform:uppercase; color:rgba(246,238,217,.55);
  text-shadow:0 1px 2px rgba(6,3,8,.95)}

/* at ten seasons the ordinary nights become ticks and only the titles keep a label */
.bcareer.many .ln-node o{width:calc(var(--cw)*.020); height:calc(var(--cw)*.020)}

.bcareer.many .ln-node.win o{width:calc(var(--cw)*.044); height:calc(var(--cw)*.044)}

.bcareer .ln-foot{position:absolute; left:calc(var(--cw)*.040); right:calc(var(--cw)*.040);
  bottom:calc(var(--cw)*.036); z-index:7; display:grid; grid-template-columns:repeat(4,1fr);
  gap:calc(var(--cw)*.010); border-top:1px solid rgba(240,220,168,.30);
  padding-top:calc(var(--cw)*.016)}

.bcareer .ln-foot div{text-align:left}

.bcareer .ln-foot b{display:block; font-size:calc(var(--cw)*.050); line-height:1; color:#fff6de;
  font-variant-numeric:tabular-nums; text-shadow:0 1px 3px rgba(6,3,8,.95)}

.bcareer .ln-foot i{display:block; font-style:normal; font-size:calc(var(--cw)*.0195);
  letter-spacing:.16em; text-transform:uppercase; color:var(--pld-gold-bright);
  margin-top:calc(var(--cw)*.004); text-shadow:0 1px 2px rgba(6,3,8,.95)}


/* ── III · the Line: the ground is the PHOTOGRAPH, not the binding, so nothing here changes for a
      player with no binding except the tint. This is the only round-one direction that is
      already binding-independent. ── */
.bcareer .ln-ph.ph-rafal{background-position:42% 30%}


/* ── III · THE LINE'S BACK — the rule passes THROUGH the card, so it is on the back at the same
      x, entering and leaving the same way. The stock stays vellum: a dark back would have been
      the direction's logic and the set's loss, and the rule is the direction's logic too. ── */
.d3b .ln-rule{left:14%; top:calc(var(--cw)*-.02); bottom:calc(var(--cw)*-.02);
  width:calc(var(--cw)*.006); z-index:6;
  background:linear-gradient(180deg,#7e5c1c,#c0922f 6%,#8a6618 50%,#c0922f 94%,#7e5c1c);
  box-shadow:none; position:absolute}




/* @@VII-CSS-BEGIN@@ */
/* ══════════════════════════════════════════════════════════════════════════════════════════
   VII · THE LINE, IN BANDS  (III × IV) — the Line's chassis carrying a VERTICAL RUN, with
   every band EYE-CENTRED on its own year's print, and the whole plate set into an L.

   Most of III is still reused verbatim (these cards carry BOTH `d3` and `d7`): the nodes, the
   name set vertically alongside the rule, the rail, the stat figures. `d7` replaces the single
   .ln-ph photograph with N horizontal BANDS, oldest at the top, and then re-cuts the plate.

   ROUND 5 CHANGES FOUR THINGS, and each one is drawn rather than argued:

   1 · THE L. The bands and the gold rule both stop at --plh, where the stat block begins; the
       block runs the full width of the face beneath them. So the plate is a name column, the
       banded picture to its right, and a full-width foot under both — a vertical stroke with a
       horizontal one at its base. The rule NO LONGER BLEEDS OFF THE BOTTOM DIE, and it no
       longer bleeds off the top either: it starts inside the border and lands on the foot's
       own rule. That was III's one promise and the L retires it; nothing below still claims it.

   2 · THE PICTURE IS NARROWER, AND THEREFORE BIGGER. A sheet used to be scaled to cover the
       whole face, which meant .265 of every print was posted under the name column and thrown
       away in the scrim. The sheet now covers --pw, which runs from the face's right edge inward
       to the MIDDLE of the name column (--pl = --nw/2). Half the name column still sits over
       picture — the type still reads over the photograph — and the other half sits over the
       plate's own dark ground. The print is scaled 1/(1 − --nw/2) = .867× of what it was, so
       the part of it in the clear is that much larger and that much less cropped.

   3 · THE SEPARATORS ARE THINNER AND SHORTER. .0055cw rather than .0090, and they run from the
       VERTICAL RULE rightward to the die edge only. A separator crossing the name column was
       ruling the type, not the picture.

   4 · THE YEAR IS STRUCK ABOVE THE LINE THAT CLOSES ITS BAND, not below the one that opens it.
       Under the old rule the top band had no line above it, so its year had to be pushed down
       past the head rail — where it sat beside ALL-TIME and competed with it. Anchoring every
       year to the FOOT of its own band removes that collision by construction: no year label
       is anywhere near the rail any more, and the newest band's year takes the L's horizontal
       as its line, so it is the same rule rather than a special case.

   THE EYE-CENTRING ARITHMETIC is unchanged in form and re-based on the new picture box:
     --sh = max( --pw × 1.4085   — the height that covers the PICTURE width at the print's aspect
                 --bh / 2fy      — below which centring the eyes opens a gap at the TOP
                 --bh / 2(1−fy)  — below which it opens one at the BOTTOM )
     --sw = --sh × .710 — every print here is 710×1000 (Rafal's 864×1216 is the same ratio to
            three places), so nothing is ever stretched.
     top  = --bh/2 − fy·--sh   and   left = --pw/2 − fx·--sw,   each CLAMPED to the band.
   Which of the three terms binds now differs per print, and the narrowing moved the line: see
   the table in the section body. It is checked in a render with the centre lines drawn, never
   in the stylesheet.

   THE BORDER IS NOW UNIVERSAL. --fb was 0 for everyone but a founder, so an ordinary member's
   card had no edge at all and its timeline started on the die. Every card now carries one:
   .046cw of the founder's own binding for the two who have one, .026cw of the set's ivory card
   STOCK for everybody else (public/card.css `.bside` default). One set of rules draws both, the
   timeline starts inside it either way, and the difference between the two objects is the
   material and the rails — not whether the card has an edge.
   ══════════════════════════════════════════════════════════════════════════════════════════ */
.bcareer{
  --nw:.265;                                   /* the name column, as a fraction of the face   */
  --fb:calc(var(--cw)*.026);                   /* THE BORDER — card stock; a founder overrides */
  --fw:calc(var(--cw) - 2*var(--fb));          /* face width                                   */
  --fh:calc(var(--cw)*1.4 - 2*var(--fb));      /* face height                                  */
  --pl:calc(var(--fw) * var(--nw) / 2);        /* the picture starts HALF WAY INTO the name     */
  --pw:calc(var(--fw) - var(--pl));            /* ...so THIS is what a sheet has to cover       */
  --sbh:calc(var(--cw)*.132);                  /* the stat block — the L's foot                 */
  --plh:calc(var(--fh) - var(--sbh));          /* the banded picture ends here                  */
  --bh:calc(var(--plh) / var(--n));
  --bl:0px;
  /* the fallback focal point is the middle of the frame, which is exactly what a system with
     no stored focal points has. Every specimen below overrides it. */
  --fx:.5; --fy:.5;
  }




/* HAND-TUNED FOCAL POINTS, AND THEY ARE HAND-TUNED. --fx/--fy are the midpoint between the
   subject's eyes as a fraction of the print, read off the five photographs by eye and then
   corrected against screenshots with the band centre lines drawn in. Nothing in this system
   stores such a point: there is no focal-point column on a member, and no face detection
   anywhere in the upload path. These five numbers do not exist outside this file. */
.bcareer .ph-josh25{--fx:.542;  --fy:.187}

.bcareer .ph-josh26{--fx:.676;  --fy:.402}

.bcareer .ph-ben25 {--fx:.549;  --fy:.212}

.bcareer .ph-ben26 {--fx:.503;  --fy:.378}

.bcareer .ph-rafal {--fx:.380;  --fy:.358}


/* the face: the card inside its border, whoever the border belongs to. Its ground is dark, so
   the name column and the plinth under the bands are one continuous plate. */
.bcareer .cl-face{position:absolute; inset:var(--fb); z-index:1; overflow:hidden; background:#0a0d18;
  border-radius:calc(var(--cw)*.008);
  box-shadow:0 0 0 1px rgba(240,220,168,.42),0 calc(var(--cw)*.006) calc(var(--cw)*.018) rgba(0,0,0,.55)}

/* isolation IS LOAD-BEARING. The bands carry z-index:calc(50 - --i) so that a soft seam's
   fade reveals the band beneath it; without a stacking context around them those 50s were
   siblings of .ln-grade (z-index:2) and the photograph painted OVER the grade. */
.bcareer .cl-plate{position:absolute; left:var(--pl); top:0; right:0; height:var(--plh);
  z-index:0; isolation:isolate; background:#0a0d18; overflow:hidden}


/* ONE BAND PER SEASON. --i is the band index (0 = oldest, at the top); --n is the number of
   seasons and is written once, on the card. */
.bcareer .cl-band{position:absolute; left:0; right:0; overflow:hidden;
  top:calc(var(--i) * var(--bh));
  height:calc(var(--bh) + var(--bl));
  z-index:calc(50 - var(--i));
  -webkit-mask-image:linear-gradient(180deg,#000 calc(100% - var(--bl)),transparent 100%);
          mask-image:linear-gradient(180deg,#000 calc(100% - var(--bl)),transparent 100%)}

.bcareer .cl-band:last-child{--bl:0px}

/* ↓↓ the invariant. Each sheet is its OWN print, scaled and offset to put ITS eyes on THIS
      band's centre line. Sized from --fy, not from --i: no band knows about any other. ↓↓ */
.bcareer .cl-sheet{position:absolute;
  --sh:max(calc(var(--pw) * 1.4085),
           calc(var(--bh) / (2 * var(--fy))),
           calc(var(--bh) / (2 - 2 * var(--fy))));
  --sw:calc(var(--sh) * .710);
  width:var(--sw); height:var(--sh);
  top:clamp(calc(var(--bh) - var(--sh)), calc(var(--bh)/2 - var(--fy)*var(--sh)), 0px);
  left:clamp(calc(var(--pw) - var(--sw)), calc(var(--pw)/2 - var(--fx)*var(--sw)), 0px);
  background-repeat:no-repeat; background-size:cover; background-position:50% 50%;
  filter:contrast(1.05) saturate(.9)}


/* THE SEAM. A hairline of the rule's own metal laid into the join, with a printed shadow above
   and below it — the Line already carries exactly this material down its spine, so the join is
   furniture rather than damage. It now runs from the VERTICAL RULE to the die edge and no
   further: left:--nw of the face is the rule's own x. */
.bcareer .cl-seams{position:absolute; left:calc(var(--fw) * var(--nw)); right:0; top:0;
  height:var(--plh); z-index:3; pointer-events:none}

/* THE KEYLINE IS NOT DECORATION. At .0055cw of bare gold the seam VANISHES over Rafal's gold
   mosaic and over the stadium lights behind Josh's 2025 print — measured in the render, not
   guessed. A 1px dark ring is what makes a gold rule read on a gold picture; it is the same
   trick .ln-node already uses to keep its gold discs off a gold ground. */
.bcareer .cl-seams i{position:absolute; left:0; right:0; height:calc(var(--cw)*.0055);
  top:calc(var(--i) * var(--bh)); transform:translateY(-50%);
  background:linear-gradient(90deg,#7e5c1c,#ead7a0 10%,#c0922f 50%,#ead7a0 90%,#7e5c1c);
  box-shadow:0 0 0 1px rgba(6,3,8,.88),
             0 calc(var(--cw)*.005) calc(var(--cw)*.012) rgba(6,3,8,.78),
             0 calc(var(--cw)*-.003) calc(var(--cw)*.009) rgba(6,3,8,.62)}

.bcareer.seam-hard .cl-seams,.bcareer.seam-soft .cl-seams{display:none}

.bcareer.seam-soft{--bl:calc(var(--cw)*.030)}


/* THE YEAR, struck immediately ABOVE the line that CLOSES its band, at the right die edge. The
   newest band's closing line is the stat block's own rule, so it needs no special case — and
   nothing lands near the head rail, which is what used to collide with ALL-TIME. */
.bcareer .cl-yrs{position:absolute; inset:var(--fb); z-index:6; pointer-events:none}

.bcareer .cl-yr{position:absolute; text-decoration:none; right:calc(var(--cw)*.034);
  top:calc((var(--i) + 1) * var(--bh) - var(--cw)*.016); transform:translateY(-100%);
  font-size:calc(var(--cw)*.030); line-height:1; letter-spacing:.13em; color:var(--pld-gold-bright);
  text-shadow:0 1px 3px rgba(6,3,8,.98),0 0 calc(var(--cw)*.02) rgba(6,3,8,.9)}

.bcareer.many .cl-yr{font-size:calc(var(--cw)*.0225); letter-spacing:.05em;
  top:calc((var(--i) + 1) * var(--bh) - var(--cw)*.010)}


/* ── the Line's own furniture, re-cut to the L. Everything that used to run the height of the
      card now runs the height of the PICTURE and stops on the foot's rule. ── */
.bcareer .ln-name{left:var(--fb); top:var(--fb); bottom:auto; height:var(--plh);
  width:calc(var(--fw) * var(--nw))}

.bcareer .ln-rail{top:calc(var(--fb) + var(--cw)*.034);
  left:calc(var(--fb) + var(--fw)*.320);
  right:calc(var(--fb) + var(--cw)*.040)}

.bcareer .ln-nodes{top:var(--fb); left:var(--fb); right:var(--fb); bottom:auto; height:var(--plh)}

/* THE RULE is now a BRACKET, not a through-line: it starts inside the border and ends exactly
   on the stat block's rule. Those two strokes are the L. */
.bcareer .ln-rule{left:calc(var(--fb) + var(--fw)*var(--nw)); top:var(--fb); bottom:auto;
  height:var(--plh); width:calc(var(--cw)*.008)}

/* the picture's grade, cut to the picture rather than to the card: the plinth under the bands
   is plate, not photograph, so it must not be washed as though it were one. The top wash is
   tighter than III's because all it still has to do is keep the rail legible — the stat row
   is no longer over a band, so the bottom wash is only a seating shadow. */
.bcareer .ln-grade{left:0; top:0; right:0; bottom:auto; height:var(--plh);
  background:
    linear-gradient(90deg,rgba(6,3,8,.975) 0 25%,rgba(6,3,8,.62) 33%,
                    rgba(6,3,8,.32) 52%,rgba(6,3,8,.20) 100%),
    linear-gradient(180deg,rgba(6,3,8,.80) 0 5%,rgba(6,3,8,.40) 10%,transparent 16%,
                    transparent 78%,rgba(6,3,8,.60) 100%)}


/* THE FOOT — the L's horizontal. Full width of the face, its own rule drawn in the seam's
   metal so the two strokes are visibly one object, and it sits on the plate rather than on a
   photograph, which is what buys the newest band its whole height back. */
.bcareer .ln-foot{left:var(--fb); right:var(--fb); bottom:var(--fb); top:calc(var(--fb) + var(--plh));
  height:var(--sbh); border-top:0; padding:calc(var(--cw)*.020) calc(var(--cw)*.030) 0;
  align-content:start}

.bcareer .ln-foot::before{content:''; position:absolute; left:0; right:0; top:0;
  height:calc(var(--cw)*.0055); transform:translateY(-50%);
  background:linear-gradient(90deg,#7e5c1c,#ead7a0 10%,#c0922f 50%,#ead7a0 90%,#7e5c1c);
  box-shadow:0 0 0 1px rgba(6,3,8,.88),0 calc(var(--cw)*.005) calc(var(--cw)*.012) rgba(6,3,8,.78)}


/* PLAYED, NO PLACE. Ben Stern is in the LA Open's record with no finishing position at all, so
   his node is drawn HOLLOW: on the line, carrying no ordinal. Every other node on these cards
   is a filled disc because every other node has a place behind it. Giving him a number would
   be the one thing this card must not do. */
.bcareer .ln-node.up o{background:rgba(6,3,8,.62); border-color:rgba(230,200,120,.85);
  box-shadow:0 0 0 calc(var(--cw)*.008) rgba(6,3,8,.75),
             inset 0 0 0 calc(var(--cw)*.004) rgba(230,200,120,.30)}

.bcareer .ln-node.up u s{color:rgba(246,238,217,.66); letter-spacing:.13em}

/* A NODE CANNOT LEAVE ITS OWN YEAR. --bi is the band index the event's year resolves to and
   --f is its evenly-spaced share of that band — the k-th of m events in a year sits at
   (k+1)/(m+1). Both are written by the builder from the event list; no percentage is typed by
   hand anywhere in this section, which is the whole point: a hand-typed top is exactly how the
   LA Open once came to be drawn in the wrong year. */
.bcareer .ln-node{top:calc((var(--bi) + var(--f)) * var(--bh))}

/* the standing line — cardview.ts prints "1 of 2 · Founded the league" under the name, so on a
   card whose name is vertical it is set vertically beside it, and it stops on the L too */
.bcareer .cl-sub{position:absolute; left:calc(var(--fb) + var(--cw)*.026); top:var(--fb);
  height:var(--plh); bottom:auto; z-index:7;
  display:flex; flex-direction:column; align-items:center; justify-content:center}

.bcareer .cl-sub span{writing-mode:vertical-rl; transform:rotate(180deg); white-space:nowrap;
  font-size:calc(var(--cw)*.0205); letter-spacing:.19em; text-transform:uppercase;
  color:var(--pld-gold-bright); text-shadow:0 1px 2px rgba(6,3,8,.98)}

/* @@VII-CSS-END@@ */



/* ══════════════════════════════════════════════════════════════════════════════════════════
   ITERATION OVERRIDES — appended last so they win, and so a change is a two-line edit in a
   small file rather than surgery inside a 1.3 MB stylesheet. Fold them into the rules above
   once a decision sticks.
   ══════════════════════════════════════════════════════════════════════════════════════════ */

/* 1 · THE BORDER SITS WHERE A FOUNDER'S BINDING SITS (owner). --fb was .026cw for card stock
   and .046cw for a founder, so an ordinary member's face was 2 x .020cw WIDER than a founder's
   and the card read as a bigger object. The border is a MATERIAL difference, not a geometric
   one: one inset for everybody, so every plate in the set is the same size. */
.bcareer{ --fb:calc(var(--cw)*.046) }


/* 2 · THE BADGE MOVES INTO THE NAME COLUMN, TOP LEFT (owner). It used to start at 32% of the
   face — to the right of the rule, over the picture — which spent picture width on chrome and
   put "All-Time" and the wordmark on top of a photograph. In the name column it sits on the
   plate, over nothing, and the column was already the quietest part of the card. */
.bcareer .ln-rail{
  left:var(--fb); right:auto; top:calc(var(--fb) + var(--cw)*.030);
  width:calc(var(--fw) * var(--nw));
  align-items:center; text-align:center;
  gap:calc(var(--cw)*.008);
}

.bcareer .ln-rail span{ font-size:calc(var(--cw)*.030); letter-spacing:.16em; line-height:1 }

.bcareer .ln-rail b{
  font-size:calc(var(--cw)*.0175); letter-spacing:.055em; line-height:1.15;
  max-width:100%; white-space:normal; text-align:center;
}


/* 3 · THE BADGE FILLS THE NAME COLUMN (owner). It was set at a size chosen for the old position
   over the picture; in the column it should measure the column. The wordmark is the wider of the
   two lines, so it sets the scale and ALL-TIME is sized off it. */
.bcareer .ln-rail{ padding:0 calc(var(--cw)*.010) }

.bcareer .ln-rail span{ font-size:calc(var(--cw)*.0330); letter-spacing:.135em }

.bcareer .ln-rail b{ font-size:calc(var(--cw)*.0282); letter-spacing:.012em; line-height:1.12 }


/* 4 · THE SUB-LINE IS CENTRED WITH THE NAME, not beside it (owner). It used to be its own
   absolutely-positioned column at left+.026cw, centred against the FACE — so it shared no axis
   with the name it belongs to. Now both sit in one flex row that is centred in the name column,
   and the pair reads as one block with a common centre line. */
.bcareer .ln-name{
  left:var(--fb); width:calc(var(--fw) * var(--nw));
  display:flex; flex-direction:row; align-items:center; justify-content:center;
  gap:calc(var(--cw)*.012);
}

.bcareer .cl-sub{
  position:static; left:auto; top:auto; height:auto; width:auto;
  display:flex; align-items:center; justify-content:center;
  flex:none;                    /* vertical-rl text has no intrinsic main size in a row: without
                                   this the flex line squeezes both children to a few px wide */
}

.bcareer .ln-name > span{ flex:none }
             /* right rail: baseline on its LEFT   */

/* 7 · THE LIGHT COMES FROM ABOVE, AND A ROTATION MUST NOT TAKE IT WITH THE TYPE (owner asked
   where the light was coming from; the card answers unanimously). Every depth cue agrees and they
   were written at different times: --gold-grad is 180deg pale-to-dark, .card carries three
   shadows all at +y, .cl-face +.006cw, .cl-seams i +.005cw, .ln-node o 0 1px 2px, the felt and
   hide bindings darken downward, and the card-stock binding is 158deg lightest-first.

   Three spans are set with transform:rotate(180deg) so they read bottom-to-top — and a rotation
   carries the shadow and the gradient round with it, so all three were lit FROM BELOW while the
   chassis around them was lit from above. The name and the sub have been wrong since this
   direction was first drawn; the left rail became wrong when the rails were mirrored, which is
   mine. Offsets are therefore written NEGATIVE inside the rotated element, which comes out
   falling in card space, and the flipped rail gets a reversed gradient so its metal brightens at
   the top like its unrotated twin. */
.bcareer .ln-name > span{ text-shadow:0 -1px 0 rgba(6,3,8,.85) }

.bcareer .cl-sub span{ text-shadow:0 -1px 2px rgba(6,3,8,.98) }



/* 6 · THE SEAM, ONE PIXEL THINNER (owner). .0055cw is 1.87px on a 340px card; .0026cw is 0.88px —
   a pixel off, and still proportional, so the 2x specimens stay in step instead of the seam
   becoming hairline on a big card and invisible on a small one. */
.bcareer .cl-seams i{ height:calc(var(--cw)*.0026) }


/* 8 · MINIMUM SCALE — THE EYES LAND IN THE BAND, NOT ON ITS CENTRE LINE (owner, decided).
   --sh was max(cover, bh/2fy, bh/(2-2fy)); the two ratio terms are what dragged a print's pupils
   onto the band's centre, and they are gone. What is left is plain cover.

   Nothing else has to change, which is the point: `top` is already
   clamp(bh - sh, bh/2 - fy*sh, 0). It still pulls the eyes toward the centre when there is room,
   and it still guarantees they stay INSIDE the band — clamped high means fy*sh < bh/2, clamped low
   means bh - sh(1-fy) > bh/2, and unclamped is the centre exactly. So "eyes in the band" needs no
   new guarantee; it falls out of the clamp that was already there.

   Measured: Josh's 2025 band 2.67 -> 1.89 x band height (-29%), Ben's 2.36 -> 1.89 (-20%), Rafal
   unchanged, and every 7-band card identical at 6.60 — from three bands up the cover term already
   dominated both ratio terms, so this only ever changed the two-band founders. What it buys is
   picture: the cup, the shirt and the stadium come back into Josh's frame. What it costs is that
   the eyes sit high in the band rather than on its centre. */
.bcareer .cl-sheet{ --sh:max(calc(var(--pw) * 1.4085), var(--bh)) }


/* 12 · THE CREST REPLACES THE WORDMARK IN THE BADGE (owner). The text stays in the markup and is
   pushed out of view rather than deleted — it is the accessible name of the block, and a
   background-image has none.
   A NOTE ON SIZE, because the guidelines set a floor: /design/index.html says the crest "needs
   ~96px to keep the plaque legible", and the name column is .265 of the face — about 82px on a
   340px card, so the crest lands nearer 58px. Below the floor. It is drawn here so it can be
   judged, but if it reads as mush at card size the honest options are the BADGE (pld-icon, which
   the same guidelines say reads down to ~28px) or going back to type. */
/* the crest leads the badge (owner) — `order` rather than re-cutting the markup on 21 cards */
.bcareer .ln-rail b.crest{ order:-1;
  display:block; width:100%; height:calc(var(--cw)*.16);
  background:url("/assets/logo.png") center/contain no-repeat;
  font-size:0; line-height:0; color:transparent; overflow:hidden;
}


/* 13 · A BIG BUBBLE NEEDS THE LABEL TO STAND BACK (owner). A .win node is .052cw across against
   an ordinary .030cw, and its two rings add another .013cw of glow — so its visual radius is
   about .039cw where an ordinary node's is .023cw. The label sits at a FIXED .052cw from the
   centre, which leaves an ordinary node .029cw of air and a champion node .013cw: the type
   crowds the very bubble the size was meant to celebrate. The win label moves out to .070cw,
   restoring the same optical gap the small nodes have. */
.bcareer .ln-node.win u{ left:calc(var(--cw)*.070) }



/* 10 · THE SCOPE LINE, AND THE SUB-LINE IN THE BACK'S REGISTER (IA review, 2026-08-16).
   The badge gains a third line naming the population the stat block counts. Without it the front
   reads "3 EVENTS · 245 POINTS" with no scope, and for a player whose play is home nights and
   casual tournaments that is "0 EVENTS · 0 POINTS" over a back listing home nights and a purse —
   the contradiction src/card.ts already records as fixed once, reappearing on a new front.

   And the sub-line drops to sentence case. The front is otherwise entirely condensed caps and the
   back entirely mixed case, which is a large register jump even in one typeface; the back's zone
   headings are already condensed caps, so this is the joint going the other way. */
.bcareer .ln-rail i{ display:block; font-style:normal; font-size:calc(var(--cw)*.0175);
  letter-spacing:.20em; text-transform:uppercase; color:var(--pld-faint); margin-top:calc(var(--cw)*.004) }

.bcareer .cl-sub span{ text-transform:none; letter-spacing:.10em; font-size:calc(var(--cw)*.0215) }


/* 11 · THE TIMELINE IS EXEMPT FROM THE NO-REPEAT RULE, AND THAT IS WORTH WRITING DOWN.
   This codebase enforces "a fact on the front must not repeat on the back" in machinery, not
   aspiration — PROMOTED/frontFactCount, and the `said` set that filters the career rows. But that
   rule governs the FACT POOL, where repetition is pure waste. The event list is a different class:
   the front is selective and pictorial (three gold nodes on a photograph — "he won the LA Open"),
   the back is complete and tabular (the ledger). Every sports card ever printed does both. So the
   overlap here is REPRISE, not redundancy, and it stays — but the front must never gain field size
   or points, because denominators are the back's job. */


/* 9 · A SEASON WITH NO PLAY IS SKIPPED, NOT DRAWN EMPTY (owner, decided). It was an open question
   whether a year a player sat out is still a band. It is not: the band count is the number of
   seasons they actually PLAYED, and a missing year simply is not there. What makes that legible
   rather than confusing is the year struck on every band — a card reading 2025 / 2027 / 2028 says
   plainly that 2026 was sat out, and needs no empty frame to say it. Ben is the case in the set:
   both his nights fall in 2025, so his card is one band, not two with one blank. */



/* the vertical name starts BELOW the badge, so the two never overlap */
.bcareer .ln-name{
  top:calc(var(--fb) + var(--cw)*.150);
  height:calc(var(--plh) - var(--cw)*.150);
}

/* ── THE ONE PIECE OF ARITHMETIC THAT HAD TO CHANGE, and it must come LAST ────────────────────
   In the study --fb is the card's border and the face is inset by it. Here `.bcareer` IS `.bface`,
   which production has already inset (.032cw, or .046cw for a founder), so positioning starts at
   0 — but --fw/--fh still have to be the face's true size or every band, node and seam is measured
   against the wrong box.

   AFTER the port, not before. Written first, the study's own `--fb: .046cw` simply overwrote it
   and every element that positions from --fb sat an inset too low: the stat block's top computed
   to 410.6px inside a 439.5px face, so its labels were clipped off the card. Same specificity,
   later wins — which is the whole reason the override block exists. */
.bcareer { --ins: calc(var(--cw) * .032); }
.fdr .bcareer { --ins: calc(var(--cw) * .046); }
.bcareer { --fb: 0px;
  --fw: calc(var(--cw) - 2 * var(--ins));
  --fh: calc(var(--cw) * 1.4 - 2 * var(--ins)); }

/* ── THE WINDOW TOKEN MEASURES THE NAME COLUMN (owner) ────────────────────────────────────────
   "ALL TIME" was set at .033cw and filled about 56% of the column, so the badge read as three
   items of three different widths stacked. The crest already measures the column; the token now
   does too, and the scope line under it is sized off the token rather than chosen. Tracking comes
   down as the size goes up — .135em at this size would push it past the column and wrap. */
.bcareer .ln-rail span { font-size: calc(var(--cw) * .0620); letter-spacing: .030em; }
.bcareer .ln-rail i { font-size: calc(var(--cw) * .0205); letter-spacing: .16em; }
