/* ============================================================================
   THE TIER TILE
   One component, two pages. The landing page loads app.css and /pricing loads
   learn.css, so a tile defined in either one could only ever exist on one of
   them, which is exactly how the two pages came to show the same two tiers as
   two different objects (owner, 2026-08-02). This file is linked by both and by
   nothing else.

   Every var() below is defined by BOTH app.css and learn.css except the two
   restated in the :root here.
   ============================================================================ */
:root {
  --pen-wash: rgba(211, 52, 44, .07);
  /* Secondary type ON a cream surface: the mirror of the --pencil / --faint ramp
     on ink, and it exists for the same reason. Carolyn's cream tile sets its
     price line in --hairline, and --hairline on cream measures 4.26:1, under the
     4.5:1 floor. This is the same grey walked toward the ink until it clears:
     5.81:1 on #F4EFE3. --hairline still carries no text anywhere. */
  --pencil-dark: #5F5C55;
  /* The component pins its own red. --pen-lift is #EC6D63 in app.css and
     #E2574D in learn.css (which declares it twice in one :root and lets the
     second win), so the one tile this file exists to make identical rendered
     two different reds on the two pages. #EC6D63 is the value app.css argues
     for: at 9.5px with .18em tracking the darker lift reads thin, and this one
     measures 5.91:1 on ink against 4.86:1. Not named --pen-lift, because
     overriding a global here would restyle 59 article pages that never asked. */
  --tier-red: #EC6D63;
}
/* ---- the tier tile, one component, both pages -------------------------------
   Carolyn's pricing mockup (brand/carolyn/) puts the FREE tier on a cream block
   and the paid tier in a pen outline on the ink. That pairing is the argument:
   the free tier is the surface this whole site is built on, so it gets the
   surface colour, and the thing you can buy is the thing that is only drawn.

   These rules are UNSCOPED on purpose. /pricing is not a `.landing` page, and
   the complaint that started this (owner, 2026-08-02) was that the two pages
   showed the same two tiers as two different objects. One component, two pages;
   .landing keeps its LAYOUT overrides above and nothing else. */
/* A flex column, and positioned: the grid below stretches the three tiles to
   ONE height (owner, 2026-08-17: they had drifted to 384/699/713px), so the
   column is what routes the slack. margin-top: auto on the footer pieces (the
   buy block, the free tile's button) drops it between the list and the footer
   instead of leaving a void under the note. position: relative anchors the
   hover sheen overlay (::after, end of file). */
.tier { border: 1px solid var(--line); border-radius: 14px; padding: 22px; background: none;
  display: flex; flex-direction: column; position: relative; }
/* The cream tile flips everything inside it to the ink ramp, because the one
   thing this palette cannot do is leave chalk-grey type on cream. */
.tier-free { background: var(--cream); color: var(--ink); border-color: var(--cream); }
/* the button ON the cream tile: the pale button inverted, since cream on cream
   is nothing and the pen on cream would put a second red object in a
   composition that already has six red bullets */
.btn-ink, .landing a.btn-ink { background: var(--ink); color: var(--cream); border-color: var(--ink); }
.btn-ink:hover, .landing a.btn-ink:hover { background: #000; color: var(--cream); text-decoration: none; }
.tier-free :focus-visible { outline-color: var(--ink); }
.tier-pro { border-color: var(--pen); background: var(--pen-wash); }
.tier-sharp { opacity: .72; }
.tier-name { font-family: var(--display); font-weight: 800; font-size: 20px; letter-spacing: -.03em; color: var(--chalk); }
.tier-free .tier-name { color: var(--ink); }
.tier-flag {
  font-family: var(--mono); font-size: 9.5px; font-weight: 500; letter-spacing: .18em; text-transform: uppercase;
  color: var(--tier-red); margin-bottom: 8px;
}
/* the lifted red exists for small red type on INK; on cream the pen itself is
   5.9:1 and is the right mark */
.tier-free .tier-flag { color: var(--pen); }
.tier-price { font-family: var(--mono); font-size: 13px; color: var(--pencil); margin-top: 7px; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.tier-free .tier-price { color: var(--pencil-dark); }
/* position:relative is load-bearing, not cosmetic: chalk.js appends its ring as
   an absolutely positioned child with inset:0, so the ring sizes itself to the
   nearest POSITIONED ancestor. Without it the circle drew around the whole
   Extra Credit tile instead of around the price. */
/* line-height: 1 IS THE FIX FOR THE RING SITTING WRONG (owner, 2026-08-09:
   "season pass red chalk is now too high and half misses the 99").

   The chalk ring is absolutely positioned against this element, so it centres on
   this element's BOX. At the inherited line-height that box is a line box, half a
   line of leading taller than the digits and asymmetric about them (digits have
   no descender, so the ink sits off-centre inside it). The ring was therefore
   correctly centred on a box that is not where the number looks like it is.

   Pinning the line height to the font size makes the box the digits' own box, so
   the ring centres on the thing a reader sees rather than on typographic
   whitespace. That is a fix rather than an offset: no magic number to re-tune the
   next time the price font changes. The flex row is baseline-aligned, so the
   "one time, not a subscription" text beside it does not move. */
.price-circle { position: relative; display: inline-block; font-size: 26px; line-height: 1; color: var(--chalk); font-weight: 500; }
.price-circle .per { font-size: 13px; color: var(--pencil); }
.tier-free .price-circle { color: var(--ink); }
.tier-free .price-circle .per { color: var(--pencil-dark); }
.tier-price .annual, .tier-price .tba { font-size: 12px; }
.tier-list { list-style: none; padding: 0; margin: 16px 0 12px; }
.tier-list li {
  font-size: 14px; line-height: 1.55; color: var(--chalk-soft);
  padding-left: 19px; position: relative; margin-bottom: 8px;
}
.tier-free .tier-list li { color: #33322F; }
.tier-list li::before {
  content: ""; position: absolute; left: 0; top: .55em; width: 6px; height: 6px;
  border-radius: 50%; background: var(--pen);
}
/* THE LIVE BULLET (owner, 2026-08-07). The Kalshi-feed bullet trades its pen
   dot for the board's own live heartbeat: the lit green dot plus the ping ring
   breathing outward, the same signal the board wears when the feed streams.
   Green here is the board's signal language crossing over deliberately, one
   dot, because the bullet names the live feed itself. Self-contained (colour
   hardcoded with the token as fallback): tiers.css ships to pages that do not
   load app.css, so it cannot lean on the board's keyframes. */
.tier-list li.li-live::before {
  background: var(--green, #6FBF8E);
  box-shadow: 0 0 7px rgba(111, 191, 142, .85);
}
.tier-list li.li-live::after {
  content: ""; position: absolute; left: 0; top: .55em; width: 6px; height: 6px;
  border-radius: 50%; background: var(--green, #6FBF8E);
  animation: tier-live-ping 2.4s cubic-bezier(0, 0, .2, 1) infinite;
}
@keyframes tier-live-ping {
  0% { transform: scale(1); opacity: .55; }
  75%, 100% { transform: scale(3.2); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .tier-list li.li-live::after { animation: none; content: none; }
}
/* THE CROSSED-OFF ROW (owner, 2026-08-17). The free tile restates the paid
   tile's rows with an X each, so the two tiles read 1:1: what this tier has,
   then what it does not, in the paid tile's own words. A crossed-off row is
   DECLARED not included, which is what keeps the section 13 rule intact (a
   tier only advertises what its reader can use; these rows say the reader
   does not get this here). The X is drawn from two gradient bars rather than
   a glyph so it cannot vary by font, and it takes the row's own dimmed grey:
   the pen stays on the rows you get. */
.tier-list li.li-not { color: var(--pencil); }
.tier-list li.li-not::before {
  background: none; border-radius: 0; width: 9px; height: 9px; top: .42em;
  background-image:
    linear-gradient(45deg, transparent 42%, currentColor 42%, currentColor 58%, transparent 58%),
    linear-gradient(135deg, transparent 42%, currentColor 42%, currentColor 58%, transparent 58%);
  opacity: .7;
}
.tier-free .tier-list li.li-not { color: var(--pencil-dark); }
.tier-list li strong { color: var(--chalk); font-weight: 600; }
.tier-free .tier-list li strong { color: var(--ink); }
/* on cream the link cannot be the pen (a second red object beside six red
   bullets), so it is the ink and the underline is what says "link" */
.tier-free .tier-list a {
  color: var(--ink); text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: rgba(23, 23, 26, .45); text-decoration-thickness: 1px;
}
.tier-free .tier-list a:hover { text-decoration-color: var(--ink); }
.tier .btn { display: block; width: 100%; text-align: center; margin-top: 20px; }
/* the free tile's own button (a direct child; the paid buttons sit inside
   .tier-buy): pinned to the stretched tile's floor beside its neighbours' */
.tier > .btn { margin-top: auto; }
/* text-align is set explicitly: learn.css centres a bare .tier-note for the
   signup box at the foot of /pricing, and inside a tile that centring is wrong. */
.tier .tier-note, .tier .tier-fine {
  font-family: var(--mono); font-size: 10.5px; color: var(--faint);
  margin-top: 12px; line-height: 1.6; text-align: left;
}
.tier-free .tier-note, .tier-free .tier-fine { color: var(--pencil-dark); }
/* Carolyn's "the annual price, shown": the arithmetic printed beside its own
   label. It is the house move applied to the one number a buyer is most likely
   to assume was rounded in our favour. */
.tier-calc { margin: 16px 0 0; padding-top: 12px; border-top: 1px solid var(--line); }
.tier-calc dt {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--faint); margin-bottom: 8px;
}
.tier-calc dd { margin: 0; font-family: var(--mono); font-size: 13px; line-height: 1.75; color: var(--chalk-soft); }
.tier-calc dd strong { color: var(--chalk); font-weight: 500; }
.tier-free .tier-calc { border-top-color: var(--line-dark); }
.tier-free .tier-calc dt { color: var(--pencil-dark); }
.tier-free .tier-calc dd { color: #33322F; }
.tier-free .tier-calc dd strong { color: var(--ink); }
/* .tier-manage is gone from the tiles (owner, 2026-08-17: all tile fine print
   dropped, Full terms link only); the account menu's acct-billing control is
   the Manage subscription path, pinned in nav.test.js. */


/* ---- the two things around the tiles on /pricing ---------------------------
   .eyebrow exists on the landing page as `.landing .eyebrow`; /pricing is an
   article page and had no such thing, so it lives here beside the component it
   labels rather than being copied into learn.css. #D3342C measures 3.66:1 on
   ink and fails AA at this size, which is why the eyebrow uses the lifted pen. */
.article .eyebrow {
  font-family: var(--mono); font-weight: 500; font-size: 11.5px; letter-spacing: .2em;
  text-transform: uppercase; color: var(--tier-red); margin: 38px 0 10px;
}
.article .eyebrow + h2 { margin-top: 0; }
/* "numbers rot at different speeds", three across */
.rot { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; margin: 24px 0 8px; }
.rot > div { border-top: 2px solid var(--pen); padding-top: 13px; }
.rot b { font-family: var(--display); font-weight: 700; font-size: 15.5px; letter-spacing: -.02em; }
.rot p { margin: 7px 0 0; font-size: 14px; line-height: 1.55; color: var(--chalk-soft); }
@media (max-width: 640px) { .rot { grid-template-columns: 1fr; gap: 16px; } }
/* the tiles want more air between them than a paragraph does */
.article .tier { margin: 18px 0 30px; }


/* the checkout controls, inside the tile they are selling (owner, 2026-08-02).
   They used to sit in their own section a page and a half further down, which
   made a reader go looking for the buy button after they had already decided. */
.tier-buy { margin-top: auto; padding-top: 14px; border-top: 1px solid var(--line); }
.tier-buy .btn { display: block; width: 100%; text-align: center; margin-top: 10px; }
.tier-buy .tier-note { text-align: left; }

/* Store builds (html[data-native], stamped at build time by app/sync-web.sh and
   at runtime by auth-ui.js / native-api.js): the paid price circles are web-only
   purchase copy (Apple 3.1.3; app/README.md v1). billing.js already withholds
   the buttons; this catches the static figures around them. The free $0 stays:
   free is a fact about the product, not a purchase offer.

   .tier-calc is here because it was missed, and the miss was invisible. It
   carries the SAVINGS ARITHMETIC ("12 x $25 = $300, the founder year is $249"),
   which is more price per square inch than the circle it sits under. The
   standard one ships with the `hidden` attribute and billing.js is what reveals
   it, so it never leaked; the founder one ships VISIBLE, because founder is the
   live offer while seats remain, so on /pricing it printed four figures inside
   the app. Any future element that carries a price and keys off these
   attributes belongs in this list. nav.test.js pins that. */
html[data-native] .tier-price[data-founder-only],
html[data-native] .tier-price[data-standard-only],
html[data-native] .tier-calc[data-founder-only],
html[data-native] .tier-calc[data-standard-only] { display: none !important; }
/* The free tile's "Start free" button carries no price, so it is lawful in a
   store build; it is hidden anyway because inside the shell the reader is
   already on the free tier, and a lone call-to-action beside two de-priced
   tiles reads like the offer the other rules just removed. */
html[data-native] .tier-free > .btn { display: none !important; }

/* ---- the NFL season pass sub-tile -------------------------------------------
   It sits INSIDE the paid card, under the two subscription buttons, because it
   answers a question the reader is already holding by the time they reach them
   ("I only want football"). It is a sub-tile rather than a sibling card so it
   reads as another way to buy the same thing, which is exactly what it is.

   Self-contained, like the rest of this file: tiers.css ships to pages that do
   not load app.css, so it declares its own keyframes and its own colours with
   the tokens as fallbacks. Cream and ink only here, no fifth colour: the ball
   and the posts are chalk on the board. */
.season-tile {
  position: relative; margin-top: 22px; padding-top: 20px;
  border-top: 1px dashed var(--hairline, #6E6A62);
}
/* The pointer line. Mono because it is an aside in the machine's voice, not
   body copy, and it sits above the tile it points at. */
.season-point {
  display: flex; align-items: center; gap: 10px; margin: 0 0 6px;
  font-family: var(--mono, ui-monospace, monospace); font-size: 13.5px;
  line-height: 1.35; color: var(--chalk, #F4EFE3);
}
/* opacity on the svg, not the paths: the arrowhead touches the shaft and the
   overlap compounded, same joint defect as sk-body and sk-post */
.season-arrow {
  width: 40px; height: 32px; flex: none; overflow: visible; align-self: flex-start;
  opacity: .75;
}
.season-arrow path {
  fill: none; stroke: var(--chalk, #F4EFE3); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}

.season-kick { margin: 2px 0 12px; }
/* overflow stays hidden: the field now extends three windows wide and the
   camera pans across it, so everything off-frame must clip at the svg box */
.season-kick svg {
  width: 100%; max-width: 340px; height: auto; display: block;
  overflow: hidden;
}

/* TEAM COLOUR, the least paint that tells two sides apart at this size: the
   helmet carries it, every limb stays chalk. Red is the site's own pen; green
   is the token the board already uses for Kalshi, on the "order book streamed
   straight in" bullet three tiles up, so the exchange keeps one colour across
   the page rather than gaining a second one here. */
.season-kick .sk-team-red { --team: #D3342C; }
/* PINNED, not var(--green). learn.css declares --green as #EC6D63, which is a
   RED, and app.css declares it as #6FBF8E. This file ships to pages that load
   either one, so the token is genuinely ambiguous here and the rushers rendered
   salmon against a red kicking team. Same trap --tier-red is pinned for above,
   same fix. #6FBF8E is the board's Kalshi green, so the exchange keeps one
   colour across the page. */
.season-kick .sk-team-green { --team: #6FBF8E; }
.season-kick .sk-fig {
  fill: none; stroke: #F4EFE3; stroke-width: 2.2;
  stroke-linecap: round; stroke-linejoin: round;
}
/* ONE OPACITY PER FIGURE (owner, 2026-08-12: "i want one consistent opacity
   throughout the shape"). The strokes used to carry their own .42 alpha, so
   every joint where two paths cross (hip, shoulder, the swinging leg against
   the torso) composited twice and read denser than the limbs around it. The
   paths are now solid cream and the translucency is applied once, on each
   figure's sk-body group, which SVG composites as a unit: overlaps inside it
   cannot compound. The helmet sits outside the group and keeps full-strength
   paint. Two different figures overlapping still deepen; that is real
   layering between shapes, not a defect inside one. */
.season-kick .sk-fig .sk-body { opacity: .42; }
/* THE HELMET IS THE ONLY COLOURED THING (owner, 2026-08-08: "drop the
   jerseys, just have them wearing detailed helmets"), and the FACEMASK is
   what says football rather than motorcycle, so it is cream: white on a
   coloured shell is the contrast that survives being 10px tall. The mask is
   an old-school TWO-BAR cage (owner, 2026-08-12: "trimming the top one and
   just having two parts like an old school helmet"): one bar at the nose,
   one at the jaw, joined at the front, extending past the shell front. All
   24 helmets (22 players, plus the scorer's cartwheel and split poses) come
   from one template in the page markup; edit them together. */
.season-kick .sk-shell { fill: var(--team); stroke: none; }
.season-kick .sk-brim {
  fill: none; stroke: rgba(23, 23, 26, .34); stroke-width: 1.1; stroke-linecap: round;
}
.season-kick .sk-ear { fill: rgba(23, 23, 26, .38); stroke: none; }
.season-kick .sk-mask {
  fill: none; stroke: #F4EFE3; stroke-width: 1.05;
  stroke-linecap: round; stroke-linejoin: round;
}
.season-kick .sk-ground {
  fill: none; stroke: var(--chalk, #F4EFE3); stroke-width: 1.6;
  stroke-linecap: round; opacity: .26;
}
/* faint yard ticks and the left goal line: without ground texture a camera
   pan reads as nothing moving, because a bare line under a flying ball gives
   the eye no reference frame */
.season-kick .sk-tick {
  fill: none; stroke: var(--chalk, #F4EFE3); stroke-width: 1.2;
  stroke-linecap: round; opacity: .13;
}
.season-kick .sk-goal {
  fill: none; stroke: var(--chalk, #F4EFE3); stroke-width: 1.6;
  stroke-linecap: round; opacity: .38;
}
/* opacity on the GROUP, not the path: the crossbar crosses both uprights and
   those joints composited to ~.75 against the .5 beams. Same fix as sk-body. */
.season-kick .sk-post {
  opacity: .5;
}
.season-kick .sk-post path {
  fill: none; stroke: var(--chalk, #F4EFE3); stroke-width: 3;
  stroke-linecap: round;
}
/* the far upright ships as its own group at the end of the camera group, so
   the kicked ball passes behind it and in front of the near one. Butt cap:
   it abuts the crossbar stroke edge-to-edge instead of overlapping it, which
   is what keeps the corner free of the compounding knot. */
.season-kick .sk-upright-far { stroke-linecap: butt; }
/* The ball is the site's red with white stitching (owner, 2026-08-08). Both
   pinned rather than tokenised, for the reason the green above is: --chalk and
   --green resolve differently depending on which stylesheet a page loaded, and
   this file ships to several. The laces have to be the light one: at 17px the
   stitching is the only thing that separates a football from an oval, so it
   needs the contrast, and ink-on-red does not have it. */
.season-kick .sk-ball .sk-skin { fill: #D3342C; stroke: none; }
.season-kick .sk-ball .sk-lace {
  fill: none; stroke: #F4EFE3; stroke-width: 1.15; stroke-linecap: round;
}

/* THE THROWN BALL CORKSCREWS (owner, 2026-08-12). A spiral seen side-on is
   the laces wrapping the ball's long axis, so each thrown ball carries two
   lace copies clipped to its own silhouette (#sk-bclip in the markup),
   sliding through the ball on a short cycle half a phase apart: one strip
   crosses the face while the other wraps the hidden side. The sweep
   overshoots the clip (6.4 against a 5.3 half-height) so a strip is fully
   hidden at the instant the cycle snaps back, and the sine-like easing is
   the projection doing its job: a point on a rotating surface moves fastest
   crossing the centre line. The flat lace (sk-hold2/sk-hold5) shows whenever
   the same ball is spotted, held or carried; the spiral group is visible
   only inside its flight window, gated on the 22s clock to match sk-pass
   and sk-twopt. Only passes spiral: the kicked ball keeps its end-over-end
   tumble, which is what a kick really does. */
.season-kick .sk-spiral { opacity: 0; }
.season-kick .sk-spinA, .season-kick .sk-spinB {
  animation: sk-spin .42s cubic-bezier(.37, 0, .63, 1) infinite;
}
.season-kick .sk-spinB { animation-delay: -.21s; }
@keyframes sk-spin {
  0%   { transform: translate(0, -6.4px); }
  100% { transform: translate(0, 6.4px); }
}
.season-kick .sk-spiral2 { animation: sk-spgate2 22s linear infinite; }
.season-kick .sk-hold2   { animation: sk-holdgate2 22s linear infinite; }
@keyframes sk-spgate2 { 0%, 33.2% { opacity: 0; } 33.4%, 40.5% { opacity: 1; } 40.7%, 100% { opacity: 0; } }
@keyframes sk-holdgate2 { 0%, 33.2% { opacity: 1; } 33.4%, 40.5% { opacity: 0; } 40.7%, 100% { opacity: 1; } }
.season-kick .sk-spiral5 { animation: sk-spgate5 22s linear infinite; }
.season-kick .sk-hold5   { animation: sk-holdgate5 22s linear infinite; }
@keyframes sk-spgate5 { 0%, 71% { opacity: 0; } 71.2%, 72.4% { opacity: 1; } 72.6%, 100% { opacity: 0; } }
@keyframes sk-holdgate5 { 0%, 71% { opacity: 1; } 71.2%, 72.4% { opacity: 0; } 72.6%, 100% { opacity: 1; } }

/* EVERY MOVING PART RUNS ON THE SAME 22s CLOCK, so the play stays a play
   (owner, 2026-08-12: the one kick grew into a three-scene drive). The old
   4.4s field goal is the first fifth of it, every old keyframe percent
   divided by 5, contact at 6%. Scene map, in percent of 22s:
     0.0 - 16.0   scene 1: the red field goal, made (the original animation)
    16.0 - 33.2   scene 2a: green offense out, snap, throw just before the sack
    33.2 - 50.0   scene 2b: camera follows the ball, catch, touchdown run left
    50.0 - 62.8   the celebration: dance, ball into the stands, fireworks,
                  and the teammates' shoulder lift
    63.0 - 100    scene 2c: green two-point try, picked off by the red DB,
                  returned the length of the field, tackled on the scene 1
                  kick spot as the field goal unit fades back in.
   Change the duration in one selector and the kicker swings at nothing.

   transform-box: view-box lets transform-origin be written in the same user
   units as the path data, so a hip at (29,98) in the markup is (29px, 98px)
   here and the two cannot drift. */

.season-kick .sk-cam,
.season-kick .sk-ball,
.season-kick .sk-leg,
.season-kick .sk-hand,
.season-kick .sk-kicker,
.season-kick .sk-rush,
.season-kick .sk-blocker,
.season-kick .sk-gq,
.season-kick .sk-qbarm,
.season-kick .sk-re,
.season-kick .sk-rn,
.season-kick .sk-gg,
.season-kick .sk-gw,
.season-kick .sk-rc,
.season-kick .sk-cycA,
.season-kick .sk-cycB,
.season-kick .sk-t1,
.season-kick .sk-t2,
.season-kick .sk-fw,
.season-kick .sk-q2arm,
.season-kick .sk-g2g,
.season-kick .sk-r2n,
.season-kick .sk-r2db,
.season-kick .sk-g2wr,
.season-kick .sk-r2r,
.season-kick .sk-g2ch,
.season-kick .sk-spinA,
.season-kick .sk-spinB,
.season-kick .sk-gw-wheel,
.season-kick .sk-gw-split { transform-box: view-box; }

/* ---- the camera. translate +X shows world at -X; linear, shaped by samples
   so the ball, then the receiver, then the runback all hold the frame. ---- */
.season-kick .sk-cam { animation: sk-cam 22s linear infinite; }
@keyframes sk-cam {
  0%, 33.4% { transform: translate(0px, 0); }
  34.4% { transform: translate(53px, 0); }
  36%   { transform: translate(139px, 0); }
  38%   { transform: translate(248px, 0); }
  40.5% { transform: translate(385px, 0); }
  43%   { transform: translate(462px, 0); }
  46%   { transform: translate(556px, 0); }
  50%, 74.5% { transform: translate(680px, 0); }
  76%   { transform: translate(648px, 0); }
  79%   { transform: translate(578px, 0); }
  82%   { transform: translate(492px, 0); }
  85%   { transform: translate(391px, 0); }
  88%   { transform: translate(280px, 0); }
  91%   { transform: translate(150px, 0); }
  93%   { transform: translate(49px, 0); }
  93.8%, 100% { transform: translate(0px, 0); }
}

/* ---- scene crossfades ---- */
.season-kick .sc1     { animation: sk-f1 22s linear infinite; }
.season-kick .sc2line { animation: sk-f2 22s linear infinite; opacity: 0; }
.season-kick .sc2wr   { animation: sk-f3 22s linear infinite; opacity: 0; }
.season-kick .sc2pt   { animation: sk-f4 22s linear infinite; opacity: 0; }
/* Staggered, not crossfaded: one unit leaves, half a beat of empty field,
   the next arrives. Ghosting two full units on the same yards read as a
   crowd. The one exception is the handoff back to scene 1, where the
   overlap IS the story: the pile lands on the spot the kicker sets up on. */
@keyframes sk-f1 { 0%, 15.5% { opacity: 1; } 18%, 97% { opacity: 0; } 99.3%, 100% { opacity: 1; } }
@keyframes sk-f2 { 0%, 18.5% { opacity: 0; } 21%, 44% { opacity: 1; } 47%, 100% { opacity: 0; } }
@keyframes sk-f3 { 0%, 30% { opacity: 0; } 33%, 60.5% { opacity: 1; } 62.8%, 100% { opacity: 0; } }
@keyframes sk-f4 { 0%, 63% { opacity: 0; } 65%, 96.5% { opacity: 1; } 98.5%, 100% { opacity: 0; } }

/* ---- scene 1, the shipped kick at one fifth of the old percents ----
   Flight: y = 0.004159(x - 210)^2 + 18.43, same parabola as production. */
.season-kick .sk-ball1 {
  transform: translate(60px, 112px);
  animation: sk-kick 22s linear infinite;
}
@keyframes sk-kick {
  0%, 6% { transform: translate(60px, 112px) rotate(0deg); opacity: 1; }
  6.38%  { transform: translate(72.7px, 96.8px) rotate(33deg); opacity: 1; }
  6.76%  { transform: translate(85.5px, 82.9px) rotate(65deg); opacity: 1; }
  7.15%  { transform: translate(98.2px, 70.4px) rotate(98deg); opacity: 1; }
  7.53%  { transform: translate(110.9px, 59.3px) rotate(131deg); opacity: 1; }
  7.91%  { transform: translate(123.6px, 49.4px) rotate(164deg); opacity: 1; }
  8.29%  { transform: translate(136.4px, 41px) rotate(196deg); opacity: 1; }
  8.67%  { transform: translate(149.1px, 33.9px) rotate(229deg); opacity: 1; }
  9.05%  { transform: translate(161.8px, 28.1px) rotate(262deg); opacity: 1; }
  9.44%  { transform: translate(174.5px, 23.7px) rotate(295deg); opacity: 1; }
  9.82%  { transform: translate(187.3px, 20.6px) rotate(327deg); opacity: 1; }
  10.2%  { transform: translate(200px, 18.8px) rotate(360deg); opacity: 1; }
  10.58% { transform: translate(212.7px, 18.5px) rotate(393deg); opacity: 1; }
  10.96% { transform: translate(225.5px, 19.4px) rotate(425deg); opacity: 1; }
  11.35% { transform: translate(238.2px, 21.7px) rotate(458deg); opacity: 1; }
  11.73% { transform: translate(250.9px, 25.4px) rotate(491deg); opacity: 1; }
  12.11% { transform: translate(263.6px, 30.4px) rotate(524deg); opacity: 1; }
  12.49% { transform: translate(276.4px, 36.7px) rotate(556deg); opacity: 1; }
  12.87% { transform: translate(289.1px, 44.4px) rotate(589deg); opacity: 1; }
  13.25% { transform: translate(301.8px, 53.5px) rotate(622deg); opacity: 1; }
  13.64% { transform: translate(314.5px, 63.9px) rotate(655deg); opacity: 1; }
  14.02% { transform: translate(327.3px, 75.6px) rotate(687deg); opacity: 1; }
  14.4%  { transform: translate(340px, 88.7px) rotate(720deg); opacity: 1; }
  14.8%  { transform: translate(352px, 104px) rotate(740deg); opacity: 0; }
  16%, 98.6% { transform: translate(60px, 112px) rotate(0deg); opacity: 0; }
  99.4%, 100% { transform: translate(60px, 112px) rotate(0deg); opacity: 1; }
}

/* the kick diagrams itself: five chalk tangents to the flight parabola
   (y = 0.004159(x - 210)^2 + 18.43), each igniting just after the ball
   passes its x, all dissolving into the handoff. Annotation rather than
   physics: the one survivor of the 2026-08-12 polish pass (owner: "i like
   the dotted line arc for the ball, thats it"), so do not grow it into a
   family of effects. Opacity only, no transform-box entry needed; figures
   draw over it. */
.season-kick .sk-arc path {
  fill: none; stroke: #F4EFE3; stroke-width: 1.2; stroke-linecap: round;
  opacity: 0;
}
.season-kick .sk-arcA { animation: sk-arcA 22s linear infinite; }
.season-kick .sk-arcB { animation: sk-arcB 22s linear infinite; }
.season-kick .sk-arcC { animation: sk-arcC 22s linear infinite; }
.season-kick .sk-arcD { animation: sk-arcD 22s linear infinite; }
.season-kick .sk-arcE { animation: sk-arcE 22s linear infinite; }
@keyframes sk-arcA { 0%, 7.4% { opacity: 0; } 7.9% { opacity: .16; } 15.3% { opacity: .05; } 16.3%, 100% { opacity: 0; } }
@keyframes sk-arcB { 0%, 8.9% { opacity: 0; } 9.4% { opacity: .16; } 15.3% { opacity: .05; } 16.3%, 100% { opacity: 0; } }
@keyframes sk-arcC { 0%, 10.4% { opacity: 0; } 10.9% { opacity: .16; } 15.3% { opacity: .05; } 16.3%, 100% { opacity: 0; } }
@keyframes sk-arcD { 0%, 11.9% { opacity: 0; } 12.4% { opacity: .16; } 15.3% { opacity: .05; } 16.3%, 100% { opacity: 0; } }
@keyframes sk-arcE { 0%, 13.4% { opacity: 0; } 13.9% { opacity: .16; } 15.3% { opacity: .05; } 16.3%, 100% { opacity: 0; } }

.season-kick .sk-kicker { animation: sk-runup 22s ease-in-out infinite; }
@keyframes sk-runup {
  0%   { transform: translate(-30px, 0); }
  6%   { transform: translate(0, 0); }
  8%, 19.5% { transform: translate(7px, 0); }
  21.5%, 100% { transform: translate(-30px, 0); }
}

.season-kick .sk-leg {
  transform-origin: 29px 98px;
  animation: sk-swing 22s ease-in-out infinite;
}
@keyframes sk-swing {
  0%, 4% { transform: rotate(42deg); }
  6%     { transform: rotate(0deg); }
  7.6%   { transform: rotate(-46deg); }
  14.4%, 19.5% { transform: rotate(-14deg); }
  21.5%, 100%  { transform: rotate(42deg); }
}

.season-kick .sk-hand {
  transform-origin: 61px 99px;
  animation: sk-hold 22s ease-in-out infinite;
}
@keyframes sk-hold {
  0%, 6% { transform: rotate(0deg); }
  7.6%   { transform: rotate(-34deg); }
  14.4%, 19.5% { transform: rotate(-30deg); }
  21.5%, 100%  { transform: rotate(0deg); }
}

.season-kick .sk-rush { animation: sk-rushk 22s ease-in-out infinite; }
.season-kick .sk-rush-b { animation-delay: -.26s; }
@keyframes sk-rushk {
  0%   { transform: translate(34px, 0); }
  6%   { transform: translate(3px, 0); }
  8%   { transform: translate(-2px, -8px); }
  11.2%, 19.5% { transform: translate(-5px, 0); }
  21.5%, 100%  { transform: translate(34px, 0); }
}

.season-kick .sk-blocker { animation: sk-brace 22s ease-in-out infinite; }
@keyframes sk-brace {
  0%, 5.2% { transform: translate(0, 0); }
  7.6%     { transform: translate(-3px, 0); }
  12%, 100% { transform: translate(0, 0); }
}

/* ---- scene 2a. Snap at 25%, throw at 33.2%, hit a beat after it. ----
   Pass parabola: y = 0.001228(x + 25.3)^2 + 36, from (192,94) to (-235,90).
   The ball shrinks over the last two samples so it lands at the carried
   size; every keyframe restates scale so no pair falls back to matrix
   interpolation. */
.season-kick .sk-ball2 {
  transform: translate(148px, 114px);
  animation: sk-pass 22s linear infinite;
}
@keyframes sk-pass {
  0%, 25% { transform: translate(148px, 114px) rotate(0deg) scale(1); opacity: 1; }
  26%     { transform: translate(167px, 104px) rotate(-24deg) scale(1); opacity: 1; }
  /* the snap lands IN THE HANDS, chest height in front of the torso, and
     HOLDS there (owner, 2026-08-12: it used to sit at (180,97), chin height,
     then drift to the release point across the whole pocket, "it kinda
     hovers over his face"). The catch settles a unit, the hold is dead
     still, and the ball only rises to the release point with the pump. */
  26.8%   { transform: translate(186px, 102px) rotate(6deg) scale(1); opacity: 1; }
  27.4%   { transform: translate(185px, 103px) rotate(0deg) scale(1); opacity: 1; }
  32.5%   { transform: translate(185px, 103px) rotate(0deg) scale(1); opacity: 1; }
  33.2%   { transform: translate(192px, 94px) rotate(-12deg) scale(1); opacity: 1; }
  34.11%  { transform: translate(138.6px, 69px) rotate(-15deg) scale(1); opacity: 1; }
  35.02%  { transform: translate(85.3px, 51px) rotate(-18deg) scale(1); opacity: 1; }
  35.94%  { transform: translate(31.9px, 40px) rotate(-20deg) scale(1); opacity: 1; }
  36.85%  { transform: translate(-21.5px, 36px) rotate(-23deg) scale(1); opacity: 1; }
  37.76%  { transform: translate(-74.9px, 39px) rotate(-26deg) scale(.97); opacity: 1; }
  38.67%  { transform: translate(-128.3px, 49px) rotate(-29deg) scale(.92); opacity: 1; }
  39.59%  { transform: translate(-181.6px, 66px) rotate(-31deg) scale(.85); opacity: 1; }
  40.5%   { transform: translate(-235px, 90px) rotate(-34deg) scale(.72); opacity: 1; }
  40.8%   { transform: translate(-235px, 90px) rotate(-34deg) scale(.72); opacity: 0; }
  60%, 99.8% { transform: translate(148px, 114px) rotate(0deg) scale(1); opacity: 0; }
  100%    { transform: translate(148px, 114px) rotate(0deg) scale(1); opacity: 1; }
}

.season-kick .sk-gq {
  transform-origin: 182px 120px;
  animation: sk-qb 22s ease-in-out infinite;
}
@keyframes sk-qb {
  0%, 27% { transform: translate(0, 0) rotate(0deg); }
  32.5%, 34% { transform: translate(12px, 0) rotate(0deg); }
  36%, 100%  { transform: translate(14px, 2px) rotate(74deg); }
}

.season-kick .sk-qbarm {
  transform-origin: 182px 100px;
  animation: sk-qbarm 22s ease-in-out infinite;
}
@keyframes sk-qbarm {
  0%, 27% { transform: rotate(0deg); }
  32.6%   { transform: rotate(-38deg); }
  33.4%, 100% { transform: rotate(34deg); }
}

.season-kick .sk-re {
  transform-origin: 102px 99px;
  animation: sk-blitz 22s ease-in-out infinite;
}
@keyframes sk-blitz {
  0%, 29% { transform: translate(0, 0) rotate(0deg); }
  33.2%   { transform: translate(80px, 0) rotate(0deg); }
  34.2%   { transform: translate(92px, -2px) rotate(-16deg); }
  36%, 100% { transform: translate(94px, 0) rotate(-22deg); }
}

.season-kick .sk-rn { animation: sk-push-rn 22s ease-in-out infinite; }
@keyframes sk-push-rn {
  0%, 25% { transform: translate(0, 0); }
  29%     { transform: translate(3px, 0); }
  33%, 100% { transform: translate(1px, 0); }
}
.season-kick .sk-gg { animation: sk-push-gg 22s ease-in-out infinite; }
@keyframes sk-push-gg {
  0%, 25% { transform: translate(0, 0); }
  29%     { transform: translate(-2px, 0); }
  33%, 100% { transform: translate(-1px, 0); }
}

/* ---- scene 2b. Catch at 40.5% (hips at -235), goal line (-555) just
   before 50%, pulled up at -572, clear of the near upright at -586.
   Then the celebration: a hip-wiggle dance, the ball hurled up and out
   of frame into the stands, and the two teammates who sprinted over
   lift the scorer onto their shoulders. ---- */
.season-kick .sk-gw {
  transform-origin: -140px 102px;
  animation: sk-wr 22s linear infinite;
}
/* THE CELEBRATION IS A CARTWHEEL INTO A FULL SPLIT (owner, 2026-08-12,
   replacing the hip-wiggle and the 9px shoulder-lift rise). The root walks
   the whole act: pull up at -432, drift 24 units deeper into the end zone
   while the wheel sprite turns a full -360 about its own centre, stop, and
   the split sprite drops in on the spot. The ball is hurled BEFORE the
   wheel starts, because a man mid-cartwheel has no hands free, and the
   teammates arrive flanking the split instead of lifting him: nobody lifts
   a man sitting in a split. Sprites and their gates are below sk-cb. */
@keyframes sk-wr {
  0%, 33.2% { transform: translate(0, 0) rotate(0deg); }
  36.8%  { transform: translate(-39px, 0) rotate(0deg); }
  40.5%  { transform: translate(-95px, 0) rotate(0deg); }
  50%    { transform: translate(-425px, 0) rotate(0deg); }
  51.6%, 52.6% { transform: translate(-432px, 0) rotate(0deg); }
  56.2%, 62.9% { transform: translate(-456px, 0) rotate(0deg); }
  63.3%, 100% { transform: translate(0, 0) rotate(0deg); }
}
/* the three scorer sprites: the runner carries the play, the star pose
   turns the wheel (origin is the star's own centre), the split lands it.
   Each gate hides its sprite outside its beat so the scene fade can never
   catch two scorers at once. */
.season-kick .sk-gw-run { animation: sk-gwrun 22s linear infinite; }
.season-kick .sk-gw-wheel {
  transform-origin: -140px 96px;
  animation: sk-gwwheel 22s linear infinite;
  opacity: 0;
}
.season-kick .sk-gw-split { animation: sk-gwsplit 22s linear infinite; opacity: 0; }
@keyframes sk-gwrun { 0%, 52.4% { opacity: 1; } 52.7%, 100% { opacity: 0; } }
@keyframes sk-gwwheel {
  0%, 52.6% { transform: rotate(0deg); opacity: 0; }
  52.8% { transform: rotate(-25deg); opacity: 1; }
  56.1% { transform: rotate(-360deg); opacity: 1; }
  56.4%, 100% { transform: rotate(-360deg); opacity: 0; }
}
@keyframes sk-gwsplit {
  0%, 56.2% { transform: translate(0, -6px); opacity: 0; }
  56.5% { transform: translate(0, -2px); opacity: 1; }
  56.9% { transform: translate(0, 0); opacity: 1; }
  62.9% { transform: translate(0, 0); opacity: 1; }
  63.3%, 100% { transform: translate(0, -6px); opacity: 0; }
}

.season-kick .sk-rc {
  transform-origin: -114px 102px;
  animation: sk-cb 22s linear infinite;
}
@keyframes sk-cb {
  0%, 33.2% { transform: translate(0, 0) rotate(0deg); }
  36.8%  { transform: translate(-30px, 0) rotate(0deg); }
  40.5%  { transform: translate(-78px, 0) rotate(0deg); }
  41.4%  { transform: translate(-98px, 4px) rotate(-55deg); }
  42.4%  { transform: translate(-112px, 9px) rotate(-80deg); }
  44%, 62.9% { transform: translate(-118px, 9px) rotate(-80deg); }
  63.3%, 100% { transform: translate(0, 0) rotate(0deg); }
}

/* the ball rides tucked, then flies up and out of the frame at 54.9 */
.season-kick .sk-ball3 {
  transform: translate(-241px, 95px) rotate(0deg) scale(.72);
  animation: sk-carry 22s linear infinite;
  opacity: 0;
}
/* the hurl comes FIRST now: ball away at 52.2, airborne while the wheel
   begins, gone from frame before the split lands. A cartwheel needs both
   hands on the grass. */
@keyframes sk-carry {
  0%, 40.5% { transform: translate(-241px, 95px) rotate(0deg) scale(.72); opacity: 0; }
  40.7%  { transform: translate(-241px, 95px) rotate(0deg) scale(.72); opacity: 1; }
  50%    { transform: translate(-571px, 95px) rotate(0deg) scale(.72); opacity: 1; }
  51.6%, 51.9% { transform: translate(-578px, 95px) rotate(0deg) scale(.72); opacity: 1; }
  52.2%  { transform: translate(-580px, 86px) rotate(0deg) scale(.72); opacity: 1; }
  52.7%  { transform: translate(-608px, 44px) rotate(-160deg) scale(.72); opacity: 1; }
  53.3%  { transform: translate(-640px, -8px) rotate(-320deg) scale(.72); opacity: 1; }
  53.8%  { transform: translate(-666px, -46px) rotate(-430deg) scale(.72); opacity: 1; }
  54.1%  { transform: translate(-674px, -58px) rotate(-460deg) scale(.72); opacity: 0; }
  54.5%, 100% { transform: translate(-241px, 95px) rotate(0deg) scale(.72); opacity: 0; }
}

/* the teammates: in from the play side at full sprint, arriving either side
   of the split as it lands (the scorer is 24 units deeper than he used to
   be, so both marks moved with him; the old 2px lift dip went with the
   lift itself) */
.season-kick .sk-t1 { animation: sk-t1 22s ease-in-out infinite; }
@keyframes sk-t1 {
  0%, 50.4% { transform: translate(0, 0); }
  57.4%, 62.9% { transform: translate(-246px, 0); }
  63.3%, 100%  { transform: translate(0, 0); }
}
.season-kick .sk-t2 { animation: sk-t2 22s ease-in-out infinite; }
@keyframes sk-t2 {
  0%, 50.4% { transform: translate(0, 0); }
  57.6%, 62.9% { transform: translate(-318px, 0); }
  63.3%, 100%  { transform: translate(0, 0); }
}

/* fireworks over the end zone: three bursts, each popping twice */
.season-kick .sk-fw path {
  fill: none; stroke: var(--chalk, #F4EFE3); stroke-width: 1.3; stroke-linecap: round;
}
.season-kick .sk-fw-red path { stroke: #EC6D63; }
.season-kick .sk-fw1 { transform-origin: -610px 30px; animation: sk-fwA 22s linear infinite; opacity: 0; }
.season-kick .sk-fw2 { transform-origin: -560px 22px; animation: sk-fwB 22s linear infinite; opacity: 0; }
.season-kick .sk-fw3 { transform-origin: -652px 42px; animation: sk-fwC 22s linear infinite; opacity: 0; }
@keyframes sk-fwA {
  0%, 51.5% { transform: scale(.15); opacity: 0; }
  52.1% { transform: scale(.7); opacity: 1; }
  53.2% { transform: scale(1.15); opacity: 0; }
  55.3% { transform: scale(.15); opacity: 0; }
  55.9% { transform: scale(.75); opacity: 1; }
  57%, 100% { transform: scale(1.2); opacity: 0; }
}
@keyframes sk-fwB {
  0%, 52.4% { transform: scale(.15); opacity: 0; }
  53%   { transform: scale(.7); opacity: 1; }
  54.2% { transform: scale(1.15); opacity: 0; }
  56.2% { transform: scale(.15); opacity: 0; }
  56.8% { transform: scale(.75); opacity: 1; }
  57.9%, 100% { transform: scale(1.2); opacity: 0; }
}
@keyframes sk-fwC {
  0%, 53.4% { transform: scale(.15); opacity: 0; }
  54%   { transform: scale(.7); opacity: 1; }
  55.2% { transform: scale(1.15); opacity: 0; }
  57.2% { transform: scale(.15); opacity: 0; }
  57.8% { transform: scale(.75); opacity: 1; }
  58.9%, 100% { transform: scale(1.2); opacity: 0; }
}

/* the running legs, a separate short clock; antiphase via negative delay */
.season-kick .sk-cycA, .season-kick .sk-cycB {
  animation: sk-cycle .36s ease-in-out infinite;
}
.season-kick .sk-cycB { animation-delay: -.18s; }
/* every cycling pair of legs swings about ITS OWN figure's hip; without
   these the origin falls back to the viewBox centre and the legs fly off */
.season-kick .sk-gw .sk-cycA, .season-kick .sk-gw .sk-cycB { transform-origin: -140px 102px; }
.season-kick .sk-t1 .sk-cycA, .season-kick .sk-t1 .sk-cycB { transform-origin: -330px 102px; }
.season-kick .sk-t2 .sk-cycA, .season-kick .sk-t2 .sk-cycB { transform-origin: -306px 102px; }
.season-kick .sk-r2r .sk-cycA, .season-kick .sk-r2r .sk-cycB { transform-origin: -574px 102px; }
.season-kick .sk-g2ch .sk-cycA, .season-kick .sk-g2ch .sk-cycB { transform-origin: -588px 102px; }
@keyframes sk-cycle {
  0%, 100% { transform: rotate(26deg); }
  50% { transform: rotate(-26deg); }
}

/* ---- scene 2c. The two-point try. Snap at 67.5%, the dart at 71%,
   picked at 72.4% by the DB who jumped the route, and the return runs
   73.3% to 93.8%: 629 units at a constant rate, camera fitted to it.
   The chasing receiver runs him down and the tackle lands on the
   scene 1 kick spot, which is the loop handing red the field goal. ---- */
.season-kick .sk-q2arm {
  transform-origin: -508px 100px;
  animation: sk-q2arm 22s ease-in-out infinite;
}
@keyframes sk-q2arm {
  0%, 70.6% { transform: rotate(0deg); }
  70.9%     { transform: rotate(-38deg); }
  71.4%, 100% { transform: rotate(35deg); }
}

.season-kick .sk-g2g { animation: sk-push-g2g 22s ease-in-out infinite; }
@keyframes sk-push-g2g {
  0%, 67.5% { transform: translate(0, 0); }
  69%       { transform: translate(-2px, 0); }
  71%, 100% { transform: translate(-1px, 0); }
}
.season-kick .sk-r2n { animation: sk-push-r2n 22s ease-in-out infinite; }
@keyframes sk-push-r2n {
  0%, 67.5% { transform: translate(0, 0); }
  69%       { transform: translate(3px, 0); }
  71%, 100% { transform: translate(1px, 0); }
}

/* the DB: waits in the end zone, leaps the lane, and lands as the
   running returner sprite takes over */
.season-kick .sk-r2db { animation: sk-db 22s ease-in-out infinite; }
@keyframes sk-db {
  0%, 71.2% { transform: translate(0, 0); opacity: 1; }
  71.9% { transform: translate(5px, -6px); opacity: 1; }
  72.4% { transform: translate(7px, -5px); opacity: 1; }
  72.9% { transform: translate(9px, 0); opacity: 1; }
  73.2%, 99.5% { transform: translate(9px, 0); opacity: 0; }
  100%  { transform: translate(0, 0); opacity: 1; }
}

/* the intended receiver: hands out for the ball he never gets, then he
   swaps for the chaser sprite and hunts the interceptor down */
.season-kick .sk-g2wr { animation: sk-wrwait 22s linear infinite; }
@keyframes sk-wrwait {
  0%, 73.1% { opacity: 1; }
  73.5%, 99.5% { opacity: 0; }
  100% { opacity: 1; }
}

.season-kick .sk-r2r {
  transform-origin: -574px 102px;
  animation: sk-ret 22s linear infinite;
  opacity: 0;
}
@keyframes sk-ret {
  0%, 72.9% { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  73.3%  { transform: translate(4px, 0) rotate(0deg); opacity: 1; }
  93.8%  { transform: translate(629px, 0) rotate(0deg); opacity: 1; }
  95.2%, 99.5% { transform: translate(640px, 4px) rotate(72deg); opacity: 1; }
  100%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
}

.season-kick .sk-g2ch {
  transform-origin: -588px 102px;
  animation: sk-chase 22s linear infinite;
  opacity: 0;
}
@keyframes sk-chase {
  0%, 73.4% { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  73.8%  { transform: translate(5px, 0) rotate(0deg); opacity: 1; }
  93.8%  { transform: translate(610px, 0) rotate(0deg); opacity: 1; }
  95%, 99.5% { transform: translate(626px, 5px) rotate(62deg); opacity: 1; }
  100%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
}

/* the ball: spotted, snapped, thrown as a dart on a shallow arc
   (y = 0.01524(x + 552)^2 + 70), picked at (-580, 82), tucked, returned,
   and dropped in the tackle right where scene 1 spots its kick */
.season-kick .sk-ball5 {
  transform: translate(-542px, 114px) rotate(0deg) scale(.72);
  animation: sk-twopt 22s linear infinite;
}
@keyframes sk-twopt {
  0%, 67.5% { transform: translate(-542px, 114px) rotate(0deg) scale(.72); opacity: 1; }
  /* same catch the scene 2a QB gets: into the hands at chest height, a
     one-unit settle, a dead-still hold, the rise saved for the pump. It
     used to park at (-516,99), face height, for the whole count. */
  67.9% { transform: translate(-530px, 105px) rotate(-20deg) scale(.72); opacity: 1; }
  68.3% { transform: translate(-512px, 102px) rotate(6deg) scale(.72); opacity: 1; }
  68.8% { transform: translate(-513px, 103px) rotate(0deg) scale(.72); opacity: 1; }
  70.7% { transform: translate(-513px, 103px) rotate(0deg) scale(.72); opacity: 1; }
  71%   { transform: translate(-514px, 92px) rotate(0deg) scale(.72); opacity: 1; }
  71.5% { transform: translate(-536px, 74px) rotate(-14deg) scale(.72); opacity: 1; }
  72%   { transform: translate(-558px, 70px) rotate(-24deg) scale(.72); opacity: 1; }
  72.4% { transform: translate(-580px, 82px) rotate(-32deg) scale(.72); opacity: 1; }
  73.1% { transform: translate(-566px, 97px) rotate(0deg) scale(.72); opacity: 1; }
  93.8% { transform: translate(63px, 97px) rotate(0deg) scale(.72); opacity: 1; }
  95.2%, 96.6% { transform: translate(74px, 106px) rotate(40deg) scale(.72); opacity: 1; }
  97.4% { transform: translate(74px, 106px) rotate(40deg) scale(.72); opacity: 0; }
  98.2% { transform: translate(-542px, 114px) rotate(0deg) scale(.72); opacity: 0; }
  99.2%, 100% { transform: translate(-542px, 114px) rotate(0deg) scale(.72); opacity: 1; }
}

/* The glow. This is the product the owner expects to sell most, so it is the
   one control on the page that moves. Kept to a shadow pulse rather than a
   colour change, because btn-gold's own colour is doing the work already. */
.btn.btn-season {
  position: relative;
  animation: season-glow 2.8s ease-in-out infinite;
}
@keyframes season-glow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(211, 52, 44, .34), 0 0 12px 0 rgba(211, 52, 44, .16); }
  50%      { box-shadow: 0 0 0 5px rgba(211, 52, 44, 0), 0 0 22px 3px rgba(211, 52, 44, .30); }
}

/* Reduced motion drops the movement and keeps the picture: the later scenes
   are hidden entirely, the ball rests past the crossbar where the kick would
   have left it (the same still this tile has always stopped on), and the
   button keeps a static glow so it is still the emphasized control. */
@media (prefers-reduced-motion: reduce) {
  .season-kick .sk-cam,
  .season-kick .sc1,
  .season-kick .sk-kicker,
  .season-kick .sk-rush { animation: none; }
  .season-kick .sc2line, .season-kick .sc2wr,
  .season-kick .sc2pt { display: none; }
  .season-kick .sk-ball1 {
    animation: none;
    transform: translate(275px, 36px) rotate(430deg);
    opacity: 1;
  }
  /* the still predates the arc and stays exact: no annotation without the
     flight it annotates */
  .season-kick .sk-arc path { animation: none; opacity: 0; }
  /* spirals stand down to their base opacity 0 and the flat laces to their
     base 1: a still ball wears still laces */
  .season-kick .sk-spinA, .season-kick .sk-spinB,
  .season-kick .sk-spiral2, .season-kick .sk-spiral5,
  .season-kick .sk-hold2, .season-kick .sk-hold5 { animation: none; }
  .season-kick .sk-leg { animation: none; transform: rotate(-42deg); }
  .season-kick .sk-hand { animation: none; transform: rotate(-34deg); }
  .season-kick .sk-rush { transform: translate(1px, -7px); }
  .season-kick .sk-rush-b { animation-delay: 0s; }
  .season-kick .sk-blocker { animation: none; transform: translate(-2.5px, 0); }
  .season-kick .sk-cycA, .season-kick .sk-cycB { animation: none; }
  .btn.btn-season {
    animation: none;
    box-shadow: 0 0 16px 2px rgba(211, 52, 44, .24);
  }
}

/* ---- the three-up grid ------------------------------------------------------
   Owner, 2026-08-08: "break it out into a third tile so we can see all pricing
   tiles on one screen in desktop." Defined HERE and not in app.css, because
   /pricing loads this file and app.css only ships to the landing page, so a
   grid written there would have left /pricing stacking three full-height cards
   down the page: the exact thing this change exists to stop.

   Three columns above 1080, two between 760 and 1080 (the season tile drops to
   its own row and still reads), one below. minmax(0,1fr) rather than 1fr so a
   long unbroken price string cannot blow a column out. */
/* auto-fit, NOT a fixed column count with breakpoints. The first version went
   three-up above 1080px and two-up below it, which put the season pass on its
   own row under Extra Credit on any laptop window narrower than that: from the
   outside that reads as the pass being nested in the paid tile, which is
   exactly the complaint this grid was built to answer. auto-fit has no cliff.
   It fits three whenever about 830px of grid is available, two below that, one
   below 555, and it does the right thing at every width in between instead of
   at four of them. */
/* stretch, not start (owner, 2026-08-17: "they are currently all vastly
   different shapes"): the row's tiles hold one height, and the .tier flex
   column above decides where each tile's slack goes. */
.tiers {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 22px; margin-top: 34px; align-items: stretch;
}
/* THE BREAKPOINT IS LOW ON PURPOSE, and it is the whole fix for "the season
   pass is still a nested tile". It used to be 1080px, so any laptop window
   narrower than that dropped the pass onto its own row directly under Extra
   Credit, which from the outside looks exactly like the pass being part of the
   paid tile. Three columns now hold down to 860px, which covers essentially
   every desktop and laptop window.

   auto-fit was tried and is worse: at 1280px it invents a FOURTH empty column,
   which narrows all three tiles and puts the dead space straight back. A fixed
   three with a low breakpoint says what we actually mean. */
@media (max-width: 860px) { .tiers { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) { .tiers { grid-template-columns: 1fr; } }

/* ---- the season tile: grey ground, red accents, red glow -------------------
   Owner, 2026-08-08. It is the one tile that is neither the free cream block
   nor the paid pen outline, so it gets its own ground: a lifted grey that reads
   as a third thing rather than as a variant of either. The red is accent only,
   which is what keeps it from competing with the paid card's outline. */
.tier.tier-season {
  background: #23232A;
  border: 1px solid rgba(211, 52, 44, .38);
  box-shadow: 0 0 0 1px rgba(211, 52, 44, .06), 0 0 26px -6px rgba(211, 52, 44, .30);
  animation: season-tile-glow 3.6s ease-in-out infinite;
}
/* THE BEST-VALUE BANNER. Pen red on cream, which is the one high-contrast pair
   the brand reserves for a mark, and the tile's own glow already carries the
   red, so this reads as part of it rather than a fifth colour.

   It sits INSIDE the tile rather than riding above it on a ribbon: a banner that
   overhangs the card needs negative margins and a z-index, and both fight the
   tile's animated glow and its lift-on-hover. This says the same thing without
   fighting anything. */
.tier-banner {
  display: inline-flex; flex-direction: column; gap: 1px;
  align-self: flex-start; margin-bottom: 9px; padding: 5px 10px 6px;
  border-radius: 5px; background: var(--cream, #F4EFE3); color: var(--ink, #17171A);
  font-family: var(--display); font-weight: 800; font-size: 11.5px;
  letter-spacing: .04em; text-transform: uppercase;
}
.tier-banner-sub {
  font-family: var(--mono); font-weight: 500; font-size: 9.5px;
  letter-spacing: .02em; text-transform: none; color: #4A4640;
}
.tier-banner-sub::before { content: "$"; }
.tier.tier-season .tier-flag { color: #EC6D63; }
.tier.tier-season .tier-name { color: var(--cream, #F4EFE3); }
.tier.tier-season .price-circle { color: var(--cream, #F4EFE3); }
@keyframes season-tile-glow {
  0%, 100% { box-shadow: 0 0 0 1px rgba(211, 52, 44, .06), 0 0 22px -8px rgba(211, 52, 44, .26); }
  50%      { box-shadow: 0 0 0 1px rgba(211, 52, 44, .12), 0 0 34px -4px rgba(211, 52, 44, .46); }
}
@media (prefers-reduced-motion: reduce) {
  .tier.tier-season { animation: none; }
}

/* THE GRID BREAKS OUT OF THE READING COLUMN, and this is the whole reason the
   three tiles would not fit a screen. /pricing renders inside .wrap, which is
   max-width 720px because it is a column sized for PROSE. Three tiles in 720px
   is about 210px each, so every bullet wrapped four or five times and the row
   grew to 2544px: the height was a width problem the entire time.

   Scoped to .wrap because the landing page does not use it, so this cannot
   reach the homepage grid, which already sits in a wide container. The
   left/transform pair is the standard breakout for a centred parent: it works
   off the VIEWPORT rather than the column, so the grid stays centred on the
   page rather than on the 720px it is escaping. */
/* IN FLOW, via a negative margin, and NOT via left + transform. A transform
   moves a box visually and changes nothing about layout, so the first version
   left a 720px-wide, 1231px-tall hole in the column: everything after the grid
   flowed under the MIDDLE tile instead of under the page. The margin does the
   same centring while the box keeps the width it is drawn at, so the founder
   card below lands where it looks like it should. */
.wrap .tiers,
.wrap .tiers-head {
  width: min(1200px, calc(100vw - 48px));
  margin-left: calc(50% - min(600px, calc(50vw - 24px)));
}
.wrap .tiers-head { margin-bottom: 0; }
.wrap .tiers-after { margin-top: 26px; }

/* THE BREAKOUT MUST KNOW ABOUT THE RAIL (found 2026-08-17, live on prod). The
   formulas above centre the grid in the VIEWPORT, and at 1280px and up
   app-rail.css pads the body 240px for the left rail, so the grid slid under
   it: at 1440px the free tile's first 160px sat beneath the nav. Same
   formulas, with the rail's 240px carved out of every viewport term, scoped to
   exactly the conditions under which app-rail.css creates the problem (the
   has-rail class exists only when app-rail.js runs, so a no-JS render keeps
   the plain centring it needs). If the rail's width changes, both files move. */
@media (min-width: 1280px) {
  html.has-rail .wrap .tiers,
  html.has-rail .wrap .tiers-head {
    width: min(1200px, calc(100vw - 240px - 48px));
    margin-left: calc(50% - min(600px, (100vw - 240px) / 2 - 24px));
  }
}

/* ---- one hover, every tile, both pages (owner, 2026-08-17) -----------------
   The landing tiles hovered on the kit's tb-lift / tb-glow pair, whose accent
   DEFAULTS TO BLUE, and /pricing's tiles did not hover at all: three tiles,
   three behaviours. This is the one hover now, defined in the component file
   so both pages get it: a lift, the border warming to the pen, a red halo
   that keeps breathing while the pointer stays, and a single sheen pass. The
   season tile's resting glow is untouched; while hovered, the shared halo
   (stronger than its idle peak) simply takes over the same box-shadow.

   The transition list carries opacity/translate with the landing reveal's own
   timing: tiers.css loads AFTER app.css, so a bare transform/box-shadow list
   here would have replaced .tb-reveal's transition and snapped the entrance.
   On /pricing, which has no reveal, those two entries are inert. */
.tier {
  transition:
    opacity .55s ease var(--tb-delay, 0s), translate .55s ease var(--tb-delay, 0s),
    transform .22s ease, box-shadow .25s ease, border-color .25s ease;
}
.tier:hover, .tier:focus-within {
  transform: translateY(-4px);
  border-color: rgba(211, 52, 44, .7);
  box-shadow:
    0 14px 34px rgba(0, 0, 0, .35),
    0 0 0 1px rgba(211, 52, 44, .14),
    0 0 38px -2px rgba(211, 52, 44, .5);
  animation: tier-halo 2.2s ease-in-out .25s infinite;
}
/* the halo's 0% frame restates the static hover shadow, so the breathing takes
   over from the entry transition without a step */
@keyframes tier-halo {
  0%, 100% {
    box-shadow:
      0 14px 34px rgba(0, 0, 0, .35),
      0 0 0 1px rgba(211, 52, 44, .14),
      0 0 38px -2px rgba(211, 52, 44, .5);
  }
  50% {
    box-shadow:
      0 14px 34px rgba(0, 0, 0, .35),
      0 0 0 1px rgba(211, 52, 44, .22),
      0 0 64px 2px rgba(211, 52, 44, .72);
  }
}
/* the sheen: one pass of light across the tile on hover-in, then done. It is
   an overlay rather than a background so it rides over the tile's own ground
   (cream, pen wash, the season grey) identically. Resting opacity is 0 and
   the keyframes run without fill, so it exists only while it sweeps. */
.tier::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: linear-gradient(105deg, transparent 42%, rgba(244, 239, 227, .09) 50%, transparent 58%) no-repeat;
  background-size: 250% 100%; background-position: 130% 0; opacity: 0;
}
/* on the cream tile a cream sheen is invisible; white reads as the gloss */
.tier-free::after {
  background-image: linear-gradient(105deg, transparent 42%, rgba(255, 255, 255, .5) 50%, transparent 58%);
}
.tier:hover::after { animation: tier-sheen .9s ease-out 1; }
@keyframes tier-sheen {
  from { background-position: 115% 0; opacity: 1; }
  to   { background-position: -15% 0; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .tier:hover, .tier:focus-within { transform: none; animation: none; }
  .tier:hover::after { animation: none; }
}
