/* ══════════════════════════════════════════════════════════════
   FYF marketing surface
   Component classes for app/components/marketing/ and the marketing
   layout. Every value is a token from fyf_foundation.css — see
   docs/superpowers/specs/2026-08-18-fyf-design-system-integration-design.md.
   Loaded only by layouts/marketing.html.erb.
   ══════════════════════════════════════════════════════════════ */

/* ── Button ───────────────────────────────────────────────────
   Pill, keyline, hard offset shadow. Hover lifts toward the reader
   and the shadow grows; press seats it. That motion is the brand's
   signature — do not flatten it. */
.fyf-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: auto;
  font-family: var(--font-subhead);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  transition:
    transform var(--dur-fast) var(--ease-snap),
    box-shadow var(--dur-fast) var(--ease-snap),
    background var(--dur-fast) var(--ease-snap);
}

.fyf-btn--block { width: 100%; }

.fyf-btn--sm { padding: 8px 16px;  font-size: 12px; }
.fyf-btn--md { padding: 13px 26px; font-size: 15px; }
.fyf-btn--lg { padding: 18px 38px; font-size: 19px; }

.fyf-btn--primary   { box-shadow: var(--shadow-hard-sm); }
.fyf-btn--secondary { background: transparent; color: var(--fyf-charcoal); box-shadow: var(--shadow-hard-sm); }
.fyf-btn--ghost     { background: transparent; color: var(--fyf-charcoal); border-color: transparent; box-shadow: none; }

/* cream on pink measured 2.64:1 -- short of AA's 4.5:1 at 15px bold, and this
   is the primary header CTA, the most prominent button on every marketing
   page. Charcoal clears 4.44:1 and keeps the pink fill; no other call site
   uses tone: :pink for a primary button (the only other tone: :pink use is
   TapeStripComponent's tape colour, unrelated to button labels). */
.fyf-btn--primary.fyf-btn--pink   { background: var(--fyf-pink);     color: var(--fyf-charcoal); }
.fyf-btn--primary.fyf-btn--yellow { background: var(--fyf-yellow);   color: var(--fyf-charcoal); }
.fyf-btn--primary.fyf-btn--blue   { background: var(--fyf-blue);     color: var(--fyf-charcoal); }
.fyf-btn--primary.fyf-btn--green  { background: var(--fyf-green);    color: var(--fyf-cream); }
.fyf-btn--primary.fyf-btn--orange { background: var(--fyf-orange);   color: var(--fyf-charcoal); }
.fyf-btn--primary.fyf-btn--ink    { background: var(--fyf-charcoal); color: var(--fyf-cream); }
.fyf-btn--primary.fyf-btn--cream  { background: var(--fyf-cream);    color: var(--fyf-charcoal); }

.fyf-btn:hover:not(:disabled) {
  transform: translate(-2px, -2px);
  box-shadow: var(--shadow-hard);
}
.fyf-btn--secondary:hover:not(:disabled) { background: var(--fyf-cream-deep); }
.fyf-btn--ghost:hover:not(:disabled) {
  background: var(--fyf-charcoal-12);
  box-shadow: none;
  transform: translate(-2px, -2px);
}

.fyf-btn:active:not(:disabled) {
  transform: translate(2px, 2px);
  box-shadow: var(--shadow-press);
}
.fyf-btn--ghost:active:not(:disabled) { box-shadow: none; }

/* Links styled as buttons must not inherit the document link styles.
   fyf_foundation.css sets `.fyf a { color: var(--text-link); text-decoration:
   underline }` and `.fyf a:hover { color: var(--text-link-hover) }`; as
   selectors those outrank the tone rules above on an <a>, so an ink/green
   primary turned charcoal-on-charcoal on hover (text vanished) and a
   secondary/ghost sat in the red link colour with an underline. Restate the
   foreground per variant+tone at higher specificity, hover and focus
   included. */
.fyf a.fyf-btn,
.fyf a.fyf-btn:hover,
.fyf a.fyf-btn:focus-visible { text-decoration: none; }

.fyf a.fyf-btn--secondary,
.fyf a.fyf-btn--secondary:hover,
.fyf a.fyf-btn--secondary:focus-visible,
.fyf a.fyf-btn--ghost,
.fyf a.fyf-btn--ghost:hover,
.fyf a.fyf-btn--ghost:focus-visible { color: var(--fyf-charcoal); }

.fyf a.fyf-btn--primary.fyf-btn--pink,   .fyf a.fyf-btn--primary.fyf-btn--pink:hover,   .fyf a.fyf-btn--primary.fyf-btn--pink:focus-visible,
.fyf a.fyf-btn--primary.fyf-btn--yellow, .fyf a.fyf-btn--primary.fyf-btn--yellow:hover, .fyf a.fyf-btn--primary.fyf-btn--yellow:focus-visible,
.fyf a.fyf-btn--primary.fyf-btn--blue,   .fyf a.fyf-btn--primary.fyf-btn--blue:hover,   .fyf a.fyf-btn--primary.fyf-btn--blue:focus-visible,
.fyf a.fyf-btn--primary.fyf-btn--orange, .fyf a.fyf-btn--primary.fyf-btn--orange:hover, .fyf a.fyf-btn--primary.fyf-btn--orange:focus-visible,
.fyf a.fyf-btn--primary.fyf-btn--cream,  .fyf a.fyf-btn--primary.fyf-btn--cream:hover,  .fyf a.fyf-btn--primary.fyf-btn--cream:focus-visible { color: var(--fyf-charcoal); }

.fyf a.fyf-btn--primary.fyf-btn--green, .fyf a.fyf-btn--primary.fyf-btn--green:hover, .fyf a.fyf-btn--primary.fyf-btn--green:focus-visible,
.fyf a.fyf-btn--primary.fyf-btn--ink,   .fyf a.fyf-btn--primary.fyf-btn--ink:hover,   .fyf a.fyf-btn--primary.fyf-btn--ink:focus-visible { color: var(--fyf-cream); }

.fyf-btn:disabled {
  opacity: 0.4;
  box-shadow: none;
  transform: none;
  cursor: not-allowed;
}

/* Every .fyf-btn -- an <a>, a real <button>, or one wrapped in a button_to
   <form> -- gets an explicit focus-visible ring. Nothing else on the
   marketing surface supplies one, so without this the only affordance was
   whatever the UA default happened to be (invisible on some fills, absent
   entirely anywhere author CSS suppressed it). A two-tone ring rather than
   one colour: this component fills in seven different tones (pink, yellow,
   blue, green, orange, ink, cream) sitting on any of the page's own field
   colours behind it, and no single ring colour clears contrast against all
   of them. Charcoal sits tight against the button edge -- it reads against
   every light fill (pink, yellow, blue, green, orange, cream) and against
   light page backgrounds; cream sits just outside that -- it reads against
   the one dark fill (ink) and against the charcoal inverse section. Between
   the two, at least one ring is always visible regardless of fill or field. */
/* :not(:disabled), not a bare :focus-visible: .fyf-btn:hover:not(:disabled)
   above is (0,3,0); a bare :focus-visible here is only (0,2,0), so hover's
   box-shadow silently outranked and wiped the charcoal half of this ring on
   any focused-and-hovered button, leaving only the cream outline -- 2.40:1 on
   the hero's blue field and 1.19:1 on the yellow CTA band, both well under
   AA's 3:1 non-text floor. Matching :not(:disabled) ties the specificity at
   (0,3,0) and this rule wins outright on source order (it sits after hover
   below). */
.fyf-btn:focus-visible:not(:disabled) {
  outline: 3px solid var(--fyf-cream);
  outline-offset: 3px;
  box-shadow: 0 0 0 3px var(--fyf-charcoal);
}

/* The reduced-motion rule in application.tailwind.css zeroes
   transition-duration, not transform -- the hover lift still moves the
   button 2px, just instantly instead of eased. The box-shadow/colour changes
   still transition-less either way, which is the accessible half of the
   affordance. */

/* ── Eyebrow ─────────────────────────────────────────────────── */
/* .fyf p{margin:0 0 1em} in fyf_foundation.css is a class+element selector
   (0,1,1); a bare .fyf-eyebrow (0,1,0) loses the tie-break on specificity
   regardless of marketing.css loading last, so the bottom margin the
   foundation adds survives here uncancelled. Same trap as the five .fyf-hero
   classes; same fix -- an ancestor class makes it two classes (0,2,0). */
.fyf .fyf-eyebrow {
  margin: 0;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
}
.fyf-eyebrow--light   { color: var(--fyf-pink-deep); }
.fyf-eyebrow--inverse { color: var(--fyf-yellow); }
/* pink-deep on the hero's blue field is 1.67:1 -- fails AA's 4.5:1 outright.
   The handoff's own alpha (rgba(48,32,45,.72)) only reaches 3.21:1 on blue,
   still short. Full-opacity charcoal measures ~4.90:1 on --fyf-blue and is
   the reading that actually matches the handoff's accessibility section
   ("charcoal on cream/yellow/blue/pink is all AA+") -- that claim is only
   true at full opacity. */
.fyf-eyebrow--accent  { color: var(--fyf-charcoal); }

/* ── Spice word ───────────────────────────────────────────────
   The stroke is em-based so it tracks the type size, and the shadow is the
   text-shadow token for the same reason. Never blurred. */
.fyf-spice {
  display: block;
  font-family: var(--font-spice);
  font-weight: 400;
  text-transform: uppercase;
  line-height: var(--leading-spice);
  letter-spacing: var(--tracking-spice);
  -webkit-text-stroke: 0.012em currentColor;
  text-shadow: var(--text-shadow-hard);
}
.fyf-spice--hero { font-size: clamp(60px, 7.4vw, 96px); }
.fyf-spice--lg   { font-size: clamp(64px, 8vw, 104px); }

.fyf-spice--cream    { color: var(--fyf-cream); }
.fyf-spice--yellow   { color: var(--fyf-yellow); }
.fyf-spice--pink     { color: var(--fyf-pink); }
.fyf-spice--charcoal { color: var(--fyf-charcoal); }

/* ── Oval device ─────────────────────────────────────────────── */
.fyf-oval {
  display: inline-block;
  border: var(--stroke-chip) solid currentColor;
  border-radius: 50%;
  padding: 0.35em 1.1em;
  line-height: 1.15;
}

/* ── Paper card ───────────────────────────────────────────────
   position: relative so tape strips can hang off the top edge. */
.fyf-card {
  position: relative;
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-hard);
}
.fyf-card--paper {
  background-color: var(--fyf-white);
  background-image: var(--texture-paper);
}
.fyf-card--cream  { background: var(--fyf-cream);  box-shadow: var(--shadow-hard-cream); }
.fyf-card--green  { background: var(--fyf-green);  color: var(--fyf-cream); box-shadow: var(--shadow-hard-cream); }
.fyf-card--yellow { background: var(--fyf-yellow); }
.fyf-card--outline-cream {
  background: transparent;
  border-color: var(--fyf-cream);
  color: var(--fyf-cream);
  box-shadow: none;
}

.fyf-card--tilt-sm { transform: rotate(var(--tilt-sm)); }
.fyf-card--tilt-md { transform: rotate(var(--tilt-md)); }
.fyf-card--tilt-lg { transform: rotate(var(--tilt-lg)); }

/* ── Tape strip ───────────────────────────────────────────────
   The only blurred shadow on the marketing surface, and it is a real one:
   tape casts a soft shadow because it sits above the paper. */
.fyf-tape {
  position: absolute;
  z-index: 1;
  width: var(--tape-w);
  height: var(--tape-h);
  top: var(--tape-top);
  left: var(--tape-left, auto);
  right: var(--tape-right, auto);
  transform: rotate(var(--tape-rotate));
  box-shadow: 0 2px 6px rgba(48, 32, 45, 0.25);
}
.fyf-tape--pink   { background: var(--tape-pink); }
.fyf-tape--orange { background: var(--tape-orange); }
.fyf-tape--cream  { background: var(--tape-cream); }

/* ── Position pill ───────────────────────────────────────────── */
.fyf-pill {
  display: inline-block;
  text-align: center;
  font-family: var(--font-subhead);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
}
/* min-width, not width: the label may be "WR2" rather than "WR", and a fixed
   width would clip the rank that makes the row readable. Base size matches
   the #play card 01 mini-board pill; the hero board's own pill reads larger
   (context override below), so the two are not actually the same size
   despite sharing this modifier. */
.fyf-pill--sm { min-width: 32px; font-size: 9px;  padding: 2px 0; }
.fyf-pill--md { min-width: 44px; font-size: 11px; padding: 4px 8px; }
/* The hero board's in-card pill reads larger than the mini-board pill in
   #play card 01, even though both render through the same :sm size --
   they are genuinely different in the mockup. min-width stays 32px so
   two-and-three-character labels like "WR2" are never clipped. */
.fyf-boards .fyf-pill--sm { font-size: 10px; letter-spacing: .06em; padding: 3px 0; }

.fyf-pill--qb { background: var(--fyf-yellow); color: var(--fyf-charcoal); }
.fyf-pill--rb { background: var(--fyf-green);  color: var(--fyf-cream); }
.fyf-pill--wr { background: var(--fyf-blue);   color: var(--fyf-charcoal); }
.fyf-pill--te { background: var(--fyf-orange); color: var(--fyf-charcoal); }
/* Fix round 1, I2: the fallback fill for a position outside the contest's
   scored pool (K/DEF) -- position_pill_or_fallback (application_helper.rb)
   renders this instead of Marketing::PositionPillComponent, which raises on
   anything but QB/RB/WR/TE. Neutral charcoal-on-tint rather than any of the
   four scored fills, so an unscored position reads as "not part of this",
   not as a fifth position colour. */
.fyf-pill--unscored { background: var(--fyf-charcoal-12); color: var(--fyf-charcoal); }

/* ── Site header ──────────────────────────────────────────────
   Nav wraps to a second row on narrow widths via flex-wrap; below 640px it
   hides entirely and the logo, wordmark, and primary button carry on. */
.fyf-header {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 20px;
  row-gap: 10px;
  padding: 14px clamp(20px, 4vw, 56px);
  background: var(--fyf-cream);
  border-bottom: var(--stroke-sticker) solid var(--fyf-charcoal);
}
.fyf-header__brand { display: inline-flex; text-decoration: none; }
.fyf-header__logo { height: 38px; width: auto; display: block; }
.fyf-header__divider {
  width: 2px;
  height: 26px;
  background: var(--fyf-charcoal-30);
}
.fyf-header__wordmark {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 19px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fyf-charcoal);
}
.fyf-header__pill {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  background: var(--fyf-yellow);
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  padding: 5px 12px;
}
.fyf-header__nav {
  display: flex;
  gap: 24px;
  margin-left: 16px;
  white-space: nowrap;
}
.fyf-header__nav a,
.fyf-header__signin {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--fyf-charcoal);
  transition: color var(--dur-fast) var(--ease-snap);
  /* 44px minimum tap target without moving the visual baseline. */
  padding: 14px 0;
}
.fyf-header__nav a:hover { color: var(--fyf-pink-deep); }
/* charcoal-60 on cream measured 3.39:1 -- short of AA's 4.5:1. charcoal-80
   clears 6.45:1. */
.fyf-header__signin { color: var(--fyf-charcoal-80); }
.fyf-header__actions {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-left: auto;
}

@media (max-width: 640px) {
  .fyf-header__nav { display: none; }
}

/* ── Site footer ─────────────────────────────────────────────── */
.fyf-footer {
  background: var(--fyf-charcoal);
  color: var(--fyf-cream);
  padding: 52px clamp(24px, 4.4vw, 56px) 42px;
}
.fyf-footer__inner { max-width: var(--container-max); margin: 0 auto; }
.fyf-footer__top {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 32px;
  flex-wrap: wrap;
}
.fyf-footer__logo { height: 72px; width: auto; display: block; }
.fyf-footer__links { display: flex; gap: 26px; flex-wrap: wrap; }
.fyf-footer__links a {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--fyf-cream);
  transition: color var(--dur-fast) var(--ease-snap);
  /* 44px minimum tap target without moving the visual baseline. */
  padding: 14px 0;
}
.fyf-footer__links a:hover { color: var(--fyf-yellow); }
.fyf-footer__rule {
  height: 2px;
  border: 0;
  background: rgba(250, 220, 194, 0.35);
  margin: 26px 0 18px;
}
.fyf-footer__legal {
  margin: 0;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: rgba(250, 220, 194, 0.7);
}

/* ── Layout shell ─────────────────────────────────────────────── */
.fyf-body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
.fyf-main { flex: 1; }

/* ── Flash ────────────────────────────────────────────────────
   A taped notice rather than a tinted bar. Enrollment refusals land here and
   the old public layout dropped them entirely once, which made a refused
   enrollment look like a dead button. */
.fyf-flash {
  max-width: var(--container-narrow);
  margin: 18px auto 0;
  padding: 14px 22px;
  font-size: var(--text-sm);
  background-color: var(--fyf-white);
  background-image: var(--texture-grid-paper);
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-md);
}
.fyf-flash--notice { border-left: var(--stroke-heavy) solid var(--fyf-green); }
.fyf-flash--alert  { border-left: var(--stroke-heavy) solid var(--fyf-pink-deep); }

/* ── Section shell ───────────────────────────────────────────── */
.fyf-section {
  padding: var(--section-y) clamp(24px, 4.4vw, 56px);
  border-bottom: var(--stroke-sticker) solid var(--fyf-charcoal);
}
.fyf-section--inverse {
  background: var(--fyf-charcoal);
  color: var(--fyf-cream);
  overflow: hidden;
}
/* #play and #rules each carry one decorative sticker (a pennant, a ball
   badge -- see the fyf_pennant/fyf_ball_badge calls in _steps.html.erb and
   _guarantees.html.erb), positioned absolute with a negative offset so it
   hangs off the section's own edge. position: relative anchors that to the
   section rather than the page; overflow: hidden (already true of
   .fyf-section--inverse above, added here for the base .fyf-section) keeps
   the negative offset from ever widening the page past 100vw. */
.fyf-section#play {
  position: relative;
  overflow: hidden;
}
.fyf-section--inverse#rules { position: relative; }
.fyf-section__inner { max-width: var(--container-max); margin: 0 auto; }
.fyf-section__inner--narrow { max-width: var(--container-narrow); }

/* Anchor jumps must land below the sticky header, not behind it. Scoped to
   .fyf -- unscoped, this leaked off the marketing surface: marketing.css
   loads via stylesheet_link_tag :app's expansion on every layout (editor,
   snapshot pages, home), so a bare [id] selector was setting
   scroll-margin-top on every element with an id app-wide, including pages
   with no sticky header to clear -- e.g. RankingSetsController's post-create
   redirect to ranking_set_path(anchor: dom_id(snapshot)) landed 96px low. */
.fyf [id] { scroll-margin-top: 96px; }

/* ── Phase strip ─────────────────────────────────────────────── */
.fyf-phase-strip {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
  padding: 13px clamp(20px, 4vw, 56px);
  background: var(--fyf-charcoal);
  /* Invisible against the charcoal fill, but every section on the page
     carries this same keyline -- kept for consistency, not visibility. */
  border-bottom: var(--stroke-sticker) solid var(--fyf-charcoal);
  color: var(--fyf-cream);
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
.fyf-phase-strip__lead { color: var(--fyf-yellow); }
.fyf-phase-strip__lead--sealed { color: var(--fyf-yellow); }
.fyf-phase-strip__sep { color: rgba(250, 220, 194, 0.5); }

/* ── Hero ─────────────────────────────────────────────────────
   auto-fit with a 500px floor: two panels side by side above ~1080px, stacked
   below, with no media query doing the deciding. The floor is wrapped in
   min(500px, 100%) rather than a bare 500px -- a bare fixed length is a
   hard lower bound that CSS Grid honors even when the container is
   narrower, so on any viewport under 500px (390px UAT target, and most
   phones) the track stayed 500px wide and .fyf-hero's overflow: hidden
   silently clipped the right ~110px of headline/body/meta text instead of
   scrolling. Found by measuring real element bounding rects at an exact
   390px CDP-emulated viewport, not by the page-level scrollWidth check
   alone, which this bug passes vacuously (the overflow is clipped, not
   scrollable). min(500px, 100%) keeps every wider-viewport behavior
   (2-up above ~1080px, single 500-768px column) identical and only
   changes the floor once the container itself drops below 500px. */
.fyf-hero {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(500px, 100%), 1fr));
  border-bottom: var(--stroke-sticker) solid var(--fyf-charcoal);
  overflow: hidden;
}
.fyf-hero__panel--field {
  background: var(--fyf-blue);
  padding: clamp(44px, 5vw, 72px) clamp(24px, 4.4vw, 60px);
  display: grid;
  align-content: center;
}
.fyf-hero__panel--artifact {
  background: var(--fyf-cream-tint);
  border-left: var(--stroke-sticker) solid var(--fyf-charcoal);
  padding: 52px clamp(20px, 2.8vw, 40px) 46px;
  display: grid;
  align-content: center;
  justify-items: center;
}
/* Stacked, the left border would draw across the top of the page. */
@media (max-width: 1080px) {
  .fyf-hero__panel--artifact {
    border-left: 0;
    border-top: var(--stroke-sticker) solid var(--fyf-charcoal);
  }
}

/* Every margin below is qualified with the .fyf-hero ancestor rather than
   left as a bare class. fyf_foundation.css resets vertical rhythm with
   `.fyf p{margin:0 0 1em}` and `.fyf h1,h2,h3,h4{margin:0}` -- both carry an
   element selector, so at equal class-count they OUTRANK a bare
   `.fyf-hero__caption`-style class selector on specificity regardless of
   marketing.css loading last, and the margin-top values below silently
   computed to 0 (caught by rendering the hero and seeing the alpha chip
   overlap the caption text it was supposed to clear). Two classes beats one
   class-plus-element, so this wins without hardcoding a tag name. */
.fyf-hero .fyf-hero__headline {
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: clamp(38px, 4.2vw, 58px);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  margin: 16px 0 0;
}
/* Handoff calls for 22px above the spice word specifically in this
   placement -- scoped to the hero rather than added to the shared .fyf-spice
   rule, which other callers of SpiceWordComponent should not inherit. */
.fyf-hero .fyf-spice--hero {
  margin-top: 22px;
}
.fyf-hero .fyf-hero__body {
  margin: 22px 0 0;
  max-width: 44ch;
  font-size: 17px;
  line-height: 1.55;
  /* rgba(48,32,45,.82) measured 3.77:1 on --fyf-blue -- short of AA's 4.5:1
     at 17px. Full-opacity charcoal (~4.90:1) clears it. */
  color: var(--fyf-charcoal);
}
.fyf-hero .fyf-hero__subhead {
  margin: 24px 0 0;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
.fyf-hero__actions {
  display: flex;
  flex-wrap: wrap;
  /* Without this, flex's default stretch cross-axis alignment pulls the
     bare "How it works" button up to match the sign-in/enroll CTA's height
     -- that CTA is a button plus a caption paragraph stacked in one flex
     item, so the lone button next to it inflated into an oversized pill. */
  align-items: flex-start;
  gap: 14px;
  margin-top: 30px;
}
.fyf-hero .fyf-hero__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 9px;
  margin: 18px 0 0;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  /* rgba(48,32,45,.88) measured 4.13:1 on --fyf-blue -- short of AA's 4.5:1
     at 12px, even bold (well under the 18.66px-bold large-text threshold).
     Full-opacity charcoal (~4.90:1) clears it. */
  color: var(--fyf-charcoal);
}
/* .fyf-hero__meta-sep had no rule at all -- the middot separators between the
   meta line's three items rendered at full charcoal, same weight as the text
   either side, with nothing to distinguish them as dividers rather than
   content. Dimmed relative to .fyf-hero__meta's own charcoal (not the cream
   .fyf-phase-strip__sep uses -- that's for cream-on-charcoal fields, this one
   is charcoal-on-blue), matching the same "dim the separators" treatment.
   Decorative (aria-hidden) either way, so no AA floor applies. */
.fyf-hero__meta-sep { color: var(--fyf-charcoal-60); }
.fyf-hero .fyf-hero__caption {
  margin: 44px auto 0;
  max-width: 52ch;
  text-align: center;
  font-size: 14px;
  line-height: 1.5;
  color: var(--fyf-charcoal-80);
}

/* ── Hero boards ─────────────────────────────────────────────── */
.fyf-boards {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(14px, 1.6vw, 24px);
  max-width: 640px;
}
@media (max-width: 640px) {
  .fyf-boards { grid-template-columns: 1fr; }
}
/* One-off angles, not the shared tilt tokens -- the market card's -1.4deg
   is close to but not equal to --tilt-sm (-1.2deg), and "Your board"'s
   1.1deg is well under --tilt-md (1.8deg, 64% more than the mockup uses).
   Both stay within the brand's 3deg ceiling; scoped to .fyf-board so no
   other card that happens to use the same tilt class is affected. */
.fyf-board.fyf-card--tilt-sm { transform: rotate(-1.4deg); }
.fyf-board.fyf-card--tilt-md { transform: rotate(1.1deg); }
/* No overflow: hidden here -- .fyf-card is position: relative precisely so
   its tape strips can hang off the top edge (top: -11px in the two boards'
   with_tape calls). Clipping at the card's own box would flatten both
   strips into flush bars. The handoff puts overflow: hidden on the hero
   instead and rounds the head bar's own top corners to match the card. */
.fyf-board__head {
  background: var(--fyf-charcoal);
  color: var(--fyf-cream);
  padding: 10px 16px;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  /* -4px so the bar's corner nests just inside the 3px charcoal card
     border rather than fighting it for the same radius. */
  border-radius: calc(var(--radius-lg) - 4px) calc(var(--radius-lg) - 4px) 0 0;
}
.fyf-board__head--yours {
  background: var(--fyf-pink);
  color: var(--fyf-charcoal);
  border-bottom: var(--stroke-sticker) solid var(--fyf-charcoal);
}
/* Three tracks on BOTH cards -- rank, pill, name -- not the mock's fourth
   `auto` track for "Your board"'s delta chip. At real viewports the cards
   render ~260px (the mock drew them at 308px), so a reserved chip track
   squeezed the two animated rows' name column to ~100px: "Jaxon
   Smith-Njigba" wrapped to three lines, those rows grew, and because
   swap_demo_controller translates the rows by their rest-state offsetTop
   gap, the swap left a hole under rank 2 and pushed rank 5 out of the
   card's bottom. The chip now hangs off the row's edge instead (see
   .fyf-board__row > .fyf-chip below), so every row's name column is the
   same width as its market twin and wraps identically. position: relative
   anchors that chip; the row's own travel is a transform, which carries
   the chip along. */
.fyf-board__row {
  position: relative;
  display: grid;
  grid-template-columns: 22px 40px 1fr;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
}
/* No background here anymore -- an opaque --fyf-cream-tint fill on every
   even row painted flat colour directly over the card's own
   background-image: var(--texture-paper) (set by PaperCardComponent),
   so alternating rows lost the grid-paper texture the odd rows kept --
   half the card looked textured, half looked like a flat cream block.
   Every non-highlighted row now shows the card's own texture uniformly;
   only the hairline survives from this rule. */
.fyf-board__row:nth-child(even) {
  border-top: var(--stroke-chip) solid var(--fyf-charcoal-12);
}
/* The market card's last row otherwise carries no border at all -- the
   mockup gives it the same hairline the interior rows use. */
.fyf-board--market .fyf-board__row:last-child {
  border-top: var(--stroke-chip) solid var(--fyf-charcoal-12);
}
.fyf-board__rank {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 15px;
}
/* No white-space: nowrap -- this project never truncates a player name;
   long names wrap to a second line instead of clipping. */
.fyf-board__name {
  font-family: var(--font-header);
  font-size: 18px;
  line-height: 1.1;
}
/* Compounded with the base class, not left bare: :nth-child(even) above is
   two selector components (class + pseudo-class), which outranks a single
   `--lifted`/`--dropped` class outright on the one property they both still
   set -- border-top. nth-child(even) no longer declares a background (see
   above), so a bare highlight class would keep its own green/red tint; what
   it would lose on every even DOM position is the border -- the row would
   show the plain charcoal-12 hairline instead of the highlight's solid
   charcoal border-top (caught by rendering the hero -- the promoted row's
   top edge was the thin grey hairline, not the bold highlight border).
   Compounding to two classes ties nth-child(even)'s specificity, and later
   source order wins the tie. */
.fyf-board__row.fyf-board__row--lifted {
  background: rgba(31, 112, 49, 0.1);
  border-top: var(--stroke-chip) solid var(--fyf-charcoal);
  border-bottom: var(--stroke-chip) solid var(--fyf-charcoal);
}
.fyf-board__row.fyf-board__row--dropped {
  background: rgba(201, 52, 43, 0.12);
  border-top: var(--stroke-chip) solid var(--fyf-charcoal);
  /* -4px so the row's red fill follows the card's own bottom corners
     instead of squaring off inside them -- without this the fill pokes a
     square notch through the card's rounded corner. */
  border-radius: 0 0 calc(var(--radius-lg) - 4px) calc(var(--radius-lg) - 4px);
}
/* _board_gap's elided-middle placeholder -- the brief names this class
   without giving it a rule, so a swap wide enough to elide (past
   HERO_MAX_ELIDED_ROWS) would otherwise print a raw, unstyled "· · ·". */
/* Also charcoal-60 at 4.09:1 on the paper card behind it. Left alone (a11y
   sweep, phase 1a fix wave Fix 4b): the content is "· · ·", a punctuation
   elision marker with no words to read, not prose -- the same category as
   the aria-hidden .fyf-phase-strip__sep dividers, which stay dim on purpose. */
.fyf-board__gap {
  padding: 4px 14px;
  text-align: center;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  color: var(--fyf-charcoal-60);
}

/* ── Delta chips ─────────────────────────────────────────────── */
.fyf-chip {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  padding: 2px 9px;
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
}
.fyf-chip--gain { background: var(--fyf-green); color: var(--fyf-cream); }
.fyf-chip--loss { background: var(--fyf-red);   color: var(--fyf-cream); }
/* On a board row the delta chip is a sticker hung off the row's right
   edge, not a grid cell: it costs the name column nothing (see the
   .fyf-board__row comment for what reserving a track did). 14px past the
   padding edge puts it astride the card's 3px border like the tape strips
   do. Vertical centring uses `translate`, NOT transform -- the pop-in
   keyframes (fyf-swap-*-chip-scale) own transform, and a centring
   transform here would be overwritten the instant they start. The row's
   own translateY travel carries the chip with it for free. */
.fyf-board__row > .fyf-chip {
  position: absolute;
  right: -14px;
  top: 50%;
  translate: 0 -50%;
  z-index: 1;
}
/* A chip of "+3"/"–3" width reaches at most ~10px into the row's content
   box; "+10" a little more. This padding keeps a long one-line name from
   running under it -- a name wider than the remaining width wraps (never
   clips), and the controller equalises the two animated rows' heights
   before measuring their travel, so a wrapped name can't desync the swap. */
.fyf-board__row--lifted .fyf-board__name,
.fyf-board__row--dropped .fyf-board__name { padding-right: 20px; }

.fyf-alpha-chip {
  position: absolute;
  left: 50%;
  bottom: -26px;
  transform: translateX(-50%) rotate(-3deg);
  background: var(--fyf-yellow);
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-hard-sm);
  padding: 8px 18px;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  /* Two full player names, not the mock's short "JSN"/"Bijan" -- names are
     never truncated (project rule), so the chip has to wrap instead of
     clipping. max-width caps the line length before it wraps; text-align
     centers the second line under the first instead of ragging left. */
  max-width: min(92%, 34ch);
  text-align: center;
}

/* ── Swap demo animation ─────────────────────────────────────── */
/* swap_demo_controller measures the rest-state gap between the promoted and
   demoted rows and writes it as --swap-dy; every other visual step is pure
   CSS from here down. Percentages below are ms/6300 from the handoff's
   setTimeout script (design-reference/FrankOpenLanding.template.html) --
   e.g. its 1300ms beat becomes 20.63% -- so the numbers are derived, not
   arbitrary, and shouldn't be hand-tweaked without redoing that division. */
.fyf-swap-running [data-swap-demo-target="promotedRow"] {
  animation: fyf-swap-promoted-row 6.3s linear infinite;
}
.fyf-swap-running [data-swap-demo-target="demotedRow"] {
  animation: fyf-swap-demoted-row 6.3s linear infinite;
}
.fyf-swap-running [data-swap-demo-target="promotedRank"] {
  animation: fyf-swap-promoted-rank 6.3s linear infinite;
}
.fyf-swap-running [data-swap-demo-target="demotedRank"] {
  animation: fyf-swap-demoted-rank 6.3s linear infinite;
}
/* Two animations per chip, not one -- the pop-in's scale and opacity (and
   the scale-out's) start at the exact same instant but finish at different
   moments (the handoff's own script sets them as two separate transitions,
   e.g. "transform .16s ease-pop, opacity .1s linear"). A single @keyframes
   rule can only carry one animation-timing-function per offset, so a
   shared start with two different eased curves needs two animations
   layered through the shorthand's comma list -- one purely for scale
   (ease-pop in, ease-snap out), one purely for opacity (linear both ways,
   which is also why its keyframes never set animation-timing-function of
   their own: the shorthand's own "linear" already covers it). */
.fyf-swap-running [data-swap-demo-target="promotedChip"] {
  animation:
    fyf-swap-promoted-chip-scale 6.3s linear infinite,
    fyf-swap-promoted-chip-opacity 6.3s linear infinite;
}
.fyf-swap-running [data-swap-demo-target="demotedChip"] {
  animation:
    fyf-swap-demoted-chip-scale 6.3s linear infinite,
    fyf-swap-demoted-chip-opacity 6.3s linear infinite;
}
.fyf-swap-running [data-swap-demo-target="alphaChip"] {
  animation:
    fyf-swap-alpha-chip-scale 6.3s linear infinite,
    fyf-swap-alpha-chip-opacity 6.3s linear infinite;
}

/* Must come AFTER every .fyf-swap-running rule above, not before -- the
   `animation` shorthand resets animation-play-state to its initial value
   (running) on every property it touches, and this selector ties with
   each running rule at (0,2,0) specificity, so whichever rule is LATER in
   source order wins the tie. Declared first (as it originally was), the
   running rules always won and .fyf-swap-paused was permanently inert. */
.fyf-swap-paused [data-swap-demo-target] { animation-play-state: paused; }

@keyframes fyf-swap-promoted-row {
  0% {
    transform: translateY(var(--swap-dy));
    background-color: transparent;
    border-top-color: var(--fyf-charcoal-12);
    border-bottom-color: var(--fyf-charcoal-12);
  }
  20.63% {
    transform: translateY(var(--swap-dy));
    animation-timing-function: var(--ease-snap);
  }
  27.30% { transform: translateY(0); }
  27.62% {
    background-color: transparent;
    border-top-color: var(--fyf-charcoal-12);
    border-bottom-color: var(--fyf-charcoal-12);
    animation-timing-function: var(--ease-snap);
  }
  31.59% {
    background-color: rgba(31, 112, 49, 0.1);
    border-top-color: var(--fyf-charcoal);
    border-bottom-color: var(--fyf-charcoal);
  }
  /* transform and the fills/borders both leave this offset on --ease-snap
     (unlike the chips below, which need two animations because their
     scale and opacity leave a shared offset on DIFFERENT curves), so one
     animation-timing-function here correctly governs all four properties. */
  89.21% {
    transform: translateY(0);
    background-color: rgba(31, 112, 49, 0.1);
    border-top-color: var(--fyf-charcoal);
    border-bottom-color: var(--fyf-charcoal);
    animation-timing-function: var(--ease-snap);
  }
  93.18% {
    background-color: transparent;
    border-top-color: var(--fyf-charcoal-12);
    border-bottom-color: var(--fyf-charcoal-12);
  }
  95.87% { transform: translateY(var(--swap-dy)); }
  100% {
    transform: translateY(var(--swap-dy));
    background-color: transparent;
    border-top-color: var(--fyf-charcoal-12);
    border-bottom-color: var(--fyf-charcoal-12);
  }
}

/* Same shape as promoted-row, mirrored dy sign, red fill instead of green
   -- and no border-bottom, because .fyf-board__row--dropped never had one
   (its red fill runs to the card's own rounded bottom corners instead). */
@keyframes fyf-swap-demoted-row {
  0% {
    transform: translateY(calc(var(--swap-dy) * -1));
    background-color: transparent;
    border-top-color: var(--fyf-charcoal-12);
  }
  20.63% {
    transform: translateY(calc(var(--swap-dy) * -1));
    animation-timing-function: var(--ease-snap);
  }
  27.30% { transform: translateY(0); }
  27.62% {
    background-color: transparent;
    border-top-color: var(--fyf-charcoal-12);
    animation-timing-function: var(--ease-snap);
  }
  31.59% {
    background-color: rgba(201, 52, 43, 0.12);
    border-top-color: var(--fyf-charcoal);
  }
  89.21% {
    transform: translateY(0);
    background-color: rgba(201, 52, 43, 0.12);
    border-top-color: var(--fyf-charcoal);
    animation-timing-function: var(--ease-snap);
  }
  93.18% {
    background-color: transparent;
    border-top-color: var(--fyf-charcoal-12);
  }
  95.87% { transform: translateY(calc(var(--swap-dy) * -1)); }
  100% {
    transform: translateY(calc(var(--swap-dy) * -1));
    background-color: transparent;
    border-top-color: var(--fyf-charcoal-12);
  }
}

/* Counter-travel: the numerals hold their slots while the row CONTENT moves
   past them, so each numeral's transform is the row transform's mirror,
   timed to the row's own travel segments (20.63%/27.30% out, 89.21%/95.87%
   back) -- not the fill/border segments, which the numerals don't have. */
@keyframes fyf-swap-promoted-rank {
  0%, 20.63% {
    transform: translateY(calc(var(--swap-dy) * -1));
    animation-timing-function: var(--ease-snap);
  }
  27.30%, 89.21% {
    transform: translateY(0);
    animation-timing-function: var(--ease-snap);
  }
  95.87%, 100% {
    transform: translateY(calc(var(--swap-dy) * -1));
  }
}
@keyframes fyf-swap-demoted-rank {
  0%, 20.63% {
    transform: translateY(var(--swap-dy));
    animation-timing-function: var(--ease-snap);
  }
  27.30%, 89.21% {
    transform: translateY(0);
    animation-timing-function: var(--ease-snap);
  }
  95.87%, 100% {
    transform: translateY(var(--swap-dy));
  }
}

@keyframes fyf-swap-promoted-chip-scale {
  0%, 29.05% {
    transform: scale(0);
    animation-timing-function: var(--ease-pop);
  }
  31.59%, 85.71% {
    transform: scale(1);
    animation-timing-function: var(--ease-snap);
  }
  87.94%, 100% { transform: scale(0); }
}
@keyframes fyf-swap-promoted-chip-opacity {
  0%, 29.05% { opacity: 0; }
  30.63%, 85.71% { opacity: 1; }
  87.62%, 100% { opacity: 0; }
}

@keyframes fyf-swap-demoted-chip-scale {
  0%, 30.95% {
    transform: scale(0);
    animation-timing-function: var(--ease-pop);
  }
  33.49%, 85.71% {
    transform: scale(1);
    animation-timing-function: var(--ease-snap);
  }
  87.94%, 100% { transform: scale(0); }
}
@keyframes fyf-swap-demoted-chip-opacity {
  0%, 30.95% { opacity: 0; }
  32.54%, 85.71% { opacity: 1; }
  87.62%, 100% { opacity: 0; }
}

/* Base composition preserved at every keyframe -- .fyf-alpha-chip's own
   translateX(-50%) rotate(-3deg) centers and tilts the pill, and an
   animated transform replaces the static one outright for as long as the
   animation runs, so the pop has to keep re-stating both alongside its
   own scale instead of layering on top of them. */
@keyframes fyf-swap-alpha-chip-scale {
  0%, 33.65% {
    transform: translateX(-50%) rotate(-3deg) scale(0);
    animation-timing-function: var(--ease-pop);
  }
  36.51%, 85.71% {
    transform: translateX(-50%) rotate(-3deg) scale(1);
    animation-timing-function: var(--ease-snap);
  }
  87.94%, 100% { transform: translateX(-50%) rotate(-3deg) scale(0); }
}
@keyframes fyf-swap-alpha-chip-opacity {
  0%, 33.65% { opacity: 0; }
  35.24%, 85.71% { opacity: 1; }
  87.62%, 100% { opacity: 0; }
}

/* ── Admin notice ────────────────────────────────────────────── */
/* display: flex; justify-content: center makes the card a non-growing flex
   item that hugs its own text under the 820px cap, rather than a block div
   that fills the cap and centres the copy inside it -- text-align: center
   on the outer wrapper does not do this; it only centres inline content
   within a box that is still as wide as its max-width allows. */
.fyf-admin-notice { padding: 18px clamp(20px, 4vw, 56px) 0; display: flex; justify-content: center; }
.fyf-admin-notice__card {
  max-width: 820px;
  padding: 14px 22px;
  background-image: var(--texture-grid-paper);
  border-width: var(--stroke-chip);
  /* Sharp index-card corners, not the shared --radius-lg PaperCardComponent
     applies by default -- the mockup deliberately breaks from it. The tilt
     override lives in its own two-class rule below, not here: see that
     rule for why. Both only make sense together with the flex-hugging
     wrapper above -- a block div at the full 820px cap would make the
     un-rounded corners and shallow tilt read as an oversight instead of a
     deliberate index-card look. */
  border-radius: 0;
  box-shadow: none;
}
/* Two-class scope, not .fyf-admin-notice__card alone -- that would tie
   specificity (0,1,0) with .fyf-card--tilt-sm and only win by being
   declared later in the file, the same positional trap the hero board
   tilts avoid below. This wins on specificity (0,2,0) instead. The
   -0.6deg value is exactly HALF of --tilt-sm (-1.2deg) -- a deliberate
   one-off, not the shared token. */
.fyf-admin-notice .fyf-card--tilt-sm { transform: rotate(-0.6deg); }
.fyf-admin-notice__lead {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-pink-deep);
}
.fyf-admin-notice__body { font-size: 14px; color: var(--fyf-charcoal-80); }

/* ── Section headline ────────────────────────────────────────── */
/* Qualified with the .fyf-section ancestor rather than left bare, same trap
   and same fix as .fyf-hero__headline above: .fyf-section__headline is an h2
   and fyf_foundation.css's `.fyf h2{...margin:0}` is class+element (0,1,1),
   which outranks a bare class selector (0,1,0) on every property the two
   share -- not just margin, but font-family/font-weight/font-size/
   line-height/letter-spacing too. Two classes (0,2,0) beats one
   class-plus-element regardless of source order. */
.fyf-section .fyf-section__headline {
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: clamp(34px, 3.8vw, 50px);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  margin: 12px 0 0;
}
.fyf-section .fyf-section__lede {
  margin: 16px 0 0;
  max-width: 64ch;
  font-size: 17px;
  line-height: 1.55;
  color: var(--fyf-charcoal-80);
}
.fyf-section--inverse .fyf-section__lede { color: rgba(250, 220, 194, 0.78); }

/* ── Card grid ───────────────────────────────────────────────── */
.fyf-cards {
  display: grid;
  /* minmax(min(320px, 100%), 1fr), not a bare 320px floor -- the same fix
     .fyf-home-hero's own grid-template-columns comment documents: a bare
     320px minimum can't shrink below that width at viewports narrower than
     ~320px + the section's own padding, which #play (this class's original
     caller) never surfaced because .fyf-section#play carries its own
     overflow: hidden -- the overflow was real, just silently clipped
     instead of showing a scrollbar. #workflow (home page, Task 2) has no
     such clipping, so the same latent bug showed up as a genuine
     320px-viewport horizontal scrollbar -- confirmed via a headless-Chrome
     pass: document.documentElement.scrollWidth (349) exceeded clientWidth
     (320) with the untilted third card measured at exactly 320px wide
     against a 272px-wide content column. Root-caused here rather than
     patched locally with another overflow: hidden, since that would trade
     a visible scrollbar for invisible clipping instead of actually fixing
     the layout. */
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: 30px;
  margin-top: 48px;
}

/* ── Step card ───────────────────────────────────────────────── */
.fyf-step-card { padding: 30px 28px 28px; }
.fyf-step-card__head { display: flex; align-items: baseline; gap: 12px; }
.fyf-step-card__num {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 30px;
  color: var(--fyf-pink);
}
/* Same h-tag trap as the section headline above: .fyf-step-card__title is an
   h3, so it needs the two-class ancestor qualification for the whole rule,
   not just margin. */
.fyf-section .fyf-step-card__title {
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: 28px;
  line-height: 1.05;
  margin: 0;
}
/* .fyf-step-card__body is a p; fyf_foundation.css's `.fyf p{margin:0 0 1em}`
   only touches margin, but it still outranks a bare class on that property,
   so this needs the same ancestor qualification. */
.fyf-section .fyf-step-card__body {
  margin: 20px 0 0;
  font-size: 16px;
  line-height: 1.55;
  color: var(--fyf-charcoal-80);
}
/* Card 03's "fRank Score" mention matches the mock's bold subhead-700
   treatment, not fyf_foundation.css's generic `.fyf strong{font-weight:600}`
   -- color is already right from that base rule, so only weight/family
   need overriding here. */
.fyf-section .fyf-step-card__body strong {
  font-family: var(--font-subhead);
  font-weight: 700;
}

/* "fRank" keeps its natural case even where an ancestor sets
   text-transform: uppercase (the result-total pill, the payoff pill) --
   reused wherever the brand name sits inside all-caps chip/pill copy. */
.fyf-case-keep { text-transform: none; }

/* ── Step card artifacts ─────────────────────────────────────── */
/* Card 01 and card 02 share this mini-board frame. Card 01 adds a position
   pill per row and lifts row 3; card 02's rows are the "locked" placeholder
   (no positions to show) and add the overlay below. */
.fyf-mini-board {
  position: relative;
  margin: 22px 0 0;
  border: var(--stroke-chip) solid var(--fyf-charcoal-30);
  border-radius: var(--radius-md);
  padding: 5px 0;
  display: grid;
  gap: 2px;
}
/* Only card 02 needs this -- its overlay is absolutely positioned against
   the box, and clipping it here (rather than relying on the section's own
   overflow: hidden) keeps the "Locked" stamp's rotation from ever poking
   past the box's rounded corners. */
.fyf-mini-board--locked { overflow: hidden; }
.fyf-mini-board__row {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 12px;
}
/* Card 01 stripes only its second row -- the mockup's tint frames the
   lifted third row rather than continuing past it, so row 4 stays plain
   there. Card 02 has no lifted row and alternates all the way down, so
   .fyf-mini-board--locked adds the row-4 tint back on top of this. */
.fyf-mini-board__row:nth-child(2) { background: var(--fyf-cream-tint); }
.fyf-mini-board--locked .fyf-mini-board__row:nth-child(4) { background: var(--fyf-cream-tint); }
.fyf-mini-board__rank {
  width: 16px;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
}
.fyf-mini-board__bar {
  flex: 1;
  height: 8px;
  background: var(--fyf-charcoal-12);
  border-radius: 4px;
}
/* Compounded with the base class, same reason as .fyf-board__row--lifted
   above: the :nth-child(2)/:nth-child(4) tint rules above are (0,2,0) and
   would otherwise silently outrank a bare `--lifted` class if the lifted
   row ever landed on a tinted position. */
.fyf-mini-board__row.fyf-mini-board__row--lifted {
  background: var(--fyf-white);
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-hard-sm);
  transform: rotate(-1.4deg);
  margin: 2px 10px;
}
.fyf-mini-board__row--lifted .fyf-mini-board__bar { background: var(--fyf-pink); }
.fyf-mini-board__chip {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  color: var(--fyf-cream);
  background: var(--fyf-green);
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  padding: 1px 9px;
}
.fyf-mini-board__overlay {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}
/* rgba(246,246,244,.86) is --fyf-white at 86% -- a one-off scrim with no
   reusable token of its own, same precedent as .fyf-board__row--lifted's
   inline-rgba tint above. */
.fyf-mini-board__locked {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 17px;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--fyf-pink-deep);
  border: var(--stroke-sticker) solid var(--fyf-pink-deep);
  border-radius: var(--radius-sm);
  padding: 6px 20px;
  transform: rotate(-8deg);
  background: rgba(246, 246, 244, .86);
}

/* Card 03's worked-example result rows -- see the ERB comment above them in
   _steps.html.erb for why these numbers are fixed rather than presenter-
   sourced. */
.fyf-result-rows { margin: 22px 0 0; display: grid; gap: 9px; }
.fyf-result-row {
  display: grid;
  grid-template-columns: 58px 1fr auto;
  align-items: center;
  gap: 10px;
  border: var(--stroke-chip) solid var(--fyf-charcoal-30);
  border-radius: var(--radius-md);
  padding: 8px 11px;
}
.fyf-result-row__label {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--fyf-charcoal-60);
}
.fyf-result-row__detail {
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Outcome pill, not a signed figure -- each row states who won the rank
   (+/−/0), so the chip is a bordered pill rather than the plain coloured
   text a numeric delta would use. */
.fyf-result-row__chip {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  padding: 1px 10px;
}
.fyf-result-row__chip--gain { background: var(--fyf-green); color: var(--fyf-cream); }
.fyf-result-row__chip--loss { background: var(--fyf-red); color: var(--fyf-cream); }
.fyf-result-row__chip--even { background: var(--fyf-white); color: var(--fyf-charcoal); }
.fyf-result-total {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  border-top: var(--stroke-chip) dashed var(--fyf-charcoal-30);
  margin-top: 3px;
  padding-top: 11px;
}
.fyf-result-total__label {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-charcoal-60);
}
.fyf-result-total__pill {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  background: var(--fyf-yellow);
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  padding: 3px 12px;
}

/* ── Payoff band ─────────────────────────────────────────────── */
.fyf-payoff {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: clamp(26px, 3.5vw, 48px);
  margin-top: 38px;
  padding: clamp(28px, 3.4vw, 40px) clamp(24px, 3vw, 40px);
  background: var(--fyf-yellow);
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-hard);
  transform: rotate(-0.5deg);
}

/* Left: the roundel badge, its dashed inner ring, and the two pennants
   tucked behind its bottom edge. */
.fyf-payoff__badge {
  flex: none;
  position: relative;
  width: 158px;
  display: grid;
  justify-items: center;
}
.fyf-payoff__pennants {
  position: absolute;
  bottom: -14px;
  left: 50%;
  display: flex;
  gap: 14px;
  transform: translateX(-50%);
}
.fyf-payoff__pennant {
  width: 26px;
  height: 52px;
  background: var(--fyf-pink);
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 78%, 0 100%);
}
.fyf-payoff__pennant--l { transform: rotate(14deg); }
.fyf-payoff__pennant--r { transform: rotate(-14deg); }
.fyf-payoff__roundel {
  position: relative;
  width: 148px;
  height: 148px;
  border-radius: 50%;
  background: var(--fyf-green);
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  box-shadow: var(--shadow-hard-sm);
  display: grid;
  place-items: center;
  align-content: center;
  gap: 2px;
  transform: rotate(-6deg);
  text-align: center;
  color: var(--fyf-cream);
}
/* rgba(246,240,230,.55) is the mock's own literal -- not a named token at
   any opacity (closest is --fyf-white's 246,246,244), so it's transcribed
   as-is, the one hard-coded colour this file is allowed per the design
   brief. Same one-off-scrim precedent as the mini-board overlay above. */
.fyf-payoff__ring {
  position: absolute;
  inset: 8px;
  border: 2px dashed rgba(246, 240, 230, .55);
  border-radius: 50%;
}
.fyf-payoff__plus {
  font-family: var(--font-spice);
  font-weight: 400;
  font-size: 52px;
  line-height: .9;
}
.fyf-payoff__badge-label {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  max-width: 11ch;
  line-height: 1.3;
}

/* Middle: headline + pill. Both are divs/spans rather than p/h-tags, so
   neither hits the p-margin or h1-h4-font-weight traps .fyf-step-card__body
   and .fyf-section__headline above have to work around. min-width stays at
   300px, its original two-column value -- NOT shrunk to squeeze the new
   leaderboard card in beside it. 300px is a real content floor, not a
   guess: .fyf-payoff__pill is nowrap and its full text ("Highest fRank
   Score wins") is ~272px, so anything smaller lets the pill's own ink spill
   past this box's edge -- flexbox min-width is a floor for the item's
   *rendered* size, it does not shrink the item's *content*. The band's
   flex-wrap already drops copy to its own row (and the card to the row
   after) once there isn't room for all three -- see the narrow-viewport
   media query below for the one case flex-wrap alone can't cover: a band
   whose own inner width is narrower than this 300px floor. */
.fyf-payoff__copy {
  flex: 1;
  min-width: 300px;
  display: grid;
  gap: 20px;
  justify-items: start;
}
.fyf-payoff__headline {
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: clamp(30px, 3.4vw, 46px);
  line-height: 1.04;
  letter-spacing: var(--tracking-display);
  max-width: 22ch;
}
/* "fRank Score" reads as one bold unit inside the header-font sentence --
   not the .fyf-case-keep treatment (this context is never uppercase, so
   text-transform:none would do nothing visible here). Scoped to the payoff
   headline: it's the only spot in the design reference that bolds "fRank
   Score" together mid-sentence -- the scoring card's other two mentions
   either sit in an already-uppercase pill (case-keep territory) or take
   the header font's own weight, not this subhead-700 override. */
.fyf-payoff__score {
  font-family: var(--font-subhead);
  font-weight: 700;
  letter-spacing: 0;
}
.fyf-payoff__pill {
  background: var(--fyf-charcoal);
  color: var(--fyf-cream);
  border-radius: var(--radius-pill);
  padding: 11px 22px;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  white-space: nowrap;
}

/* Below ~396px the band's own inner width (padding floors out around 96px
   total between .fyf-section and .fyf-payoff) drops under .fyf-payoff__copy's
   300px floor. Zeroing that floor alone isn't the fix, though: while it's
   still sharing a row with the 158px roundel, copy's flex-grow share can
   shrink to a sliver (tens of px) that's narrower than a single word of the
   headline's own huge clamp(30px, 3.4vw, 46px) font -- a wrapped headline
   still can't break a word in half, so it overflows that sliver regardless.
   Below the breakpoint the band stops trying to share rows at all: badge,
   copy, and card each stack full-width instead, so copy always gets the
   whole band's width rather than a fraction of it -- comfortably more than
   any single word needs. Even the full band width runs out for the nowrap
   pill on the narrowest phones (its one-line text is ~272px; the band's own
   inner width is ~224px at a 320px viewport), so the pill is also allowed
   to wrap. 480px picks up every phone width in the danger zone with margin
   to spare; two-across / three-across above it are untouched. */
@media (max-width: 480px) {
  .fyf-payoff {
    flex-direction: column;
    align-items: stretch;
  }
  /* Explicit width overrides align-items: stretch, so the roundel keeps its
     own 158px instead of stretching full-width -- centered via auto margins
     rather than left-stuck against the band's edge. */
  .fyf-payoff__badge {
    margin: 0 auto;
  }
  .fyf-payoff__copy {
    min-width: 0;
  }
  .fyf-payoff__pill {
    white-space: normal;
  }
}

/* Right: the leaderboard teaser card. A third flex child alongside the
   roundel and the headline column -- PaperCardComponent (tone: paper,
   no tilt, no tape) supplies the white stock, texture, keyline, and hard
   shadow, so only the sizing and the contents are declared here. flex-basis
   300px matches .fyf-payoff__copy's own min-width so the two share the
   band's leftover space evenly; the max-width keeps the card from
   stretching wide and empty on an ultra-wide band. It wraps to its own
   full-width row below the badge + headline once the band can't fit
   all three -- same flex-wrap the band already had for two children. */
.fyf-payoff__board {
  flex: 1 1 300px;
  /* Flex items default to min-width: auto -- their content's min-content
     size becomes a hard floor the flex-basis/max-width above can't shrink
     past. "frontyardfaithful" is one unbroken word, so without this the
     card (and the band) would overflow the viewport on a narrow screen
     rather than wrap it. See .fyf-leader-row's minmax(0, 1fr) below for
     the matching fix one level down, at the grid track that word lives in. */
  min-width: 0;
  max-width: 400px;
  padding: clamp(22px, 2.4vw, 26px);
  display: grid;
  gap: 14px;
  align-content: start;
  text-align: left;
}
.fyf-payoff__board-eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-charcoal-60);
}
.fyf-payoff__board-dot {
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--fyf-green);
  border: var(--stroke-chip) solid var(--fyf-charcoal);
}
/* Header font, sentence case -- a headline is never uppercase, brand rule
   (see the .fyf h1-h4 comment in fyf_foundation.css). */
.fyf-payoff__board-headline {
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: clamp(20px, 2vw, 26px);
  line-height: 1.1;
  color: var(--fyf-charcoal);
}
.fyf-leader-rows {
  display: grid;
  gap: 10px;
}
.fyf-leader-row {
  display: grid;
  /* minmax(0, 1fr), not a bare 1fr -- a bare 1fr track's minimum size is
     its content's min-content width, not 0, so a name column would refuse
     to shrink past its longest unbroken word and blow out the row (and the
     card, and the viewport) instead of wrapping. */
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  padding: 8px 16px;
}
.fyf-leader-row__rank {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  color: var(--fyf-charcoal-60);
}
.fyf-leader-row__name {
  font-family: var(--font-body);
  font-size: 15px;
  color: var(--fyf-charcoal);
  /* "frontyardfaithful" is one unbroken word -- let it wrap mid-word on a
     narrow card rather than overflow. Never truncate a display name here,
     same house rule the ranking lists follow. */
  overflow-wrap: anywhere;
}
.fyf-leader-row__score {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  color: var(--fyf-cream);
  background: var(--fyf-green);
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  padding: 2px 12px;
}
/* Row 3 is the joke/aspirational entry -- "You, hopefully" hasn't scored
   anything yet. Dashed border + the page's alt cream tint mark it as not
   real data, same "this isn't live" signal the mini-board's locked overlay
   and the scoring card's worked example use elsewhere on this page. */
.fyf-leader-row--you {
  border-style: dashed;
  border-color: var(--fyf-charcoal-60);
  background: var(--fyf-cream-tint);
}
/* Overrides the pill shape above with a circle for the unscored "?". */
.fyf-leader-row__score--tbd {
  width: 28px;
  height: 28px;
  padding: 0;
  display: grid;
  place-items: center;
  color: var(--fyf-charcoal);
  background: var(--fyf-white);
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: 50%;
}

/* ── Scoring rules link ──────────────────────────────────────── */
/* 2026-08-21: the <details> accordion that used to sit here (a scoring
   card walking the four-step formula, the outcome trio, and an "advanced"
   double-matchup card -- .fyf-scoring, .fyf-outcomes, .fyf-scoring-details,
   .fyf-scoring-advanced) is gone; every line of it duplicated /rules. Its
   toggle survives as a plain link to that page, in the same dress: the
   summary's pink tracked-uppercase label over a sticker-weight underline.
   Centred at the accordion's old 960px, deliberately departing from the
   mockup -- same call as commit 0830200 made for this section's old
   .fyf-scoring-wrap. The mockup's own 960px/no-margin still sits
   left-aligned with dead space to its right while every other section
   fills the container, so the on-sight "this looks broken" report still
   applies. Do not re-left-align this on a future fidelity pass without
   re-litigating that report. */
.fyf-scoring-link-wrap { max-width: 960px; margin: 40px auto 0; }
/* Qualified with .fyf-section: fyf_foundation.css's `.fyf a` (0,1,1) sets
   the link colour and a 2px underline, and a bare single-class rule (0,1,0)
   loses to it on both. The underline lives on the inner span instead, so
   the rule (not the arrow) carries it, as the summary's did. */
.fyf-section .fyf-scoring-link {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-pink-deep);
  text-decoration: none;
}
.fyf-section .fyf-scoring-link:hover { color: var(--fyf-pink-deep); }
.fyf-section .fyf-scoring-link:hover .fyf-scoring-link__text { border-bottom-color: var(--fyf-charcoal); }
/* Nothing clips this link (unlike .fyf-disclosure__summary), so the UA ring
   can draw outside the box; offset pushes it clear of the underline. */
.fyf-section .fyf-scoring-link:focus-visible {
  outline: var(--stroke-sticker) solid var(--fyf-charcoal);
  outline-offset: 4px;
}
.fyf-scoring-link__text {
  border-bottom: var(--stroke-sticker) solid var(--fyf-pink-deep);
  padding-bottom: 3px;
}

/* ── Scoring stage ────────────────────────────────────────────
   The four-beat demo: a matchup, a season-value meter, a weight table, and
   the running total, cycled by Task 4's scoring-stage controller. Static
   here -- beat 0 visible, beats 1-3 `hidden`, dot 0 filled. No max-width:
   the mockup's own stage has none, unlike the rules link below it, so it
   fills the section container the way every other full-width block here
   does. min-height: 520px is a floor, not a cap -- overflow: hidden still
   only clips the dot-grid background/rounded corners at the edges; the
   beat pane below is normal flow now (not absolutely inset to this box),
   so taller-than-520px content on a phone grows this box instead of
   spilling past a fixed height and getting clipped. display: flex;
   flex-direction: column stacks the beat pane above the control bar in
   source order (both normal-flow children now) and lets the bar's own
   margin-top: auto below pin it to the bottom edge regardless of how
   tall the beat above it grows. */
.fyf-stage {
  position: relative;
  display: flex;
  flex-direction: column;
  margin: 28px 0 0;
  background: var(--fyf-cream-tint);
  background-image: radial-gradient(var(--fyf-charcoal-12) 1.5px, transparent 1.5px);
  background-size: 20px 20px;
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-hard);
  min-height: 520px;
  overflow: hidden;
}
/* Normal flow, not an absolute overlay: hidden beats are display: none
   (the [hidden] override below), so at most one beat pane is ever in the
   render tree at a time -- there was never anything to overlay. The old
   `position: absolute; inset: 0 0 60px 0` pinned every beat to exactly
   (the stage's containing block for absolute children -- its PADDING
   box, not its border box) minus the control bar's 60px bottom reserve.
   fyf_foundation.css sets box-sizing: border-box on everything under
   .fyf, and .fyf-stage's min-height: 520px above is a border-box value
   with a 3px border (--stroke-sticker) on all four sides, so that
   padding box was 520 - 6 = 514px, not 520 -- and the old beat height
   was 514 - 60 = 454px. min-height: 454px reproduces that exact number
   (not the stage's own 520px minus 60, which overshoots by the 6px of
   border) as a floor rather than a hard box: desktop, where every beat's
   real content is well under it -- the matchup beats (row + empty/filled
   meter-slot) run ~373px, the weight table ~333px, the running total
   ~347px, each including this rule's own 24px top/bottom padding --
   renders unchanged, while on phone/tablet content taller than 454px now
   grows the beat (and with it .fyf-stage above, whose own min-height is
   likewise just a floor) instead of clipping under the bar. */
.fyf-stage__beat {
  display: grid;
  align-content: center;
  justify-items: center;
  min-height: 454px;
  padding: 24px;
}
.fyf-stage__beat--matchup { gap: 24px; }
.fyf-stage__beat--total { gap: 28px; }
/* Tailwind preflight's `[hidden]:where(:not([hidden=until-found])){display:none}`
   is (0,1,0) specificity -- the same as this class -- and marketing.css
   loads after Tailwind, but source order only breaks a TIE, and preflight's
   `:where()` wrapper contributes none, so the tie is real and this file's
   `display: grid` above wins it, leaving all four beats stacked visible.
   Same fix as .cmp-menu[hidden]/.fr-palette-backdrop[hidden] in editor.css. */
.fyf-stage__beat[hidden] { display: none; }

/* Beats 0-1: the matchup row */
.fyf-stage__row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(14px, 2.4vw, 30px);
  flex-wrap: wrap;
}
/* flex-wrap: wrap above is what shipped as the phone fallback, and it's
   not a designed stack -- two 36vw cards + the 52px VS roundel + their
   two gaps only clear the beat's available content width above roughly
   701px, so anything narrower wraps mid-row: the market card drops alone
   to a second line, left-stuck instead of centred under the VS roundel.
   The crossover: section padding is clamp(24px,4.4vw,56px), so for
   viewport V in the un-clamped range available width = V - 2*(.044V) -
   6px border - 48px beat padding = .912V - 54. Required width (once
   `min(250px,36vw)` caps each card at 250px, true above V=694.4) =
   2*250 + 52 roundel + 2*clamp(14px,2.4vw,30px) = 552 + .048V. Setting
   them equal, .912V - 54 = 552 + .048V, solves to V ~= 701.4px. 720px
   gives that a few px of headroom. Below it, force the same three items
   into a clean vertical stack -- card / VS / card, centred -- instead of
   leaving the browser's wrap point to decide. Tablet (768px) is above
   this breakpoint and untouched, matching the desktop row layout
   (required ~=588.9px there vs. ~=646.4px available -- a comfortable
   margin). */
@media (max-width: 720px) {
  .fyf-stage__row { flex-direction: column; align-items: center; }
}
.fyf-stage__card {
  width: min(250px, 36vw);
  background: var(--fyf-white);
  background-image: var(--texture-paper);
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: var(--radius-lg);
}
/* 1s, not the mock's .55s -- user-directed slowdown of the card slide-in
   (2026-08-20 UAT). The VS pop's delay below tracks this duration so it
   still lands as the cards settle; don't retune one without the other. */
.fyf-stage__card--you {
  box-shadow: var(--shadow-hard);
  animation: fyf-slide-l 1s var(--ease-snap) both;
}
.fyf-stage__card--market {
  box-shadow: var(--shadow-hard-sm);
  animation: fyf-slide-r 1s var(--ease-snap) both;
}
.fyf-stage__card-head {
  border-bottom: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: calc(var(--radius-lg) - 4px) calc(var(--radius-lg) - 4px) 0 0;
  padding: 8px 14px;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
.fyf-stage__card-head--you { background: var(--fyf-pink); }
.fyf-stage__card-head--market { background: var(--fyf-charcoal); color: var(--fyf-cream); }
.fyf-stage__card-name {
  padding: 16px 14px 18px;
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: clamp(20px, 2vw, 26px);
  line-height: 1.05;
}
.fyf-stage__vs {
  flex: none;
  width: 52px;
  height: 52px;
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: 999px;
  background: var(--fyf-yellow);
  box-shadow: var(--shadow-hard-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 14px;
  animation: fyf-pop .3s var(--ease-pop) .9s both;
}

/* Beat 1: the season-value meter, appended after the shared matchup row */
.fyf-stage__meter-slot { min-height: 204px; width: min(560px, 100%); display: grid; align-content: start; }
/* grid-template-columns lives here, on the shared parent, not on each
   .fyf-stage__meter-row -- two independent grids each resolving their own
   minmax(52px, max-content) gave "Jaxon Smith-Njigba" and "Bijan Robinson"
   different column-1 widths, so the two bar tracks started at different x
   offsets with different widths and the 78%/52% fills stopped being
   comparable at a glance, which is the whole point of a meter. Hoisting
   the columns here and making each row `display: contents` (below) makes
   both name/track pairs items of this ONE grid, so column 1 resolves once
   -- sized to the longer of the two names -- and both tracks share it. */
.fyf-stage__meter {
  display: grid;
  grid-template-columns: minmax(52px, max-content) 1fr;
  align-items: center;
  gap: 9px 10px;
  background: var(--fyf-white);
  background-image: var(--texture-paper);
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-hard-sm);
  padding: 14px 18px 16px;
  animation: fyf-rise .4s var(--ease-snap) both;
}
.fyf-stage__meter-head {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 4px 14px;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
.fyf-stage__meter-head-sub { white-space: nowrap; color: var(--fyf-charcoal-60); }
/* display: contents -- not its own grid -- so its two children (name,
   track) become direct items of .fyf-stage__meter's grid above and share
   its column tracks instead of each row sizing column 1 independently.
   The name still wraps rather than truncating if the shared column ever
   gets tighter than the longer name (no white-space: nowrap here or on
   .fyf-stage__meter-name). */
.fyf-stage__meter-row { display: contents; }
.fyf-stage__meter-name { font-family: var(--font-subhead); font-weight: 700; font-size: 11px; letter-spacing: .05em; }
.fyf-stage__meter-track {
  display: block;
  position: relative;
  height: 16px;
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: 999px;
  overflow: hidden;
  background: var(--fyf-cream-tint);
}
.fyf-stage__meter-fill { display: block; height: 100%; }
.fyf-stage__meter-fill--you { background: var(--fyf-pink); animation: fyf-bar-you 3s steps(18, end) .3s both; }
.fyf-stage__meter-fill--mkt { background: var(--fyf-blue); animation: fyf-bar-mkt 3s steps(18, end) .3s both; }
/* The week-tick overlay -- an allowed literal (see design_tokens_spec's
   scoping check + this task's brief): 18 weekly ticks over the bar, at the
   same faint ink used for the board-row hairlines elsewhere on this page. */
.fyf-stage__meter-ticks {
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(
    90deg,
    transparent 0, transparent calc(100% / 18 - 2px),
    rgba(48, 32, 45, .22) calc(100% / 18 - 2px), rgba(48, 32, 45, .22) calc(100% / 18)
  );
  pointer-events: none;
}
/* grid-column: 1 / -1 -- also a direct child of .fyf-stage__meter's now
   2-column grid, so it has to span both tracks itself or it would be
   auto-placed into just column 1 and squeezed to that width. */
.fyf-stage__verdicts { grid-column: 1 / -1; display: grid; gap: 5px; margin-top: 6px; justify-content: center; }
/* minmax(0, 1fr), not a fixed 106px: 106px fit the mockup's "JSN > BIJAN"
   abbreviation, but this project spells illustrative names out in full
   ("Jaxon Smith-Njigba > Bijan Robinson"), which overflowed the fixed
   column by ~150px. The chip (92px) and note (96px) columns stay fixed --
   only the label needs to give. */
.fyf-stage__verdict {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 92px 96px;
  align-items: center;
  gap: 12px;
  padding: 4px 12px;
}
.fyf-stage__verdict--win {
  background: rgba(31, 112, 49, .1);
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-sm);
  padding: 4px 10px;
  animation: fyf-rise .35s var(--ease-snap) 3.5s both;
}
.fyf-stage__verdict--tie { animation: fyf-rise .35s var(--ease-snap) 3.7s both; }
.fyf-stage__verdict--loss { animation: fyf-rise .35s var(--ease-snap) 3.9s both; }
/* No white-space: nowrap -- same rule as .fyf-board__name/.fyf-stage__weight-name: this project never truncates a player name, and the flexible label column above lets it wrap instead of clipping. */
.fyf-stage__verdict-label { font-family: var(--font-subhead); font-weight: 700; font-size: 12px; letter-spacing: .04em; }
.fyf-stage__verdict-label--muted { color: var(--fyf-charcoal-80); }
.fyf-stage__verdict-op--win  { color: var(--fyf-green-deep); }
.fyf-stage__verdict-op--tie  { color: var(--fyf-charcoal-60); }
.fyf-stage__verdict-op--loss { color: var(--fyf-red-deep); }
.fyf-stage__verdict-chip {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: .04em;
  text-transform: uppercase;
  text-align: center;
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  padding: 3px 0;
}
.fyf-stage__verdict-chip--win  { color: var(--fyf-cream); background: var(--fyf-green); }
.fyf-stage__verdict-chip--tie  { background: var(--fyf-cream-tint); }
.fyf-stage__verdict-chip--loss { color: var(--fyf-cream); background: var(--fyf-red); }
.fyf-stage__verdict-note {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  white-space: nowrap;
  animation: fyf-pop .3s var(--ease-pop) 4.4s both;
}
/* ≤600px: the chip (92px) + note (96px) fixed columns + 2×12px gaps
   already total 212px. At 390px viewport: section padding clamps to its
   24px floor (4.4vw = 17.16px < 24px), so the stage is 390 - 48 = 342px
   wide; minus its 3px*2 border, 336px; minus the beat's 24px*2 padding,
   288px of beat content. The meter box stretches to fill that (its slot
   is width: min(560px,100%), and 100% of 288 < 560), so minus the
   meter's own 18px*2 padding, the verdicts block (grid-column: 1/-1 of
   the meter's grid) is 288 - 36 = 252px wide. 252 - 212 leaves the
   flexible label column only ~40px -- nowhere near enough for "Jaxon
   Smith-Njigba > Bijan Robinson" (this
   project spells worked-example names out in full, never an initialism --
   see the no-nowrap comment on .fyf-stage__verdict-label above), so it
   wrapped to five or six near-empty lines. Drop the short, already-nowrap
   note ("<- this season") to its own full-width line below instead --
   it never needed the room -- freeing the label column back up to
   ~148px, comfortable for a two-line wrap of the full comparison. */
@media (max-width: 600px) {
  .fyf-stage__verdict {
    grid-template-columns: minmax(0, 1fr) 92px;
    grid-template-areas:
      "label chip"
      "note  note";
  }
  .fyf-stage__verdict-label { grid-area: label; }
  .fyf-stage__verdict-chip { grid-area: chip; }
  .fyf-stage__verdict-note { grid-area: note; }
  /* The tie/loss verdicts never render a .fyf-stage__verdict-note (only
     the win verdict's "<- this season" copy exists in the ERB -- see
     _scoring_stage.html.erb), so their "note" row/area above would sit
     permanently empty. An empty grid row still pulls in the base rule's
     gap: 12px row-gap ahead of it even with nothing inside it (gap is a
     gutter between tracks, not conditional on their occupancy), which
     left a dead 12px sliver below every tie/loss row. Collapsing their
     template to the single real row removes that gutter outright instead
     of just shrinking it. */
  .fyf-stage__verdict--tie,
  .fyf-stage__verdict--loss {
    grid-template-areas: "label chip";
  }
}

/* Beat 2: the weight table */
.fyf-stage__panel {
  width: min(640px, 94%);
  background: var(--fyf-white);
  background-image: var(--texture-paper);
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-hard-sm);
  overflow: hidden;
}
.fyf-stage__weight-header {
  display: grid;
  grid-template-columns: 44px 68px minmax(0, 1fr) minmax(0, 1fr) 58px;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: var(--fyf-cream-tint);
  border-bottom: var(--stroke-chip) solid var(--fyf-charcoal);
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-charcoal-60);
}
.fyf-stage__weight-center { text-align: center; }
.fyf-stage__weight-row {
  display: grid;
  grid-template-columns: 44px 68px minmax(0, 1fr) minmax(0, 1fr) 58px;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
}
.fyf-stage__weight-row--r1   { animation: fyf-rise .35s var(--ease-snap) .05s both; }
.fyf-stage__weight-row--r2   { animation: fyf-rise .35s var(--ease-snap) .2s both; }
.fyf-stage__weight-row--r3   { animation: fyf-rise .35s var(--ease-snap) .35s both; }
.fyf-stage__weight-row--mid  { animation: fyf-rise .35s var(--ease-snap) .5s both; }
.fyf-stage__weight-row--last { animation: fyf-rise .35s var(--ease-snap) .65s both; }
/* The "your pick" row (#2) -- same translucent-brand-tint technique as
   .fyf-board__row--lifted above, just pink instead of green: highlighting a
   row with its own accent colour at low alpha, not yet in a token. */
.fyf-stage__weight-row--you {
  background: rgba(240, 70, 160, .1);
  border-top: var(--stroke-chip) solid var(--fyf-charcoal);
  border-bottom: var(--stroke-chip) solid var(--fyf-charcoal);
}
.fyf-stage__weight-row--divider { border-top: var(--stroke-chip) solid var(--fyf-charcoal-12); }
.fyf-stage__weight-gap {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 13px;
  color: var(--fyf-charcoal-60);
  padding: 2px 14px;
  line-height: .9;
  border-top: var(--stroke-chip) solid var(--fyf-charcoal-12);
}
.fyf-stage__weight-rank { font-family: var(--font-subhead); font-weight: 700; font-size: 14px; }
.fyf-stage__weight-pill {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  text-align: center;
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  padding: 2px 0;
}
/* The yellow alpha ramp -- an allowed literal (this task's brief): five
   illustrative rows fading from full weight to almost none, same fyf-yellow
   the rest of the page reaches with var(), just not opaque here. */
.fyf-stage__weight-pill--1   { background: rgba(250, 202, 22, .92); }
.fyf-stage__weight-pill--2   { background: rgba(250, 202, 22, .88); }
.fyf-stage__weight-pill--3   { background: rgba(250, 202, 22, .84); }
.fyf-stage__weight-pill--mid { background: rgba(250, 202, 22, .48); }
.fyf-stage__weight-pill--last { background: rgba(250, 202, 22, .12); }
.fyf-stage__weight-player { display: flex; align-items: center; gap: 7px; min-width: 0; }
/* No white-space: nowrap / text-overflow: ellipsis -- same rule as
   .fyf-board__name above: this project never truncates a player name, even
   in a compact illustrative table, and these rows spell the full name out
   (never an initialism) specifically so it reads without abbreviating. */
.fyf-stage__weight-name { font-family: var(--font-header); font-size: 15px; line-height: 1.1; }
.fyf-stage__weight-pts {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  text-align: center;
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  padding: 2px 0;
}
.fyf-stage__weight-pts--zero { background: var(--fyf-cream-tint); }
.fyf-stage__weight-pts--gain { color: var(--fyf-cream); background: var(--fyf-green); }
.fyf-stage__weight-pts--loss { color: var(--fyf-cream); background: var(--fyf-red); }
/* ≤600px: the fixed 44px/68px/58px rank/weight/pts columns + 4x10px gaps
   already total 210px. At 390px viewport, the beat content area is 288px
   (see the derivation in the ≤600px .fyf-stage__verdict comment above --
   same 390 -> 342 -> 336 -> 288 chain); the panel's own width: min(640px,
   94%) resolves to 94% of that, ~271px, since 640 > 271. 271 minus the
   row's 14px*2 padding is ~243px of row content -- leaving the two
   flexible pick columns ~33px combined (243 - 210),
   under a single position pill's own 32px min-width (.fyf-pill--sm)
   before a single letter of the name. Below this breakpoint, restructure
   each row into three lines instead of one: rank + weight + fRank pts
   stay together on line one (the same three values the header used to
   label), and the two picks each get their own full-width line below,
   with the panel's whole ~271px to themselves -- comfortably fitting
   even the longest names without truncating (never truncate a player
   name -- project rule, see .fyf-stage__weight-name above). The header
   row is dropped rather than reflowed to match: "Rank / Weight / Your
   pick / Market's pick / fRank pts" doesn't map onto the three-line shape
   below it, and the same information already reads off each row's own
   rank number, x-weight pill, position pill, and coloured pts pill
   without the column labels. Tablet (768px) is well above this
   breakpoint (its two pick columns land around 185px each unmodified)
   and renders the original 5-column row untouched.

   The weight and pts pills need their own padding restored here, not
   "unchanged" from the base rule above: their 68px/58px desktop width
   came entirely from the old fixed grid TRACK stretching them (the pill
   rules themselves only ever set padding: 2px 0, no horizontal padding
   of their own), and grid-template-columns: auto auto 1fr auto sizes the
   `auto` weight/pts tracks to each pill's own max-content width instead
   -- with zero horizontal padding, that's just the glyph's ("x1.00",
   "+") own width, collapsing both pills to ~12px lozenges. padding: 2px
   10px below is what gives them back a pill-shaped hit area now that no
   fixed track is doing that job. */
@media (max-width: 600px) {
  .fyf-stage__weight-header { display: none; }
  .fyf-stage__weight-row {
    grid-template-columns: auto auto 1fr auto;
    grid-template-areas:
      "rank weight .   pts"
      "you  you    you you"
      "mkt  mkt    mkt mkt";
  }
  .fyf-stage__weight-rank { grid-area: rank; }
  .fyf-stage__weight-pill { grid-area: weight; padding: 2px 10px; }
  .fyf-stage__weight-pts { grid-area: pts; padding: 2px 10px; }
  .fyf-stage__weight-player { grid-area: you; }
  /* --mkt is an explicit modifier on the market's-pick span in the ERB
     (_scoring_stage.html.erb), not an `+ .fyf-stage__weight-player`
     adjacent-sibling match against the class alone -- a sibling
     combinator only counts "comes right after another one of these",
     which happens to be true for today's fixed two-pick row but would
     silently swallow a third pick into this same area if one were ever
     added. The modifier carries the same specificity as the base rule
     above, so source order (this rule after it) is what lets `--mkt`
     override `you` for the one span that has both classes. */
  .fyf-stage__weight-player--mkt { grid-area: mkt; }
}
.fyf-stage__weight-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 4px 14px;
  padding: 8px 14px;
  border-top: var(--stroke-chip) dashed var(--fyf-charcoal-30);
  background: var(--fyf-cream-tint);
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-charcoal-60);
  animation: fyf-rise .35s var(--ease-snap) .8s both;
}
.fyf-stage__nowrap { white-space: nowrap; }

/* Beat 3: the running total */
.fyf-stage__total-panel {
  width: min(460px, 94%);
  display: grid;
  gap: 7px;
  background: var(--fyf-white);
  background-image: var(--texture-paper);
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-hard-sm);
  padding: 16px 20px 18px;
}
.fyf-stage__total-row { display: grid; grid-template-columns: 48px 1fr auto; align-items: center; gap: 12px; }
.fyf-stage__total-row--1    { animation: fyf-rise .35s var(--ease-snap) .05s both; }
.fyf-stage__total-row--2    { animation: fyf-rise .35s var(--ease-snap) .25s both; }
.fyf-stage__total-row--3    { animation: fyf-rise .35s var(--ease-snap) .45s both; }
.fyf-stage__total-row--last { animation: fyf-rise .35s var(--ease-snap) .75s both; }
.fyf-stage__total-rank { font-family: var(--font-subhead); font-weight: 700; font-size: 14px; }
.fyf-stage__total-leader { border-bottom: 2px dotted var(--fyf-charcoal-30); }
.fyf-stage__total-pill {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  width: 64px;
  text-align: center;
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  padding: 2px 0;
}
.fyf-stage__total-pill--gain { color: var(--fyf-cream); background: var(--fyf-green); }
.fyf-stage__total-pill--loss { color: var(--fyf-cream); background: var(--fyf-red); }
.fyf-stage__total-pill--zero { background: var(--fyf-cream-tint); }
.fyf-stage__total-gap {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 14px;
  color: var(--fyf-charcoal-60);
  padding-left: 14px;
  line-height: .9;
  animation: fyf-rise .35s var(--ease-snap) .6s both;
}
.fyf-stage__total-sum {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  border-top: 2px dashed var(--fyf-charcoal-30);
  margin-top: 5px;
  padding-top: 14px;
  animation: fyf-pop .5s var(--ease-pop) 1.1s both;
}
.fyf-stage__total-label {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-charcoal-60);
}
.fyf-stage__total-badge {
  background: var(--fyf-yellow);
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-hard-sm);
  padding: 10px 22px;
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: clamp(22px, 2.2vw, 30px);
  line-height: 1;
}
.fyf-stage__legend {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  justify-content: center;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  animation: fyf-rise .45s var(--ease-snap) 1.9s both;
}
.fyf-stage__legend-chip { border: var(--stroke-chip) solid var(--fyf-charcoal); border-radius: var(--radius-pill); padding: 5px 14px; }
.fyf-stage__legend-chip--loss { color: var(--fyf-cream); background: var(--fyf-red); }
.fyf-stage__legend-chip--win  { color: var(--fyf-cream); background: var(--fyf-green); }
.fyf-stage__legend-zero {
  width: 32px;
  height: 32px;
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
}

/* Bottom control bar -- normal flow, not `position: absolute; bottom: 0`
   overlaying the beat above it as it used to: as .fyf-stage's last child
   (.fyf-stage is now `display: flex; flex-direction: column` above), it
   simply follows whichever beat pane is in flow above it, so it can never
   be overlapped by (or clip) taller beat content the way the old overlay
   could. margin-top: auto is what keeps it bottom-flush rather than just
   coincidentally near the bottom: the bar's own rendered height (~53.5px:
   2px border-top + 11px*2 padding + its tallest inline child) doesn't
   fill the beat's 60px reserve on its own, so the auto margin absorbs
   whatever's left of .fyf-stage's 520px floor and pins the bar to the
   edge -- and once a beat grows past that floor, the margin has nothing
   left to absorb and the bar simply sits right after it, still at the
   true bottom of the (now taller) box. Dot 0 starts filled (the pink
   modifier below); Task 4's controller moves the fill as it moves the
   beats. */
.fyf-stage__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: auto;
  padding: 11px 18px;
  border-top: var(--stroke-chip) solid var(--fyf-charcoal);
  background: var(--fyf-white);
  background-image: var(--texture-paper);
}
.fyf-stage__caption {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: clamp(13px, 1.3vw, 15px);
  letter-spacing: .02em;
}
.fyf-stage__controls { display: flex; align-items: center; gap: 8px; }
.fyf-stage__dot {
  width: 12px;
  height: 12px;
  border-radius: 999px;
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  background: var(--fyf-white);
  cursor: pointer;
  padding: 0;
}
.fyf-stage__dot--active { background: var(--fyf-pink); }
.fyf-stage__bar-btn {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  background: var(--fyf-white);
  padding: 5px 12px;
  cursor: pointer;
}
.fyf-stage__bar-btn--toggle { margin-left: 6px; }

/* ── Scoring stage keyframes + play state ────────────────────────
   Renamed fyf-prefixed-kebab from the mockup's fyfSlideL/fyfSlideR/fyfPop/
   fyfRise/fyfBarYou/fyfBarMkt. Task 4's controller toggles [data-paused] on
   the stage root (also carrying .fyf-stage, which is what satisfies
   design_tokens_spec's selector-scoping check below -- a bare
   [data-fyf-stage][data-paused] selector would not). Pausing snaps every
   running animation to its end state (duration/delay to ~0, but
   play-state forced running rather than paused) rather than literally
   freezing mid-motion, so "Pause" reads as "skip to settled", not a stutter
   -- and the reduced-motion query below does the same unconditionally. */
@keyframes fyf-slide-l {
  from { transform: translateX(-70px); opacity: 0; }
  to   { transform: translateX(0);     opacity: 1; }
}
@keyframes fyf-slide-r {
  from { transform: translateX(70px); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}
@keyframes fyf-pop {
  from { transform: scale(0); }
  to   { transform: scale(1); }
}
@keyframes fyf-rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes fyf-bar-you { from { width: 0; } to { width: 78%; } }
@keyframes fyf-bar-mkt { from { width: 0; } to { width: 52%; } }
@media (prefers-reduced-motion: reduce) {
  .fyf-stage * { animation-duration: .01s !important; animation-delay: 0s !important; }
}
.fyf-stage[data-paused] * {
  animation-duration: .01s !important;
  animation-delay: 0s !important;
  animation-play-state: running !important;
}

/* ── Rules cards ─────────────────────────────────────────────── */
.fyf-rules-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 22px;
  align-items: stretch;
  margin-top: 44px;
}
/* display: grid (not just align-content: start, which is inert without a
   grid/flex container) is what turns the card's own gap into the vertical
   rhythm between eyebrow/title/body -- so each child's own margin can stay
   at (or near) 0, matching the handoff. The Deadline (cream) card uses a
   tighter 8px gap than the other two, which use 10px -- see the override
   below. */
.fyf-rules-card { display: grid; gap: 10px; padding: 28px; align-content: start; }
.fyf-rules-card.fyf-card--cream { gap: 8px; }
/* .fyf-rules-card__title is an h3 -- same h-tag trap as .fyf-step-card__title
   above: fyf_foundation.css's `.fyf h1,h2,h3,h4{...margin:0}` combined
   selector sits at (0,1,1) on font-family/weight/line-height/letter-spacing/
   margin, and a bare single class loses on all of them. Qualified with the
   .fyf-rules-cards ancestor to reach (0,2,0), same fix as everywhere else in
   this file. margin: 0 because the card's own grid gap (above) supplies the
   space above the title now. */
.fyf-rules-cards .fyf-rules-card__title {
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: 30px;
  line-height: 1.03;
  letter-spacing: var(--tracking-display);
  margin: 0;
}
/* .fyf-rules-card__body and __date are <p>s -- same margin trap. Full-
   opacity cream, not an alpha: on the Win card's solid green fill,
   rgba(250,220,194,.88) measures 4.01:1 -- short of AA at 16px. Full-opacity
   cream clears 4.70:1, same reasoning already applied to the hero copy. It
   also reads better on the Enter card's transparent/charcoal fill, so one
   value serves both. margin: 0 -- the card's grid gap supplies the space
   above the body on the Enter/Win cards; the Deadline card's --dark variant
   below adds its own 6px on top of the (tighter, 8px) card gap. */
.fyf-rules-cards .fyf-rules-card__body {
  margin: 0;
  font-size: 16px;
  line-height: 1.5;
  color: var(--fyf-cream);
}
/* The modifier needs the SAME ancestor qualification as the base rule above
   -- a bare `.fyf-rules-card__body--dark` is (0,1,0), which loses outright
   to the base rule's (0,2,0) regardless of source order, so the Deadline
   card's cream fallback was winning over its own charcoal override: cream
   text on the cream card fill measured ~1:1, invisible. This is the exact
   trap the ancestor-qualification comments throughout this file warn about,
   reproduced by forgetting to apply it to a modifier alongside its base. */
.fyf-rules-cards .fyf-rules-card__body--dark { color: var(--fyf-charcoal-80); margin-top: 6px; }
.fyf-rules-cards .fyf-rules-card__date {
  margin: 0;
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: clamp(32px, 3.2vw, 42px);
  line-height: 1;
  letter-spacing: var(--tracking-display);
  color: var(--fyf-charcoal);
}
/* The date and time halves are two <span>s, not a <br>-split string, so a
   spec can assert on each in isolation. display: block stacks them into the
   two-line reading the design draws ("September 9, 2026" / "3:00 PM ET")
   without a foundation trap -- fyf_foundation.css has no rule for <span>. */
.fyf-rules-card__date-day,
.fyf-rules-card__date-time { display: block; }
.fyf-rules-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 18px 32px;
  margin-top: 26px;
  padding-top: 22px;
  border-top: var(--stroke-chip) solid rgba(250, 220, 194, 0.28);
}
/* .fyf-rules-footer__text is a <p> -- same margin trap. */
.fyf-rules-footer .fyf-rules-footer__text {
  margin: 0;
  max-width: 52ch;
  font-size: 16px;
  line-height: 1.5;
  color: rgba(250, 220, 194, 0.85);
}
.fyf-rules-footer__text strong { color: var(--fyf-cream); }

/* ── Disclosure accordions ───────────────────────────────────── */
.fyf-section--disclosures { padding-top: 56px; padding-bottom: 56px; }
.fyf-section--disclosures .fyf-section__inner { display: grid; gap: 16px; }
.fyf-disclosure {
  background-color: var(--fyf-white);
  background-image: var(--texture-paper);
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-hard-sm);
  overflow: hidden;
}
.fyf-disclosure__summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 22px;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
.fyf-disclosure__summary::-webkit-details-marker { display: none; }
/* .fyf-disclosure has overflow: hidden, and .fyf-disclosure__summary is its
   first child -- its border box IS the clip boundary, so the UA's default
   focus outline (drawn outside the border box) was clipped away entirely,
   leaving both accordions keyboard-focusable with no visible ring. Negative
   outline-offset draws the ring INSIDE the clipped box instead of outside it. */
.fyf-disclosure__summary:focus-visible {
  outline: var(--stroke-sticker) solid var(--fyf-charcoal);
  outline-offset: -3px;
}
.fyf-disclosure__glyph { color: var(--fyf-pink-deep); }
.fyf-disclosure[open] .fyf-disclosure__glyph::before { content: "–"; font-size: 13px; }
.fyf-disclosure[open] .fyf-disclosure__glyph { font-size: 0; }
.fyf-disclosure__panel {
  border-top: var(--stroke-chip) solid var(--fyf-charcoal-12);
  padding: 16px 22px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--fyf-charcoal-80);
}

/* ── CTA band ────────────────────────────────────────────────── */
.fyf-cta-band {
  background: var(--fyf-yellow);
  padding: clamp(56px, 6vw, 84px) clamp(24px, 4.4vw, 56px);
  border-bottom: var(--stroke-sticker) solid var(--fyf-charcoal);
  text-align: center;
}
/* .fyf-cta-band__subhead and __body are <p>s -- same margin trap as the
   rules cards above. */
.fyf-cta-band .fyf-cta-band__subhead {
  margin: 0;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
.fyf-cta-band .fyf-section__headline { margin-top: 22px; }
.fyf-cta-band .fyf-cta-band__body {
  margin: 14px auto 30px;
  max-width: 44ch;
  font-size: 17px;
}

/* ── CTA (shared by the hero panel and the CTA band) ─────────────
   No margin of its own -- see the comment atop _cta.html.erb. The old
   `<div class="mt-6">` root put a top margin on this half of the hero's
   actions row that the sibling "How it works" button never had, so under
   align-items: flex-start the two buttons never lined up. Each caller now
   supplies its own spacing instead (.fyf-hero__actions's gap; the CTA
   band's .fyf-cta-band__body bottom margin). */
.fyf-cta {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}
/* form_class: (see _cta.html.erb) puts this on the <form> button_to wraps
   around the real <button>, which itself carries the .fyf-btn classes
   directly -- so there is no inner element left to unstyle here. */
.fyf-cta-form { display: inline-block; }

/* Informational CTA states (not-yet-open, in-progress, submitted, sealed)
   used to render as bg-surface/border-border/text-text-dim Tailwind cards --
   the app's own dark editor-theme tokens -- as flex children sitting
   directly on the hero's blue field, which is how review caught them: they
   looked broken there. An opaque paper card reads correctly regardless of
   the field colour behind it, so every one of those states gets one now. */
.fyf-cta-notice { max-width: 420px; text-align: left; }
/* __lead, __aside, and __body are all <p>s -- same margin trap as above. */
.fyf-cta-notice .fyf-cta-notice__lead {
  margin: 0;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 17px;
  color: var(--fyf-charcoal);
}
/* charcoal-60 measured 4.09:1 on the paper card behind it -- short of AA's
   4.5:1 at 13px. This is aside prose ("Consensus has moved since you last
   edited...") -- charcoal-80 clears 7.79:1. */
.fyf-cta-notice .fyf-cta-notice__aside {
  margin: 10px 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--fyf-charcoal-80);
}
.fyf-cta-notice .fyf-cta-notice__body {
  margin: 0;
  font-size: 15px;
  line-height: 1.5;
  color: var(--fyf-charcoal-80);
}
.fyf-cta-notice__links {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-top: 12px;
  font-size: 14px;
}
.fyf-cta-notice .fyf-cta-notice__link {
  color: var(--fyf-pink-deep);
  font-weight: 700;
}

/* ══════════════════════════════════════════════════════════════
   Rules page (contests#rules)
   2026-08-20 fyf-rules-reskin, Task 1. Transcribed section-for-section from
   docs/design/fyf-landing-handoff/design-reference/FrankOpenRules.template.html.
   ══════════════════════════════════════════════════════════════ */

/* ── Hero ─────────────────────────────────────────────────────
   Not .fyf-hero -- the design's rules-page hero is a single intro block, not
   the landing page's two-panel split (no field/artifact grid to resolve).
   Zero bottom padding, no border-bottom: it flows straight into #math, which
   supplies its own top padding and the page's first keyline. */
.fyf-rules-hero {
  padding: clamp(44px, 5vw, 72px) clamp(24px, 4.4vw, 56px) 0;
}
.fyf-rules-hero .fyf-rules-hero__back {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-pink-deep);
  text-decoration: none;
}
.fyf-rules-hero .fyf-rules-hero__back:hover { color: var(--fyf-charcoal); }
/* charcoal-60 measured against the design's own literal value for this line
   (FrankOpenRules.template.html line 515) -- distinct from every
   Marketing::EyebrowComponent field (pink-deep/yellow/charcoal), so it gets
   its own class rather than a fourth field none of this page's other
   eyebrows use. */
.fyf-rules-hero .fyf-rules-hero__meta {
  margin: 26px 0 0;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--fyf-charcoal-60);
}
.fyf-rules-hero .fyf-rules-hero__headline {
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: clamp(40px, 4.4vw, 60px);
  line-height: 1.02;
  letter-spacing: var(--tracking-display);
  margin: 14px 0 0;
}
.fyf-rules-hero .fyf-rules-hero__lede {
  margin: 16px 0 0;
  max-width: 62ch;
  font-size: 18px;
  line-height: 1.55;
  color: var(--fyf-charcoal-80);
}
/* Not in the design's mock -- this page's existing "View results" link
   (shown once the contest has locked or scored), kept alongside the design's
   own back-link treatment rather than dropped. */
.fyf-rules-hero .fyf-rules-hero__actions { margin: 14px 0 0; }

/* ── #math ────────────────────────────────────────────────────
   The design's own vertical rhythm for this section (40-64px) is tighter
   than the generic .fyf-section's --section-y (80px) -- co-applied with
   .fyf-section for the shared border-bottom, this overrides just the
   padding to match. */
.fyf-rules-math {
  padding-top: clamp(40px, 4.4vw, 64px);
  padding-bottom: clamp(40px, 4.4vw, 64px);
}
/* The formula's four terms as a row of paper/color chips connected by
   operator glyphs -- the section's visual anchor per the design, sitting
   above the prose intro and the mono formula box below it. */
.fyf-rules-chips {
  display: flex;
  align-items: center;
  gap: 14px;
  row-gap: 18px;
  flex-wrap: wrap;
  margin: 8px 0 0;
}
.fyf-rules-chip {
  display: grid;
  justify-items: center;
  gap: 4px;
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: var(--radius-md);
  padding: 12px 18px;
}
.fyf-rules-chip--paper {
  background-color: var(--fyf-white);
  background-image: var(--texture-paper);
}
.fyf-rules-chip--blue   { background: var(--fyf-blue); }
.fyf-rules-chip--pink   { background: var(--fyf-pink); }
.fyf-rules-chip--yellow { background: var(--fyf-yellow); box-shadow: var(--shadow-hard-sm); }
.fyf-rules-chip__label {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 22px;
}
.fyf-rules-chip__caption {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
.fyf-rules-chip--paper .fyf-rules-chip__caption { color: var(--fyf-charcoal-60); }
/* Phase-1c fix wave, finding 1: the design's own literal value (charcoal at
   70% opacity) measured 3.09:1 on the blue chip and 2.94:1 on pink -- both
   short of AA's 4.5:1 for 10px/700 text. Standing project rule for this
   handoff (see reference_fyf_handoff_contrast.md): its own alpha values
   fail AA and contradict its own accessibility section, so they get
   dropped rather than transcribed. Solid var(--fyf-charcoal) clears AA on
   blue (4.90:1) and yellow (9.87:1) outright. */
.fyf-rules-chip--blue .fyf-rules-chip__caption,
.fyf-rules-chip--pink .fyf-rules-chip__caption,
.fyf-rules-chip--yellow .fyf-rules-chip__caption {
  color: var(--fyf-charcoal);
}
/* Pink is the one background solid charcoal doesn't clear on its own --
   4.44:1, short of AA-normal's 4.5:1 -- and there is no darker token to
   reach for (charcoal is already the design system's darkest ink; every
   other pink variant measures worse, not better: charcoal-on-pink-deep
   2.90:1, cream-on-pink 2.64:1). Fix round 1 (coordinator ruling): this
   stays at the same 10px as its blue/yellow siblings rather than being
   sized up to clear AA on its own -- a lone 19px caption among three
   otherwise-identical chips read as a rendering bug and made "Scale only"
   the loudest label in the row, inverting the intended emphasis. The
   4.44:1 is carried as a documented exception instead: this whole row is
   aria-hidden (decorative, restated below), and its exact content --
   "Scale only" and every other chip's label/caption pair -- is restated
   twice in fully AA-compliant type: once in the prose immediately below
   (.fyf-rules-math__intro) and again in the mono formula box
   (.fyf-rules-formula__lines, "S only sets the scale of the number..."). */
.fyf-rules-chip-op {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 26px;
}
.fyf-rules-math .fyf-rules-math__intro {
  margin: 22px 0 0;
  max-width: 70ch;
  font-size: 16px;
  line-height: 1.55;
  color: var(--fyf-charcoal-80);
}
/* The formula card: white paper stock, grid-paper texture, hard charcoal
   keyline+shadow -- the "light paper treatment" the brief calls for in place
   of the old dark-mode <pre> block. */
.fyf-rules-formula {
  margin: 30px 0 0;
  overflow-x: auto;
  background-color: var(--fyf-white);
  background-image: var(--texture-grid-paper);
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-hard);
  padding: 24px 26px;
}
.fyf-rules-formula__lines {
  font-family: var(--font-mono);
  font-size: 14px;
  line-height: 1.9;
}
/* white-space: pre lives on each LINE, not the .fyf-rules-formula__lines
   container -- each line is its own presenter-rendered <div>, and ERB's own
   indentation/newlines between those closing/opening tags are themselves
   text-node children of the container. A `pre` on the container preserved
   that template whitespace as literal blank rendered lines (measured on the
   live page: the four formula lines rendered triple-spaced). Scoped to the
   line instead, `pre` only has to protect the padding spaces inside each
   line's own text (see rules.html.erb -- each label is right-padded to
   align every "=" into one column, matching the design's own layout) and
   stop that line from word-wrapping; the container's inter-tag whitespace
   goes back to normal HTML collapsing. */
.fyf-rules-formula__line { white-space: pre; }
.fyf-rules-formula__line--total { font-weight: 700; }
.fyf-rules-formula .fyf-rules-formula__note {
  margin: 14px 0 0;
  padding-top: 12px;
  border-top: var(--stroke-chip) solid var(--fyf-charcoal-12);
  font-size: 13px;
  line-height: 1.5;
  color: var(--fyf-charcoal-60);
}

/* ── #params (the charcoal inverse band) ─────────────────────── */
.fyf-rules-params__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 22px;
  margin: 40px 0 0;
}
.fyf-rules-params__card {
  border: var(--stroke-sticker) solid var(--fyf-cream);
  border-radius: var(--radius-lg);
  padding: 22px 24px;
}
.fyf-rules-params__label {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: rgba(250, 220, 194, 0.65);
}
.fyf-rules-params__value {
  margin-top: 6px;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 38px;
}
/* Missed-week charge and Scoring format read as short prose ("50% of
   replacement", "Half PPR Redraft"), not a bare numeral -- the design's own
   downshift for the one long value it has (Missed-week charge). */
.fyf-rules-params__value--sm { font-size: 28px; margin-top: 10px; }
.fyf-rules-params__value--mono {
  font-family: var(--font-mono);
  font-size: 20px;
  margin-top: 12px;
  word-break: break-all;
}
.fyf-rules-params__list {
  display: grid;
  gap: 16px;
  margin: 36px 0 0;
  padding: 0;
  max-width: 78ch;
  list-style: none;
}
.fyf-rules-params__list-item {
  display: grid;
  grid-template-columns: 48px 1fr;
  gap: 8px;
  align-items: baseline;
  font-size: 16px;
  line-height: 1.55;
  color: rgba(250, 220, 194, 0.9);
}
.fyf-rules-params__list-num {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 16px;
  color: var(--fyf-yellow);
}
.fyf-rules-params .fyf-rules-params__list-item strong { color: var(--fyf-cream); }

/* _availability + _fine_print: real disclosures the design's mock has no
   literal spot for (see those partials' own comments), given a home here
   rather than dropped, styled to read on this section's charcoal field. */
/* The availability card's engine id, beneath the rule-in-words value: the
   thing the digest names, kept legible but no longer the headline figure.
   Same break-anywhere as --mono so the id can't widen a 390px card. */
.fyf-rules-params__card-id {
  margin-top: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: rgba(250, 220, 194, 0.6);
  overflow-wrap: anywhere;
  word-break: break-all;
}

/* 2026-08-21: the .fyf-rules-params__disclosures / __disclosure-title /
   __disclosure-body rules are gone with the block they styled (the
   "What counts as played" and fine-print disclosures -- both folded into
   the cards and numbered list above them). */
/* 2026-08-21: the .fyf-rules-params__fine-print* and __footnote rules that
   lived here are gone with the markup they styled -- the fine print
   partial was deleted (its scoring items are numbered list items 04/05
   now) and both footnote paragraphs were removed. */

/* ── CTA band actions row ─────────────────────────────────────
   The design's rules-page band holds two buttons (Enter the Open / Back to
   the Open) side by side, unlike the landing page's single-button band --
   .fyf-cta itself stays inline-flex/flex-start (correct for the hero's
   left-aligned use), so this wraps both children in a centered row instead
   of restyling that shared class. */
.fyf-rules-cta__row {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  justify-content: center;
  margin: 26px 0 0;
}

/* ── #example ─────────────────────────────────────────────────
   Ten-slot comparison, per-board score/regroup tables, and final standings.
   Real <table> markup throughout (not the design's own div-grid rows) --
   more accessible for genuinely tabular data, and it lets every table here
   reuse Marketing::PaperCardComponent for its paper/keyline/shadow shell
   instead of hand-rolling that per table. */
.fyf-rules-example .fyf-rules-example__intro {
  margin: 14px 0 0;
  max-width: 70ch;
  font-size: 16px;
  line-height: 1.55;
  color: var(--fyf-charcoal-80);
}
.fyf-rules-example .fyf-rules-example__table-wrap {
  margin: 34px 0 0;
  overflow-x: auto;
  overflow-y: hidden;
}
.fyf-rules-example__table { width: 100%; border-collapse: collapse; font-size: 14px; }
.fyf-rules-example__table thead tr { border-bottom: var(--stroke-chip) solid var(--fyf-charcoal); }
.fyf-rules-example__table th {
  padding: 11px 16px;
  text-align: left;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-charcoal-60);
  white-space: nowrap;
}
.fyf-rules-example__table-head--yellow { color: var(--fyf-yellow-deep); }
.fyf-rules-example__table-head--pink { color: var(--fyf-pink-deep); }
.fyf-rules-example__table td {
  padding: 9px 16px;
  vertical-align: middle;
}
.fyf-rules-example__table tbody tr:nth-child(even) { background: var(--fyf-cream-tint); }
.fyf-rules-example__table tbody tr:not(:last-child) { border-bottom: var(--stroke-hairline) solid var(--fyf-charcoal-12); }
.fyf-rules-example__table tfoot td {
  padding: 11px 16px;
  border-top: var(--stroke-chip) solid var(--fyf-charcoal);
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
/* A real <caption> rather than a second heading -- renders above the table
   by default, reads as the card's own sub-header the way the design's own
   "Same math, grouped by player · contribution = ..." line does. */
.fyf-rules-example__table-caption {
  caption-side: top;
  text-align: left;
  padding: 14px 16px 0;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-charcoal-60);
}
.fyf-rules-example__num { text-align: right; }
/* Finding I1 (fix round 1): .fyf-rules-example__table th sets text-align:
   left as class+element (0,1,1), which outranks the bare .fyf-rules-
   example__num class (0,1,0) on every header cell -- the numeric column
   headers were left-aligned while their own body cells right-aligned
   underneath them. th.__num at (0,2,1) beats it. */
.fyf-rules-example__table th.fyf-rules-example__num { text-align: right; }
.fyf-rules-example__mono { font-family: var(--font-mono); font-size: 13px; }
.fyf-rules-example__muted { color: var(--fyf-charcoal-60); }
.fyf-rules-example__value--pos { color: var(--fyf-green); font-weight: 700; }
.fyf-rules-example__value--neg { color: var(--fyf-pink-deep); font-weight: 700; }
/* The slot table's two entry columns bold whenever that board differs from
   consensus at this rank -- computed in the view from the same three
   fields this table already renders (row[:consensus]/[:careful]/[:bold]),
   not a fourth presenter field. */
.fyf-rules-example__cell--moved { font-weight: 700; }
/* A pick is one line: pill · name · season value (slot tables only), as
   the design's "Ellison 8.2". Three grid tracks rather than inline-flex
   wrap: the flex version broke each row wherever it ran out of room (pill
   alone on a line here, name+value there), so no two rows matched. Pill
   and value are auto tracks; the name track is minmax(0, 1fr) so a long
   name (Marvin Harrison Jr.) wraps under itself when squeezed, never
   truncates, and never forces the table wider (see .fyf-board__name for
   the same no-truncation rule). At full width nothing wraps. */
.fyf-rules-example__player {
  display: inline-grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  column-gap: 8px;
  align-items: baseline;
}
.fyf-rules-example__name { font-size: 15px; }
.fyf-rules-example .fyf-rules-example__caption {
  margin: 12px 0 0;
  max-width: 70ch;
  font-size: 14px;
  line-height: 1.5;
  color: var(--fyf-charcoal-60);
}
.fyf-rules-example .fyf-rules-example__board-head {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin: 48px 0 0;
}
.fyf-rules-example .fyf-rules-example__board-title {
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: 32px;
  line-height: 1.05;
  margin: 0;
}
.fyf-rules-example__board-badge {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  padding: 4px 14px;
}
.fyf-rules-example__board-badge--yellow { background: var(--fyf-yellow); }
.fyf-rules-example__board-badge--pink { background: var(--fyf-pink); }
/* min(420px, 100%): the design's own card-pair grid floors each card at
   420px, which is wider than a 390px phone viewport -- min() lets a card
   shrink to fill the single column it drops to there instead of forcing
   the page to scroll sideways. Same pattern as line ~460's hero grid. */
/* One table per row, not the design's side-by-side pair: the mock's picks
   were "Ellison 8.2" -- a short fictional surname and a one-digit value.
   Ours carry a position pill, a real name (Marvin Harrison Jr.) and a
   three-digit value, and two of those tables abreast at ~560px each
   wrapped every pick mid-name. Stacked, each gets the full measure and a
   pick reads on one line. */
.fyf-rules-example .fyf-rules-example__board-tables {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 26px;
  margin: 22px 0 0;
  align-items: start;
}
.fyf-rules-example .fyf-rules-example__board-tables .fyf-rules-example__table-wrap { margin: 0; }
.fyf-rules-example .fyf-rules-example__standings-title {
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: 32px;
  line-height: 1.05;
  margin: 52px 0 0;
}
.fyf-rules-example .fyf-rules-example__standings {
  display: grid;
  gap: 14px;
  margin: 22px 0 0;
  max-width: 820px;
}
.fyf-rules-example__standing {
  display: grid;
  grid-template-columns: 28px 96px 1fr auto;
  align-items: center;
  gap: 14px;
}
.fyf-rules-example__standing-rank {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 18px;
}
.fyf-rules-example__standing-label {
  font-family: var(--font-header);
  font-size: 22px;
}
.fyf-rules-example__standing-track { height: 26px; }
.fyf-rules-example__standing-bar {
  display: block;
  height: 100%;
  min-width: 4px;
  border: var(--stroke-chip) solid var(--fyf-charcoal);
}
.fyf-rules-example__standing-bar--pink { background: var(--fyf-pink); }
.fyf-rules-example__standing-bar--yellow { background: var(--fyf-yellow); }
.fyf-rules-example__standing-tick {
  display: block;
  height: 100%;
  width: 2px;
  border-left: var(--stroke-chip) solid var(--fyf-charcoal);
}
.fyf-rules-example__standing-value {
  font-weight: 700;
  font-size: 16px;
  text-align: right;
}
.fyf-rules-example .fyf-rules-example__standings-note {
  margin: 18px 0 0;
  max-width: 70ch;
  font-size: 15px;
  line-height: 1.5;
  color: var(--fyf-charcoal-80);
}

/* ── #worth ───────────────────────────────────────────────────
   Per-season value-archive receipts. Same paper-card/table vocabulary as
   #example above; the one thing this section needs that #example doesn't
   is the digest wrap fix (finding 3, phase-1c fix wave) below. */
.fyf-rules-worth .fyf-rules-worth__intro {
  margin: 16px 0 0;
  max-width: 72ch;
  font-size: 16px;
  line-height: 1.55;
  color: var(--fyf-charcoal-80);
}
.fyf-rules-worth .fyf-rules-worth__intro--dim {
  margin-top: 12px;
  font-size: 14px;
  color: var(--fyf-charcoal-60);
}
.fyf-rules-worth__seasons {
  display: grid;
  gap: 30px;
  margin: 36px 0 0;
}
.fyf-rules-worth__season { overflow: hidden; }
.fyf-rules-worth__season-head {
  display: flex;
  align-items: baseline;
  gap: 16px;
  flex-wrap: wrap;
  padding: 18px 22px 0;
}
.fyf-rules-worth .fyf-rules-worth__season-title {
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: 30px;
  margin: 0;
}
.fyf-rules-worth__season-meta {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-charcoal-60);
}
.fyf-rules-worth__table-wrap {
  margin: 14px 0 0;
  overflow-x: auto;
  overflow-y: hidden;
}
.fyf-rules-worth__table { width: 100%; border-collapse: collapse; font-size: 14px; }
.fyf-rules-worth__table thead tr { border-bottom: var(--stroke-chip) solid var(--fyf-charcoal); }
.fyf-rules-worth__table th {
  padding: 10px 22px;
  text-align: left;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-charcoal-60);
  white-space: nowrap;
}
.fyf-rules-worth__table td {
  padding: 8px 22px;
  vertical-align: middle;
}
/* Zebra is a server-side class (_worth_player_row), not tbody
   tr:nth-child(even): every player row is followed by a hidden log row that
   is a real <tr>, so nth-child would count it and stripe two visible rows
   in a row. Hairlines sit on the player rows only -- the log row carries
   its own top rule below. */
.fyf-rules-worth__row--alt { background: var(--fyf-cream-tint); }
.fyf-rules-worth__row:not(:last-child) { border-bottom: var(--stroke-hairline) solid var(--fyf-charcoal-12); }
.fyf-rules-worth__name { font-family: var(--font-header); font-size: 18px; }
.fyf-rules-worth__num { text-align: right; }
/* Same fix as .fyf-rules-example__table th.__num above -- .fyf-rules-
   worth__table th (0,1,1) otherwise beats the bare .fyf-rules-worth__num
   class (0,1,0) on header cells. */
.fyf-rules-worth__table th.fyf-rules-worth__num { text-align: right; }
.fyf-rules-worth__mono { font-family: var(--font-mono); font-size: 13px; }
.fyf-rules-worth__muted { color: var(--fyf-charcoal-60); }
.fyf-rules-worth__value--pos { color: var(--fyf-green); font-weight: 700; }
.fyf-rules-worth__value--neg { color: var(--fyf-pink-deep); font-weight: 700; }
.fyf-rules-worth__details { border-top: var(--stroke-chip) solid var(--fyf-charcoal-12); }
.fyf-rules-worth__summary {
  cursor: pointer;
  list-style: none;
  padding: 12px 22px;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-pink-deep);
}
.fyf-rules-worth__summary::-webkit-details-marker { display: none; }
/* Fix round 1, M4: same trap and same fix as .fyf-disclosure__summary
   above -- .fyf-rules-worth__season (the PaperCardComponent wrapper) has
   overflow: hidden, and this summary is inside it, so the UA's default
   focus outline (drawn outside the border box) was clipped away entirely.
   Negative outline-offset draws the ring inside the clipped box instead. */
.fyf-rules-worth__summary:focus-visible {
  outline: var(--stroke-sticker) solid var(--fyf-charcoal);
  outline-offset: -3px;
}
.fyf-rules-worth__details .fyf-rules-worth__table-wrap { margin: 0 0 14px; }
.fyf-rules-worth .fyf-rules-worth__fine-print {
  margin: 0;
  padding: 12px 22px;
  border-top: var(--stroke-chip) solid var(--fyf-charcoal-12);
  font-size: 12px;
  line-height: 1.7;
  color: var(--fyf-charcoal-60);
}
.fyf-rules-worth__repl-point { margin-right: 12px; }
/* The fix for finding 3 (phase-1c fix wave): a committed archive's digest
   is a 64-char unbroken hex string. Rendered as one mono token with no
   break opportunity, it was wide enough on its own to force a 502px
   horizontal scroll at a 390px viewport -- outside any of this section's
   overflow-x wrappers, that width propagated straight to the page. Same
   fix .fyf-rules-params__value--mono already uses for the availability-rule
   id in #params: break the token in place instead of letting it push
   anything wider than its container. */
.fyf-rules-worth__digest { overflow-wrap: anywhere; word-break: break-all; }

/* 2026-08-21 receipts polish. Flat card: .fyf-card--paper's --texture-paper
   hairlines read as graph paper behind 150 rows of mono numerals, and the
   zebra tint only showed it on every other row. Keyline, shadow and radius
   stay; only the texture goes, and only on these cards. */
.fyf-rules-worth .fyf-rules-worth__season { background-image: none; }

/* Season tab strip -- one season on screen at a time. Same vocabulary as
   the board badges in #example (subhead caps, chip keyline, pill radius,
   yellow fill for the selected one). */
.fyf-rules-worth__tabs {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.fyf-rules-worth__tab {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  padding: 8px 18px;
  border: var(--stroke-chip) solid var(--fyf-charcoal);
  border-radius: var(--radius-pill);
  background: var(--fyf-white);
  color: var(--fyf-charcoal);
  cursor: pointer;
}
.fyf-rules-worth__tab:hover { background: var(--fyf-cream-tint); }
.fyf-rules-worth__tab[aria-selected="true"] { background: var(--fyf-yellow); }
.fyf-rules-worth__tab:focus-visible {
  outline: var(--stroke-sticker) solid var(--fyf-charcoal);
  outline-offset: 2px;
}

/* Per-row "Weeks" toggle and the log row it reveals. */
.fyf-rules-worth__log-toggle {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  padding: 4px 10px;
  border: var(--stroke-chip) solid var(--fyf-charcoal-12);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--fyf-pink-deep);
  cursor: pointer;
  white-space: nowrap;
}
.fyf-rules-worth__log-toggle:hover { border-color: var(--fyf-charcoal); }
.fyf-rules-worth__log-toggle:focus-visible {
  outline: var(--stroke-sticker) solid var(--fyf-charcoal);
  outline-offset: 2px;
}
.fyf-rules-worth__log-chevron { display: inline-block; transition: transform 120ms ease; }
.fyf-rules-worth__row--open .fyf-rules-worth__log-chevron { transform: rotate(180deg); }
.fyf-rules-worth__log-row > td {
  padding: 0 22px 16px;
  background: var(--fyf-cream-tint);
  border-bottom: var(--stroke-hairline) solid var(--fyf-charcoal-12);
}
.fyf-rules-worth__log-loading { margin: 12px 0 0; font-size: 13px; }

/* The log itself (_value_log), four parts: the how-it-works block, the
   weeks-across grid (>= 900px), the weeks-down table (< 900px), and the
   Y equation. */
.fyf-rules-worth__log { padding-top: 14px; display: grid; gap: 14px; }

/* 1. How the first played week became its value: a title on the real
   numbers, three numbered steps (plain-words claim, the arithmetic in
   mono, a muted one-line gloss), then the formula as a single muted
   footnote. Left rule in the section's pink so it reads as an aside, not a
   fourth table. Capped at a comfortable measure -- it is prose. */
.fyf-rules-worth__log-how {
  padding: 10px 14px 10px 16px;
  border-left: var(--stroke-chip) solid var(--fyf-pink);
  max-width: 78ch;
}
.fyf-rules-worth .fyf-rules-worth__log-how-title {
  margin: 0 0 10px;
  font-family: var(--font-header);
  font-size: 20px;
  line-height: 1.2;
}
.fyf-rules-worth .fyf-rules-worth__log-how-title .fyf-rules-worth__muted {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  margin-left: 6px;
}
.fyf-rules-worth .fyf-rules-worth__log-steps {
  margin: 0;
  padding: 0 0 0 22px;
  /* Explicit: the foundation resets list markers, and the numbers are the
     point -- three steps, in order. */
  list-style: decimal;
  display: grid;
  gap: 8px;
  font-size: 13px;
  line-height: 1.5;
}
.fyf-rules-worth__log-step::marker {
  font-family: var(--font-subhead);
  font-weight: 700;
  color: var(--fyf-pink-deep);
}
.fyf-rules-worth__log-step { padding-left: 4px; }
.fyf-rules-worth__log-step-head { font-weight: 700; }
/* The arithmetic on its own line under every step -- step 1's longer
   claim wrapped it there anyway, and three steps should read the same. */
.fyf-rules-worth__log-step-math { display: block; margin: 2px 0 0; white-space: nowrap; }
.fyf-rules-worth__log-step .fyf-rules-worth__muted { display: block; font-size: 12px; }
.fyf-rules-worth .fyf-rules-worth__log-how-math {
  margin: 10px 0 0;
  font-size: 12px;
  line-height: 1.6;
  overflow-wrap: anywhere;
}
.fyf-rules-worth__log-how-math sup { font-size: 0.75em; }

/* Every selector below is prefixed with .fyf-rules-worth__log: these tables
   sit inside a .fyf-rules-worth__table cell, and that table's own th/td
   rules (0,1,1) would otherwise outrank a bare block class (0,1,0) --
   uppercase row labels and 22px cell padding leaked in that way. */
/* 2. Weeks across. Real <table> markup laid out as a GRID, not a nested
   table: this lives in a <td> spanning the whole #worth table, and
   anything with an intrinsic width there (a nested auto-layout table, a
   flex strip -- the first cut) widens the TABLE, not the cell, and slid
   the Log column out past the card edge the moment one row opened.
   minmax(0, 1fr) tracks contribute no min-content width; the one
   max-content track is the short row-label column. --log-weeks is the
   season's graded-games count, set inline by the partial. */
.fyf-rules-worth__log .fyf-rules-worth__log-grid {
  display: grid;
  grid-template-columns: max-content repeat(var(--log-weeks, 18), minmax(0, 1fr));
  column-gap: 2px;
  border-collapse: collapse;
  font-size: 12px;
}
.fyf-rules-worth__log .fyf-rules-worth__log-grid thead,
.fyf-rules-worth__log .fyf-rules-worth__log-grid tbody,
.fyf-rules-worth__log .fyf-rules-worth__log-grid tr { display: contents; }
.fyf-rules-worth__log .fyf-rules-worth__log-th,
.fyf-rules-worth__log .fyf-rules-worth__log-td,
.fyf-rules-worth__log .fyf-rules-worth__log-grid .fyf-rules-worth__log-rh {
  min-width: 0;
  padding: 5px 2px;
  text-align: center;
  border-bottom: var(--stroke-hairline) solid var(--fyf-charcoal-12);
  white-space: nowrap;
}
.fyf-rules-worth__log .fyf-rules-worth__log-th,
.fyf-rules-worth__log .fyf-rules-worth__log-rh {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-charcoal-60);
}
.fyf-rules-worth__log .fyf-rules-worth__log-th { border-bottom: var(--stroke-chip) solid var(--fyf-charcoal); }
/* Row labels keep case: "h(points)" is a formula name, and H(POINTS) is
   not the thing the curve line above calls it. */
.fyf-rules-worth__log .fyf-rules-worth__log-rh { text-transform: none; letter-spacing: 0.02em; font-size: 11px; }
.fyf-rules-worth__log .fyf-rules-worth__log-th--out { color: var(--fyf-charcoal-60); opacity: 0.55; }
.fyf-rules-worth__log .fyf-rules-worth__log-rh,
.fyf-rules-worth__log .fyf-rules-worth__log-th--label { text-align: left; padding-right: 10px; }
.fyf-rules-worth__log .fyf-rules-worth__log-grid tbody tr:last-child .fyf-rules-worth__log-td,
.fyf-rules-worth__log .fyf-rules-worth__log-grid tbody tr:last-child .fyf-rules-worth__log-rh { border-bottom: 0; }

/* 3. Weeks down: an ordinary table, phone only. */
.fyf-rules-worth__log .fyf-rules-worth__log-list { width: 100%; max-width: 420px; border-collapse: collapse; font-size: 13px; }
.fyf-rules-worth__log .fyf-rules-worth__log-list th,
.fyf-rules-worth__log .fyf-rules-worth__log-list td { padding: 5px 8px; text-align: left; }
.fyf-rules-worth__log .fyf-rules-worth__log-list thead th {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-charcoal-60);
  border-bottom: var(--stroke-chip) solid var(--fyf-charcoal);
}
.fyf-rules-worth__log .fyf-rules-worth__log-list th.fyf-rules-worth__num,
.fyf-rules-worth__log .fyf-rules-worth__log-list td.fyf-rules-worth__num { text-align: right; }
.fyf-rules-worth__log .fyf-rules-worth__log-tr { border-bottom: var(--stroke-hairline) solid var(--fyf-charcoal-12); }
.fyf-rules-worth__log .fyf-rules-worth__log-tr--out { opacity: 0.6; }
@media (min-width: 900px) { .fyf-rules-worth__log-list { display: none; } }
@media (max-width: 899px) { .fyf-rules-worth__log-grid { display: none; } }

/* 4. Y = played - charge. */
.fyf-rules-worth .fyf-rules-worth__log-total {
  margin: 0;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: baseline;
  font-size: 14px;
  line-height: 1.5;
}
.fyf-rules-worth__log-total-label {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
.fyf-rules-worth .fyf-rules-worth__log-other {
  margin: 0;
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
  font-size: 13px;
  line-height: 1.5;
}
/* Phone: the term column has no room beside a formula. */
@media (max-width: 560px) {
  .fyf-rules-worth__log-how-line { grid-template-columns: 1fr; gap: 2px; }
}

/* ── Published rules, verbatim ───────────────────────────────── */
.fyf-rules-verbatim .fyf-rules-verbatim__body {
  margin: 22px 0 0;
  max-width: 78ch;
  padding: 24px 26px;
  background-color: var(--fyf-white);
  background-image: var(--texture-paper);
  border: var(--stroke-sticker) solid var(--fyf-charcoal);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-hard);
  font-size: 15px;
  line-height: 1.6;
  color: var(--fyf-charcoal-80);
}
.fyf-rules-verbatim .fyf-rules-verbatim__body p:last-child { margin-bottom: 0; }

/* ── Home hero (fyf-home-*) ───────────────────────────────────
   The fRank front door's own hero -- namespaced separately from the
   contest page's .fyf-hero/.fyf-board (rather than reusing those classes)
   so the two pages' hero styling can evolve independently; this one only
   happens to look similar because both are the same blue-field/paper-card
   pattern from the same design system. See
   docs/design/fyf-landing-handoff/design-reference/FrankLanding.template.html
   for the source (search "Own your fantasy rankings"). */
.fyf-home-hero {
  display: grid;
  /* Same auto-fit/minmax(min(500px,100%),1fr) the contest hero uses (see
     .fyf-hero above) -- proven to stack cleanly on narrow/emulated
     viewports without a fixed breakpoint fighting the grid's own math. */
  grid-template-columns: repeat(auto-fit, minmax(min(500px, 100%), 1fr));
  border-bottom: var(--stroke-sticker) solid var(--fyf-charcoal);
  overflow: hidden;
}
/* Pink colorway (user-directed, 2026-08-21) -- the mock's own variant map
   pairs field: --fyf-pink with a yellow spice word; everything else in the
   hero keeps its authored values. Soft pink, not the full token: the full
   --fyf-pink read "blinding" at this size (user), and the soft variant
   takes charcoal text from a 4.44:1 exception to a 5.54:1 AA pass. */
.fyf-home-hero__panel--field {
  background: var(--fyf-pink-soft);
  padding: clamp(44px, 5vw, 76px) clamp(24px, 4.4vw, 64px);
  display: grid;
  align-content: center;
}
.fyf-home-hero__panel--board {
  /* No display:grid/align-content/justify-items here (unlike the field
     panel opposite) -- the mock's own right panel is a plain padded block
     (FrankLanding.template.html:512), and centering it as a grid item
     shrank the card to its own fit-content width instead of letting it
     fill the panel (fixed below via .fyf-home-board's own width: 100%)
     and pushed it ~99px from the top instead of sitting right under the
     56px padding-top the mock specifies. */
  position: relative;
  overflow: hidden;
  background: var(--fyf-cream-tint);
  border-left: var(--stroke-sticker) solid var(--fyf-charcoal);
  padding: 56px clamp(20px, 2.6vw, 38px) 0;
  min-height: 680px;
}
/* Stacked, the left border would draw across the top of the page -- same
   fix .fyf-hero__panel--artifact uses above, same breakpoint. */
@media (max-width: 1080px) {
  .fyf-home-hero__panel--board {
    border-left: 0;
    border-top: var(--stroke-sticker) solid var(--fyf-charcoal);
  }
}

/* Double-classed against the .fyf-home-hero ancestor, not left bare, for
   the same reason .fyf-hero's own headline/lede rules are (see the comment
   above .fyf-hero .fyf-hero__headline): fyf_foundation.css's `.fyf h1{margin:0}`
   and `.fyf p{margin:0 0 1em}` are class+element selectors (0,1,1), which
   outrank a bare one-class selector (0,1,0) regardless of source order. Two
   classes (0,2,0) wins without hardcoding the tag name. */
.fyf-home-hero .fyf-home-hero__headline {
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: clamp(38px, 4.4vw, 60px);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  color: var(--fyf-charcoal);
  margin: 16px 0 0;
}
/* Full-opacity charcoal, not the handoff's rgba(48,32,45,.8) -- the alpha
   fails AA on any brand field (the app-wide precedent). On the soft-pink
   field, solid charcoal measures 5.54:1 -- a clean AA pass at 17px (the
   full --fyf-pink only reached 4.44:1, which is part of why the field
   uses the soft token). */
.fyf-home-hero .fyf-home-hero__lede {
  margin: 16px 0 0;
  max-width: 46ch;
  font-size: 17px;
  line-height: 1.5;
  color: var(--fyf-charcoal);
}
.fyf-home-hero .fyf-spice--hero { margin-top: 30px; }
.fyf-home-hero .fyf-home-hero__subhead {
  margin: 26px 0 0;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-charcoal);
}
.fyf-home-hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 34px;
}

/* The mock's card wrapper is width:100% of the panel's content box
   (FrankLanding.template.html:513) -- belt-and-braces protection against a
   future change of the panel's display model. A block box normally stretches
   to fill its plain (non-grid/flex) container without an explicit width, but
   this declaration ensures the fill holds regardless of what the panel's own
   display value becomes. Not currently load-bearing. */
.fyf-home-board { width: 100%; }

/* ── Home hero board ──────────────────────────────────────────
   The illustrative product board -- a paper card with a # / Pos / Player /
   Your note grid, distinct from .fyf-board above (that one is the swap-demo
   card: rank/pill/name/delta-chip, no note column, no column-head row). */
.fyf-home-board__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 15px 20px;
  background: var(--fyf-charcoal);
  color: var(--fyf-cream);
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  white-space: nowrap;
  /* No overflow: hidden on .fyf-home-board -- the two tape strips hang off
     its top edge via negative top offsets (PaperCardComponent#with_tape),
     same as .fyf-board above, and clipping the card would clip them too.
     The head bar rounds its own top corners to match the card's instead --
     -4px so the corner nests just inside the card's 3px charcoal border
     rather than fighting it for the same radius. Same trade-off as
     .fyf-board__head, same fix. */
  border-radius: calc(var(--radius-lg) - 4px) calc(var(--radius-lg) - 4px) 0 0;
}
.fyf-home-board__hint { color: var(--fyf-yellow); }

.fyf-home-board__columns,
.fyf-home-board__row {
  display: grid;
  grid-template-columns: 32px 44px auto minmax(0, 1fr);
  align-items: center;
  gap: 14px;
  padding: 11px 20px;
}
.fyf-home-board__columns {
  padding: 12px 20px;
  background: var(--fyf-cream-tint);
  border-bottom: var(--stroke-chip) solid var(--fyf-charcoal-12);
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-charcoal-60);
}
.fyf-home-board__columns span:nth-child(2) { text-align: center; }
.fyf-home-board__columns span:last-child { text-align: right; }

/* Zebra striping follows DOM order (rows AND the interleaved tier-break
   div, counted together), which is exactly how the mock's own shaded rows
   land on ranks 2/5/7 rather than a clean odd/even split -- no per-row
   modifier class needed, :nth-child(even) already lands in the right
   places once the tier break shifts everything after it by one.
   Border only, no background fill: an opaque --fyf-cream-tint fill here
   would flatten .fyf-home-board's own --texture-paper on alternating rows
   exactly the way .fyf-board__row:nth-child(even)'s own comment above
   documents and deliberately avoids -- same fix, same reasoning, carried
   over rather than reintroduced. */
.fyf-home-board__row:nth-child(even) {
  border-top: var(--stroke-chip) solid var(--fyf-charcoal-12);
  border-bottom: var(--stroke-chip) solid var(--fyf-charcoal-12);
}
.fyf-home-board__rank {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 21px;
  color: var(--fyf-charcoal);
}
/* No white-space: nowrap, unlike the mock's own inline style -- this
   project never truncates a player name (see .fyf-board__name's own
   comment above); a long enough name wraps instead of overflowing its
   fixed-width column. */
.fyf-home-board__name {
  font-family: var(--font-header);
  font-size: 22px;
  line-height: 1.1;
  color: var(--fyf-charcoal);
}
.fyf-home-board__team {
  margin-left: 6px;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-label);
  color: var(--fyf-charcoal-60);
}
.fyf-home-board__note {
  font-size: 13px;
  text-align: right;
  color: var(--fyf-charcoal-80);
}

.fyf-home-board__tier {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 20px;
  background: var(--fyf-charcoal);
  color: var(--fyf-cream);
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  white-space: nowrap;
}
.fyf-home-board__tier-rule {
  flex: 1;
  height: 2px;
  background: var(--texture-chalk-hash);
  opacity: 0.8;
}
.fyf-home-board__tier-hint { color: var(--fyf-yellow); }

@media (max-width: 640px) {
  .fyf-home-board__head,
  .fyf-home-board__tier { white-space: normal; }
}

/* ── Home workflow cards (fyf-home-workflow-*) ───────────────────
   The #workflow section itself reuses the shared .fyf-section/.fyf-cards/
   PaperCardComponent vocabulary the contest pages already established (see
   .fyf-step-card above) -- only the numbered-card internals are new. The
   mock stacks the number above the title (34px num, 8px gap, 32px title,
   12px gap, 16px body) rather than the contest steps' side-by-side
   num+title row, so reusing .fyf-step-card's own classes would have meant
   fighting its layout instead of matching the mock. See
   FrankLanding.template.html, search "Drag, tier, tag". */
.fyf-home-workflow__card { padding: 32px 28px 30px; }
.fyf-home-workflow__num {
  display: block;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 34px;
  letter-spacing: 0.02em;
  color: var(--fyf-pink);
}
/* h3 -- same margin/font-shorthand trap .fyf-step-card__title above routes
   around: fyf_foundation.css's `.fyf h3{...margin:0}` is class+element
   (0,1,1), a bare single class (0,1,0) loses on every property they share.
   Two classes via the .fyf-section ancestor (0,2,0) wins outright. */
.fyf-section .fyf-home-workflow__title {
  font-family: var(--font-header);
  font-weight: var(--weight-display);
  font-size: 32px;
  line-height: 1.05;
  margin: 8px 0 0;
}
/* p -- same margin trap as .fyf-step-card__body above. */
.fyf-section .fyf-home-workflow__body {
  margin: 12px 0 0;
  font-size: 16px;
  line-height: 1.5;
  color: var(--fyf-charcoal-80);
}

/* ── Home fit band (fyf-home-fit-*) ──────────────────────────────
   The #fit band itself is .fyf-section--inverse (shared inverse-band
   vocabulary, see _guarantees.html.erb) with PaperCardComponent's own
   :green/:outline_cream tones supplying the two cards' fill/border/shadow.
   Only the numbered/dash list internals are new -- .fyf-rules-cards is a
   flat 3-up row, not this 2-up numbered-vs-dash layout. See
   FrankLanding.template.html, search "Built for the ranker". */
.fyf-home-fit__grid {
  display: grid;
  /* minmax(min(360px, 100%), 1fr), not a bare 360px floor -- the same fix
     .fyf-home-hero's own grid-template-columns comment documents: a bare
     360px minimum can't shrink below that width at narrow viewports, and
     since this section is .fyf-section--inverse (overflow: hidden), the
     result wasn't a page scrollbar but silent clipping -- cards cut off at
     320/390px with no visible sign anything was wrong. Confirmed by a
     headless-Chrome pass at 320px before this fix: the section reported no
     document-level horizontal overflow, but each card's own
     getBoundingClientRect() extended well past the viewport's right edge. */
  grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr));
  gap: 28px;
  margin-top: 48px;
}
.fyf-home-fit__label {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--fyf-cream);
}
/* The handoff's own alpha here is rgba(250,220,194,.62) -- unlike the same
   handoff's alpha values measured elsewhere in this file (all on lighter
   fields: blue/green/pink, all short of AA), this one sits on solid
   charcoal, where blending toward a DARK background barely dims a light
   foreground: measured ~5.43:1, clearing AA's 4.5:1 for 13px/700 text with
   room to spare. Standing project rule (reference_fyf_handoff_contrast.md)
   is to measure rather than assume the handoff's own alphas are safe --
   this one is, so it is kept rather than dropped to full opacity. */
.fyf-home-fit__label--muted { color: rgba(250, 220, 194, 0.62); }
.fyf-home-fit__list {
  display: grid;
  gap: 16px;
  margin: 22px 0 0;
  padding: 0;
  list-style: none;
}
.fyf-home-fit__item {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  align-items: baseline;
  gap: 6px;
}
.fyf-home-fit__item--dash { grid-template-columns: 34px minmax(0, 1fr); }
.fyf-home-fit__num {
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 16px;
  color: var(--fyf-yellow);
}
.fyf-home-fit__dash {
  display: block;
  color: var(--fyf-pink);
  font-size: 22px;
  line-height: 1;
}
.fyf-home-fit__text {
  font-family: var(--font-header);
  font-size: 24px;
  line-height: 1.15;
  color: var(--fyf-cream);
}
/* rgba(250,220,194,.82) on solid charcoal measures well past AA even before
   accounting for this text's own 24px size (large text only needs 3:1) --
   same "dark background, alpha barely dims a light foreground" math as
   .fyf-home-fit__label--muted above. */
.fyf-home-fit__text--muted { color: rgba(250, 220, 194, 0.82); }
.fyf-home-fit__takeaway {
  margin: 34px 0 0;
  font-family: var(--font-subhead);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--fyf-cream);
}
