    /* ================================= HERO ================================= */
    .hero{ padding:90px 0 40px; text-align:center; }
    .pill{
      display:inline-flex; align-items:center; gap:8px; font-size:13px; font-weight:600;
      color:var(--lime); background:var(--glass); border:1px solid var(--glass-line);
      padding:7px 15px; border-radius:999px; margin-bottom:26px;
    }
    .pill .dot{ width:7px; height:7px; border-radius:50%; background:var(--lime); box-shadow:0 0 10px var(--lime); }
    /* Scoped to the hero. As a bare `h1{}` this styled the <h1> on every route in
       the app — /reading's .rx-title, /listening's .rl-title and anything a
       module renders — at up to 82px, and each of those then had to fight it. */
    .hero h1{
      font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif; font-weight:800;
      font-size:clamp(40px,7vw,82px); line-height:1.0; letter-spacing:-2px; margin-bottom:22px;
    }
    .hero h1 .grad{
      background:linear-gradient(120deg,var(--lime),var(--cyan),var(--violet));
      -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent;
    }
    .hero p.lead{ font-size:20px; color:var(--muted); max-width:600px; margin:0 auto 34px; }
    .hero-cta{ display:flex; gap:14px; justify-content:center; flex-wrap:wrap; margin-bottom:14px; }
    .btn-lg{ padding:15px 28px; font-size:16px; border-radius:13px; }
    .trust{ font-size:13.5px; color:var(--muted); margin-top:18px; }

    /* Staggered entrance for hero items. The hidden start state lives INSIDE the
       @keyframes (not on .reveal itself) so the element's DEFAULT state is fully
       visible: if the animation never runs — reduced-motion, a browser that skips
       it, or a tab that loaded while backgrounded so the animation was frozen —
       the content still shows instead of being stranded at opacity:0 (which read
       as a blank hero — just the background gradient — on mobile). */
    .reveal{ animation:rise .7s both; }
    .reveal:nth-child(1){ animation-delay:.05s; }
    .reveal:nth-child(2){ animation-delay:.15s; }
    .reveal:nth-child(3){ animation-delay:.25s; }
    .reveal:nth-child(4){ animation-delay:.35s; }
    .reveal:nth-child(5){ animation-delay:.45s; }
    .reveal:nth-child(6){ animation-delay:.55s; }
    @keyframes rise{ from{ opacity:0; transform:translateY(18px); } to{ opacity:1; transform:translateY(0); } }
    /* Reduced-motion: skip the movement entirely, but stay visible. */
    @media (prefers-reduced-motion: reduce){
      .reveal{ animation:none; opacity:1; transform:none; }
    }

    /* ===================== ★ INSTANT BAND CHECK (unique feature) ===================== */
    .checker{
      position:relative; margin:40px auto 0; max-width:880px;
      background:var(--glass); border:1px solid var(--glass-line); border-radius:24px;
      padding:26px 28px; display:grid; grid-template-columns:1.05fr 1fr; gap:24px;
      box-shadow:0 30px 80px -40px rgba(0,0,0,.8); text-align:left;
      overflow:hidden;
      /* 3D stage — a JS mousemove handler tilts the card via --rx/--ry */
      transform:perspective(1300px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg));
      transform-style:preserve-3d;
      transition:transform .55s cubic-bezier(.2,.7,.2,1), box-shadow .4s ease, border-color .4s ease;
      will-change:transform;
    }
    .checker[data-checker-tilt]:hover{
      box-shadow:0 44px 110px -46px rgba(0,0,0,.9), 0 0 0 1px rgba(198,242,78,.22) inset;
    }

    /* three floating gradient ORBS — visible, discrete, drifting depth
       (replaces the old flat aurora that muddied light mode) */
    .orb{
      position:absolute; z-index:0; pointer-events:none; border-radius:50%;
      /* PERF: `mix-blend-mode:screen` was the most expensive thing on this page.
         Blend modes force the browser to read back and re-blend the backdrop,
         which defeats GPU compositing and pushes a full-size repaint onto the
         MAIN thread on every animated frame — cost scales with viewport area, so
         it only became fatal at desktop width (mobile held a steady 60fps).
         Dropped the blend, and cut the blur: these are already soft radial-
         gradients, so a 38px convolution on top was largely redundant. Opacity is
         nudged up to keep the same glow strength without the additive blend. */
      filter:blur(14px); opacity:.85;
      will-change:transform;
    }
    .orb.o1{
      width:340px; height:340px; left:-90px; top:-110px;
      background:radial-gradient(circle at 50% 50%, rgba(139,109,255,.85), rgba(139,109,255,0) 65%);
      animation:orbDrift1 13s ease-in-out infinite alternate;
    }
    .orb.o2{
      width:300px; height:300px; right:-70px; top:20%;
      background:radial-gradient(circle at 50% 50%, rgba(78,224,242,.75), rgba(78,224,242,0) 65%);
      animation:orbDrift2 15s ease-in-out infinite alternate;
    }
    .orb.o3{
      width:360px; height:360px; left:20%; bottom:-140px;
      background:radial-gradient(circle at 50% 50%, rgba(198,242,78,.70), rgba(198,242,78,0) 65%);
      animation:orbDrift3 17s ease-in-out infinite alternate;
    }
    @keyframes orbDrift1{
      0%  { transform:translate(0,0)      scale(1);   }
      100%{ transform:translate(70px,55px) scale(1.15);}
    }
    @keyframes orbDrift2{
      0%  { transform:translate(0,0)       scale(1);   }
      100%{ transform:translate(-55px,45px) scale(1.12);}
    }
    @keyframes orbDrift3{
      0%  { transform:translate(0,0)       scale(1);   }
      100%{ transform:translate(45px,-60px) scale(1.18);}
    }

    /* cursor-following spotlight — JS sets --mx / --my; sits above the orbs */
    .checker-spot{
      position:absolute; inset:0; z-index:0; pointer-events:none;
      background:radial-gradient(340px circle at var(--mx,50%) var(--my,-50%),
                                  rgba(255,255,255,.16), transparent 60%);
      opacity:0; transition:opacity .35s ease;
    }
    .checker:hover .checker-spot{ opacity:1; }

    /* keep every child of the card above the orbs/spotlight */
    .checker > *:not(.orb):not(.checker-spot){ position:relative; z-index:1; transform:translateZ(0); }
    /* lift key foreground pieces off the card plane for a real 3D feel */
    .checker .prem-show{ transform:translateZ(28px); }
    .checker .gauge-wrap{ transform:translateZ(20px); }
    .checker h3{ font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif; font-size:20px; margin-bottom:4px; }
    .checker .hint{ font-size:13px; color:var(--muted); margin-bottom:14px; }
    .checker textarea{
      width:100%; height:190px; resize:none; border-radius:14px; padding:14px;
      background:rgba(0,0,0,.35); border:1px solid var(--glass-line); color:var(--text);
      font-family:'Hanken Grotesk', 'Native Emoji', sans-serif; font-size:15px; line-height:1.5; outline:none;
    }
    .checker textarea:focus{ border-color:var(--lime); }
    .checker .count{ font-size:12px; color:var(--muted); margin-top:8px; text-align:right; }

    /* ===== Premium membership showcase (replaces the band-checker input) ===== */
    .prem-show{ display:flex; flex-direction:column; height:100%; }
    .prem-eyebrow{
      display:inline-flex; align-items:center; gap:7px; align-self:flex-start;
      font-size:11px; font-weight:800; letter-spacing:1.3px; text-transform:uppercase;
      color:#3a2400; background:linear-gradient(135deg,#fde68a,#f59e0b);
      padding:5px 12px; border-radius:99px; margin-bottom:13px;
      box-shadow:0 6px 18px -6px rgba(245,158,11,.6);
    }
    .prem-crown{ font-size:13px; }
    .prem-show h3{ font-size:clamp(26px, 3.1vw, 34px); line-height:1.08; letter-spacing:-1px; margin-bottom:8px; }
    .prem-show .hint{ margin-bottom:16px; font-size:13px; line-height:1.5; }

    /* the headline percentage — the whole pitch in one number */
    .pv-pct{
      position:relative; display:inline-block;
      font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif; font-weight:800;
      background:linear-gradient(120deg,#f59e0b 0%,#ef4444 52%,#ec4899 100%);
      -webkit-background-clip:text; background-clip:text;
      color:transparent; -webkit-text-fill-color:transparent;
    }
    .pv-pct::after{        /* a strike that draws itself under the number */
      content:''; position:absolute; left:-2%; right:-2%; bottom:6%; height:3px; border-radius:2px;
      background:linear-gradient(90deg,#f59e0b,#ec4899);
      transform-origin:left center; opacity:.85;
      animation:pvStrike .8s cubic-bezier(.2,.8,.2,1) backwards; animation-delay:.7s;
    }
    @keyframes pvStrike{ from{ transform:scaleX(0); opacity:0; } }
    .pv-hi{ color:#f59e0b; font-weight:800; font-variant-numeric:tabular-nums; }

    /* ===================== THE VAULT LIST =====================
       Each row is one shelf of the library, drawn as a *lock meter*: the filled
       part is what a free account holds, the hatched remainder is what is being
       withheld. Hovering runs the unlock — the bar surges to 100%, the padlock
       opens and the sub-line swaps to what you'd actually get. Every figure is
       counted from the live library, so the row can be read as a receipt.

       `--c` is the row accent as a bare "r,g,b" triplet (so it can be poured
       into rgba()); `--cl` is its light-theme twin. Everything below reads
       `--ca`, which the light-theme block at the bottom of this file repoints
       at `--cl`. `--free` is the free share of that shelf. */
    .pv-list{ list-style:none; margin:0 0 16px; padding:0; position:relative; display:flex; flex-direction:column; gap:7px; }
    .pv-row{
      --ca:var(--c);
      position:relative; overflow:hidden;
      display:grid; grid-template-columns:auto minmax(0,1fr) auto; align-items:center; column-gap:12px;
      padding:10px 12px 11px; border-radius:14px;
      background:linear-gradient(95deg, rgba(var(--ca),.15), rgba(var(--ca),.03) 62%);
      border:1px solid rgba(var(--ca),.28);
      transform-style:preserve-3d;
      animation:pvRowIn .72s cubic-bezier(.34,1.56,.64,1) both;
      animation-delay:calc(.8s + var(--i) * .12s);
      transition:background .38s ease, border-color .38s ease, box-shadow .38s ease,
                 transform .38s cubic-bezier(.2,.7,.2,1);
    }
    @keyframes pvRowIn{
      0%  { opacity:0; transform:translateX(-30px) rotateY(-12deg) scale(.95); }
      70% { opacity:1; transform:translateX(4px)   rotateY(2deg)   scale(1.02); }
      100%{ opacity:1; transform:translateX(0)     rotateY(0)      scale(1);    }
    }
    /* light sweep — parked off-screen, fired only by the hover */
    .pv-row::after{
      content:''; position:absolute; inset:0; z-index:3; pointer-events:none;
      background:linear-gradient(105deg, transparent 34%, rgba(255,241,196,.55) 50%, transparent 66%);
      transform:translateX(-135%);
    }
    .pv-row:hover::after,
    .pv-row.is-open::after{ transform:translateX(135%); transition:transform .8s cubic-bezier(.3,.55,.3,1); }
    /* `.is-open` is the same state as :hover, driven by JS. A phone has no
       hover, so without it the entire unlock reveal — the whole point of the
       card — would be desktop-only. 01-foundation.js cycles the class through
       the rows on any (hover: none) device. */
    .pv-row:hover,
    .pv-row.is-open{
      background:linear-gradient(95deg, rgba(245,158,11,.22), rgba(245,158,11,.04) 66%);
      border-color:rgba(245,158,11,.65);
      box-shadow:0 18px 40px -18px rgba(245,158,11,.7), inset 0 0 0 1px rgba(245,158,11,.16);
      transform:translateY(-2px) translateZ(12px);
    }

    .pv-ico{
      font-size:19px; width:26px; text-align:center; position:relative; z-index:1;
      transition:transform .35s cubic-bezier(.34,1.56,.64,1);
    }
    .pv-row:hover .pv-ico,
    .pv-row.is-open .pv-ico{ transform:scale(1.24) rotate(-9deg); }
    .pv-body{ min-width:0; position:relative; z-index:1; }

    .pv-head{ display:flex; align-items:baseline; justify-content:space-between; gap:10px; }
    .pv-name{ font-size:13.5px; font-weight:800; color:var(--text); letter-spacing:-.1px; }
    .pv-n{
      font-size:10.5px; font-weight:700; white-space:nowrap; letter-spacing:.3px;
      text-transform:uppercase; color:rgb(var(--ca)); transition:color .35s ease;
    }
    .pv-n b{
      font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif;
      font-size:14.5px; font-weight:800; font-variant-numeric:tabular-nums; letter-spacing:0;
    }
    .pv-row:hover .pv-n,
    .pv-row.is-open .pv-n{ color:#f59e0b; }
    /* "locked" ⇄ "yours" — stacked in one grid cell so the swap can't shift the row */
    .pv-state{ display:inline-grid; vertical-align:baseline; }
    .pv-state i{
      grid-area:1/1; font-style:normal;
      transition:opacity .3s ease, transform .3s ease;
    }
    .pv-state i:last-child{ opacity:0; transform:translateY(4px); }
    .pv-row:hover .pv-state i:first-child,
    .pv-row.is-open .pv-state i:first-child{ opacity:0; transform:translateY(-4px); }
    .pv-row:hover .pv-state i:last-child,
    .pv-row.is-open .pv-state i:last-child{ opacity:1; transform:none; }

    /* the lock meter: solid = yours, hatched = withheld */
    .pv-bar{
      display:block; position:relative; height:5px; margin:7px 0 6px; border-radius:99px; overflow:hidden;
      background:
        repeating-linear-gradient(115deg, rgba(var(--ca),.24) 0 4px, transparent 4px 9px),
        rgba(var(--ca),.10);
    }
    .pv-bar i{
      /* min-width so the Shadowing row's honest 0.5% is still a visible sliver
         rather than nothing at all — the point is "you have almost none of it",
         not "the bar is broken". */
      position:absolute; left:0; top:0; bottom:0; width:var(--free); min-width:4px; border-radius:99px;
      background:linear-gradient(90deg, rgb(var(--ca)), rgba(var(--ca),.7));
      box-shadow:0 0 12px rgba(var(--ca),.75);
      transition:width .65s cubic-bezier(.2,.85,.25,1), background .4s ease, box-shadow .4s ease;
    }
    .pv-row:hover .pv-bar i,
    .pv-row.is-open .pv-bar i{
      width:100%;
      background:linear-gradient(90deg,#fde68a,#f59e0b 55%,#c6f24e);
      box-shadow:0 0 20px rgba(245,158,11,.9);
    }

    /* Two sub-lines stacked in one cell: the state you're in, and the state
       you'd buy. The box is held at two lines and the text centred inside it,
       so a row can't change height when the copy swaps — and rows whose two
       variants wrap differently still line up with each other. */
    .pv-sub{ display:grid; font-size:11.5px; line-height:1.35; min-height:2.7em; }
    .pv-sub > span{ grid-area:1/1; align-self:center; transition:opacity .34s ease, transform .34s ease; }
    .pv-sub-off{ color:var(--muted); }
    .pv-sub-on{ opacity:0; transform:translateY(5px); color:#fde68a; font-weight:700; }
    .pv-row:hover .pv-sub-off,
    .pv-row.is-open .pv-sub-off{ opacity:0; transform:translateY(-5px); }
    .pv-row:hover .pv-sub-on,
    .pv-row.is-open .pv-sub-on{ opacity:1; transform:none; }

    /* the padlock that actually opens */
    .pv-lock{
      position:relative; z-index:1; display:inline-grid; place-items:center;
      width:28px; height:28px; border-radius:50%;
      background:rgba(var(--ca),.12); border:1px solid rgba(var(--ca),.3);
      transition:background .35s ease, border-color .35s ease, box-shadow .35s ease;
    }
    /* PERF: the breathing ring is a pseudo-element fading/scaling, not an animated
       box-shadow — see the note on .rs-new. Five of these sit in the vault list,
       each repainting every frame before this change, on the busiest card on the
       page. Now the compositor owns it. */
    .pv-lock::after{
      content:''; position:absolute; inset:0; border-radius:50%;
      pointer-events:none; z-index:-1;
      box-shadow:0 0 0 6px rgba(245,158,11,.18);
      opacity:0; animation:pvLockBreathe 4.5s ease-in-out infinite;
      animation-delay:calc(1.6s + var(--i) * .45s);
    }
    .pv-lock i{
      grid-area:1/1; font-style:normal; font-size:14px; line-height:1;
      transition:opacity .3s ease, transform .45s cubic-bezier(.34,1.7,.5,1);
    }
    .pv-l-open{ opacity:0; transform:scale(.4) rotate(-28deg); }
    .pv-row:hover .pv-lock,
    .pv-row.is-open .pv-lock{
      background:rgba(245,158,11,.24); border-color:rgba(245,158,11,.7);
      box-shadow:0 0 0 5px rgba(245,158,11,.14), 0 0 18px rgba(245,158,11,.5);
    }
    .pv-row:hover .pv-l-shut,
    .pv-row.is-open .pv-l-shut{ opacity:0; transform:scale(.5) rotate(20deg); }
    .pv-row:hover .pv-l-open,
    .pv-row.is-open .pv-l-open{ opacity:1; transform:scale(1.16) rotate(0); }
    @keyframes pvLockBreathe{
      0%,88%,100%{ opacity:0; transform:scale(.86); }
      94%        { opacity:1; transform:scale(1); }
    }

    /* price line under the CTA — the anchor, in the smallest unit that's true */
    .pv-price{
      margin-top:11px; font-size:11.5px; line-height:1.45; color:var(--muted);
    }
    .pv-price b{ color:var(--text); font-weight:800; }

    /* ---- narrow card: the vault list keeps its shape ----
       Below 920px the checker collapses to one column, so the rows lose ~90px
       of width and the longer sub-lines run to three. `min-height` is a floor,
       not a cap, so the mixed 2- and 3-line rows came out at 95px and 116px
       alternately — raise the floor to three lines and they line up again. */
    @media (max-width:920px){
      .pv-sub{ min-height:4.05em; font-size:11px; }
      .pv-meter{ width:100%; }
    }
    @media (max-width:420px){
      .pv-row{ column-gap:9px; padding:10px 10px 11px; }
      .pv-ico{ font-size:17px; width:21px; }
      .pv-name{ font-size:12.5px; }
      .pv-lock{ width:25px; height:25px; }
    }

    /* ---- light theme: repoint every row accent at its darker twin, and swap
       the golds that vanish on white. Without this the cyan and amber rows are
       a pale smear on a pale card. ---- */
    :root[data-theme="light"] .pv-row{ --ca:var(--cl); }
    :root[data-theme="light"] .pv-sub-on{ color:#b45309; }
    :root[data-theme="light"] .pv-hi,
    :root[data-theme="light"] .pv-meter-val b,
    :root[data-theme="light"] .pv-meter-tip{ color:#b45309; }
    :root[data-theme="light"] .pv-meter-tip::before{ background:#b45309; }
    :root[data-theme="light"] .disclaimer b{ color:#047857; }
    :root[data-theme="light"] .disclaimer{ background:rgba(5,150,105,.07); border-color:rgba(5,150,105,.24); }
    :root[data-theme="light"] .pv-row::after{
      background:linear-gradient(105deg, transparent 34%, rgba(255,255,255,.85) 50%, transparent 66%);
    }
    /* --lime is #65a30d in light: 3.09:1 on the near-white card, under the 4.5
       floor for the ticker's 12px figure. Deepen just this one number. */
    :root[data-theme="light"] .live-count{ color:#3f6212; }

    /* gold call-to-action — press-in 3D depth + moving sheen */
    .prem-show .prem-cta{
      position:relative; overflow:hidden;
      align-self:flex-start; margin-top:auto;
      font-size:15px; font-weight:800; padding:14px 28px; border-radius:14px;
      color:#1a1300; border:none; cursor:pointer;
      display:inline-flex; align-items:center; gap:9px;
      background:linear-gradient(135deg,#fde68a,#f59e0b 55%,#c6f24e); background-size:200% 100%;
      box-shadow:
        0 14px 34px -12px rgba(245,158,11,.85),
        inset 0 1px 0 rgba(255,255,255,.55),
        inset 0 -2px 0 rgba(120,60,0,.18);
      animation:premCtaGlow 3s ease-in-out infinite;
      transition:transform .18s cubic-bezier(.2,.7,.2,1), box-shadow .18s ease;
      transform:translateZ(38px);
    }
    /* diagonal light sheen that sweeps across the button */
    .prem-cta-sheen{
      position:absolute; inset:0; pointer-events:none;
      background:linear-gradient(120deg, transparent 30%, rgba(255,255,255,.7) 50%, transparent 70%);
      transform:translateX(-140%);
      animation:premCtaSheen 3.8s ease-in-out infinite;
      animation-delay:1.5s;
    }
    @keyframes premCtaGlow{
      0%,100%{ background-position:0% 0; }
      50%    { background-position:100% 0; }
    }
    @keyframes premCtaSheen{
      0%   { transform:translateX(-140%); }
      45%  { transform:translateX(140%); }
      100% { transform:translateX(140%); }
    }
    .prem-show .prem-cta:hover{
      transform:translateZ(38px) translateY(-3px) scale(1.03);
      box-shadow:
        0 22px 44px -14px rgba(245,158,11,.95),
        0 0 0 6px rgba(245,158,11,.14),
        inset 0 1px 0 rgba(255,255,255,.6);
    }
    .prem-show .prem-cta:active{
      transform:translateZ(38px) translateY(0) scale(.98);
      box-shadow:0 8px 18px -8px rgba(245,158,11,.75), inset 0 2px 8px rgba(120,60,0,.25);
    }
    .prem-cta-arrow{ transition:transform .25s ease; }
    .prem-show .prem-cta:hover .prem-cta-arrow{ transform:translateX(6px); }

    /* floating gold sparkles */
    .prem-spark{ position:absolute; width:5px; height:5px; border-radius:50%; background:#fde68a; box-shadow:0 0 8px #fde68a; opacity:0; pointer-events:none; animation:premSpark 4s ease-in infinite; }
    .prem-spark.ps1{ left:12%; bottom:0; animation-delay:.3s; }
    .prem-spark.ps2{ left:46%; bottom:6px; animation-delay:1.5s; background:#c6f24e; box-shadow:0 0 8px #c6f24e; }
    .prem-spark.ps3{ left:74%; bottom:2px; animation-delay:2.4s; }
    .prem-spark.ps4{ left:90%; bottom:8px; animation-delay:3.1s; background:#c6f24e; box-shadow:0 0 8px #c6f24e; }
    @keyframes premSpark{ 0%{ opacity:0; transform:translateY(0) scale(.5); } 15%{ opacity:1; } 100%{ opacity:0; transform:translateY(-92px) scale(1.1); } }

    /* gauge caption */
    .gauge-cap{ margin-top:22px; font-size:14px; color:var(--muted); text-align:center; }
    .gauge-cap b{ color:var(--lime); }

    @media (prefers-reduced-motion: reduce){
      .checker, .pv-row, .pv-lock, .pv-pct::after, .prem-show .prem-cta,
      .prem-cta-sheen, .prem-spark, .orb, .checker-spot,
      .live-ticker .live-dot, .live-count, .pv-meter-bar i{ animation:none !important; }
      .checker{ transform:none !important; }
      /* the rows animate IN, so their hidden start state has to be released here
         or the whole vault list reads as an empty hole (see the entrance-animation
         rule: the hidden state lives in the keyframes, never on the element). */
      .pv-row{ opacity:1; transform:none; }
      .pv-pct::after{ transform:scaleX(1); opacity:.85; }
      .pv-meter-bar i{ width:var(--pv-meter-w); }
    }

    /* gauge column — vertical stack that FILLS the card height so the left side
       reads as substantial (not a small dial floating in whitespace).
       space-between distributes ticker / gauge / caption+stars evenly. */
    .gauge-wrap{
      position:relative; display:flex; flex-direction:column;
      justify-content:center; align-items:center; text-align:center;
      /* sit as a tight, centered cluster beside the taller perks list rather than
         stretching to the full card height (which left the dial jammed to the top
         with a large void below it once the perks list grew) */
      align-self:center; padding:6px 0 4px; gap:22px;
    }
    /* live prepping-now ticker — fills the top, adds "this is real" energy */
    .live-ticker{
      display:inline-flex; align-items:center; gap:8px;
      padding:6px 12px 6px 10px; border-radius:99px;
      background:rgba(198,242,78,.10); border:1px solid rgba(198,242,78,.28);
      font-size:12px; font-weight:600; color:var(--text);
      box-shadow:0 6px 20px -10px rgba(198,242,78,.35);
    }
    .live-dot{
      position:relative;
      width:8px; height:8px; border-radius:50%; background:#c6f24e;
    }
    .live-dot::after{
      content:''; position:absolute; inset:0; border-radius:50%;
      pointer-events:none; background:rgba(198,242,78,.7);
      animation:livePulse 1.6s ease-out infinite;
    }
    /* The expanding ring rides a pseudo-element's transform + opacity instead of
       an animated box-shadow, so it never repaints. Same reasoning as .rs-new. */
    @keyframes livePulse{
      0%   { opacity:.7; transform:scale(1); }
      70%  { opacity:0;  transform:scale(3.5); }
      100% { opacity:0;  transform:scale(3.5); }
    }
    .live-count{ font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif; font-weight:800; color:var(--lime); font-variant-numeric:tabular-nums; }
    .live-lbl{ color:var(--muted); }

    /* ===== THE ASCENT — the band-climb screen that replaced the dial =====
       A self-contained dark "device screen": its own gradient background so it
       reads identically in light and dark. A comet climbs a band-scale chart to
       the goal and slams (painted on the canvas by 30-band-ascent.js); the
       number + label are DOM overlays so the brand font stays crisp. Sized with
       `min()` for the same reason the dial was — as the sole item of a `1fr`
       track, max-width is ignored during intrinsic sizing and it would overhang
       a phone. `aspect-ratio` keeps a stable height (the canvas is out of flow,
       so it can't fight the ratio). */
    .ascent{
      position:relative; width:min(460px, 100%); aspect-ratio:340/210; margin:0 auto;
      border-radius:22px; overflow:hidden; isolation:isolate;
      background:
        radial-gradient(120% 92% at 16% 112%, rgba(139,109,255,.40), transparent 55%),
        radial-gradient(115% 90% at 96% -12%, rgba(198,242,78,.26),  transparent 50%),
        linear-gradient(158deg, #16163a 0%, #0b0b1f 52%, #060610 100%);
      /* Static brand halo ties the dark "device screen" to the light page — the
         same violet + lime as the aurora orbs behind it, so it reads as part of
         the scene, not a black rectangle dropped on top. All the MOTION comes from
         the compositor-only pulse (::before) and sheen (::after) below — never from
         animating this shadow, which repaints every frame and janks weak GPUs. */
      box-shadow:
        inset 0 0 0 1px rgba(255,255,255,.10),
        inset 0 1px 0 0 rgba(255,255,255,.12),
        0 34px 80px -34px rgba(96,64,208,.80),
        0 0 66px -16px rgba(139,109,255,.50);
    }
    /* breathing lime glow where the comet tops out at the goal — opacity only */
    .ascent::before{
      content:''; position:absolute; inset:0; z-index:0; pointer-events:none;
      background:radial-gradient(88% 62% at 80% -6%, rgba(198,242,78,.30), transparent 60%);
      animation:ascentPulse 5s ease-in-out infinite;
    }
    @keyframes ascentPulse{ 0%,100%{ opacity:.45; } 50%{ opacity:1; } }
    .ascent-cv{ position:absolute; inset:0; display:block; width:100%; height:100%; z-index:1; }
    /* live screen reflection — a soft sheen sweeps the glass (transform-only) */
    .ascent::after{
      content:''; position:absolute; top:-12%; bottom:-12%; left:0; width:40%; z-index:3;
      pointer-events:none; transform:translateX(-280%) skewX(-14deg);
      background:linear-gradient(100deg, transparent, rgba(255,255,255,.08) 42%, rgba(255,255,255,.18) 52%, rgba(255,255,255,.08) 62%, transparent);
      animation:ascentSheen 6.5s ease-in-out infinite; animation-delay:1.1s;
    }
    @keyframes ascentSheen{ 0%,64%{ transform:translateX(-280%) skewX(-14deg); opacity:0; } 68%{ opacity:1; } 84%{ transform:translateX(600%) skewX(-14deg); opacity:1; } 86%,100%{ opacity:0; } }
    /* readout — top-left, over the empty quadrant the climb never enters */
    .ascent-hud{ position:absolute; left:20px; top:17px; z-index:2; pointer-events:none; text-align:left; }
    .ascent-num{
      font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif;
      font-size:70px; font-weight:800; line-height:.88; letter-spacing:-2.8px;
      color:#8b6dff; font-variant-numeric:tabular-nums;
      text-shadow:0 0 30px rgba(139,109,255,.55);
      transform-origin:left center; will-change:transform, color;
    }
    .ascent-lbl{ margin-top:9px; font-size:13px; font-weight:600; color:rgba(255,255,255,.55); }
    .ascent-lbl b{ color:rgba(255,255,255,.88); font-weight:800; }
    /* tiny "live" chip, top-right */
    .ascent-tag{
      position:absolute; right:14px; top:14px; z-index:2; pointer-events:none;
      display:inline-flex; align-items:center; gap:5px; padding:4px 10px 4px 8px; border-radius:99px;
      background:rgba(198,242,78,.14); border:1px solid rgba(198,242,78,.32);
      font-size:9.5px; font-weight:800; letter-spacing:1px; text-transform:uppercase; color:#c6f24e;
      backdrop-filter:blur(4px);
    }
    .ascent-tag i{
      width:6px; height:6px; border-radius:50%; background:#c6f24e;
      box-shadow:0 0 8px #c6f24e; animation:ascentTag 1.6s ease-out infinite;
    }
    @keyframes ascentTag{ 0%,100%{ opacity:1; } 50%{ opacity:.3; } }
    @media (prefers-reduced-motion: reduce){
      .ascent-tag i{ animation:none; }
      .ascent::before{ animation:none; opacity:.8; }
      .ascent::after{ animation:none; display:none; }
    }

    /* ===== THE VAULT METER — the same story as the rows, totalled =====
       67 of 1,931 is 3.5%, which is a sliver you cannot honestly draw at a
       readable size: the fill gets its true width from `--pv-meter-w`, and the
       "you are here" flag is what makes the sliver legible. */
    .pv-meter{
      --pv-meter-w:3.5%;
      width:min(300px, 100%); margin-top:2px; text-align:left;
      padding:12px 14px 11px; border-radius:15px;
      background:rgba(245,158,11,.07); border:1px solid rgba(245,158,11,.24);
    }
    .pv-meter-top{ display:flex; align-items:baseline; justify-content:space-between; gap:10px; }
    .pv-meter-lbl{ font-size:10.5px; font-weight:800; letter-spacing:.9px; text-transform:uppercase; color:var(--muted); }
    .pv-meter-val{ font-size:12px; color:var(--muted); font-variant-numeric:tabular-nums; }
    .pv-meter-val b{
      font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif;
      font-size:16px; font-weight:800; color:#f59e0b;
    }
    .pv-meter-bar{
      position:relative; height:9px; margin:9px 0 22px; border-radius:99px; overflow:visible;
      background:
        repeating-linear-gradient(115deg, rgba(245,158,11,.22) 0 4px, transparent 4px 9px),
        rgba(245,158,11,.10);
      box-shadow:inset 0 0 0 1px rgba(245,158,11,.2);
    }
    /* `backwards`, not `both`: the true width lives on the ELEMENT and the
       keyframe only supplies the 0-width start. An animation that never runs —
       a tab that loaded in the background freezes it at time 0 — would
       otherwise leave this reading 0%, and this bar is a factual claim about
       the account, not decoration. Same reasoning for the flag and the strike. */
    .pv-meter-bar i{
      position:absolute; left:0; top:0; bottom:0; width:var(--pv-meter-w); border-radius:99px;
      background:linear-gradient(90deg,#fde68a,#f59e0b);
      box-shadow:0 0 14px rgba(245,158,11,.85);
      animation:pvMeterFill 1.5s cubic-bezier(.2,.85,.25,1) backwards; animation-delay:.9s;
    }
    @keyframes pvMeterFill{ from{ width:0; } }
    /* the flag is pinned to the end of the fill, so it points at the real 3.5% */
    .pv-meter-tip{
      position:absolute; top:14px; left:var(--pv-meter-w); transform:translateX(-6px);
      font-size:9.5px; font-weight:800; letter-spacing:.4px; text-transform:uppercase;
      color:#f59e0b; white-space:nowrap;
      animation:pvTipIn .5s ease backwards; animation-delay:2.2s;
    }
    .pv-meter-tip::before{
      content:''; position:absolute; left:2px; top:-9px; width:1.5px; height:8px;
      background:#f59e0b; border-radius:1px;
    }
    @keyframes pvTipIn{ from{ opacity:0; transform:translateX(-6px) translateY(-4px); } to{ opacity:1; transform:translateX(-6px) translateY(0); } }
    .pv-meter-foot{ font-size:11px; line-height:1.4; color:var(--muted); }
    .pv-meter-foot b{ color:var(--text); font-weight:800; }
    .criteria{ margin-top:30px; text-align:left; }
    .crit{ margin-bottom:11px; }
    .crit .row{ display:flex; justify-content:space-between; font-size:12.5px; margin-bottom:4px; color:var(--muted); }
    .crit .bar{ height:7px; border-radius:99px; background:rgba(255,255,255,.08); overflow:hidden; }
    .crit .fill{ height:100%; width:0%; border-radius:99px; background:linear-gradient(90deg,var(--violet),var(--lime)); transition:width .6s ease; }
    /* the honesty strip — naming what Premium does NOT gate is the one line on
       this card that makes the other numbers believable, so it gets real
       contrast instead of the old 0.8-opacity footnote treatment. */
    .disclaimer{
      grid-column:1 / -1; margin-top:18px; padding:11px 16px; border-radius:14px;
      font-size:11.5px; line-height:1.55; color:var(--muted); text-align:center;
      background:rgba(52,211,153,.07); border:1px solid rgba(52,211,153,.22);
    }
    .disclaimer b{ color:#34d399; font-weight:800; letter-spacing:.2px; }

    /* ============================ SECTION HELPERS ============================ */
    section.block{ padding:80px 0 0; }
    .eyebrow{ text-align:center; color:var(--lime); font-weight:700; font-size:13px; letter-spacing:1.5px; text-transform:uppercase; margin-bottom:12px; }
    /* A bare `h2{}` here applied to EVERY <h2> in the app, so any section heading
       on any route came out as a 46px centred hero unless it happened to override
       both properties. That silently broke headings on the writing, articles and
       word-bank surfaces (a short title centred inside a shrink-to-fit box reads
       as randomly indented). The homepage keeps exactly what it had — its
       sections are `section.block > .wrap` — and everything else now gets a
       section heading it can still override.

       Note there is no `text-align` here on purpose: alignment is inherited, so
       a heading centres inside a centred head (/articles, /wordbank) and sits
       left inside a left one (/writing/practice) without either having to say
       so. Forcing `left` here was the same mistake as forcing `center`. */
    h2{
      font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif; font-weight:800;
      font-size:23px; line-height:1.25; letter-spacing:-.5px;
    }
    /* Specificity is deliberate: (0,1,1), the same weight as the per-surface
       rules like `.articles-head h2` — and this file loads early, so those still
       win on source order exactly as they did against the old bare `h2{}`.
       Anything stronger here would silently take the heading back off them. */
    .wrap h2{
      font-size:clamp(30px,4.5vw,46px); text-align:center; letter-spacing:-1px; line-height:1.05;
    }
    .sub{ text-align:center; color:var(--muted); max-width:560px; margin:14px auto 0; font-size:17px; }

    /* ============================ SKILLS GRID ============================ */
    .grid{ display:grid; grid-template-columns:repeat(3,1fr); gap:18px; margin-top:44px; perspective:1100px; }
    .tile{
      background:var(--glass); border:1px solid var(--glass-line); border-radius:var(--radius);
      padding:26px; position:relative; cursor:pointer; overflow:hidden;
      transform-style:preserve-3d; will-change:transform;
      transition:opacity .7s ease, filter .7s ease, transform .3s cubic-bezier(.2,.7,.2,1),
                 border-color .3s ease, background .3s ease, box-shadow .3s ease;
    }
    /* keep content above the glow/border layers */
    .tile > *{ position:relative; z-index:1; }
    .tile:hover{ border-color:rgba(198,242,78,.35); background:rgba(198,242,78,.04);
                 box-shadow:0 26px 60px -28px rgba(0,0,0,.85); }

    /* cursor-tracking spotlight glow */
    .tile::before{
      content:""; position:absolute; inset:0; border-radius:inherit; z-index:0; pointer-events:none;
      opacity:0; transition:opacity .35s ease;
      background:radial-gradient(240px circle at var(--mx,50%) var(--my,50%), rgba(198,242,78,.20), transparent 62%);
    }
    .tile:hover::before{ opacity:1; }

    /* animated gradient border that traces the edge on hover */
    @property --ang{ syntax:'<angle>'; initial-value:0deg; inherits:false; }
    .tile::after{
      content:""; position:absolute; inset:0; border-radius:inherit; z-index:2; pointer-events:none;
      padding:1.5px; opacity:0; transition:opacity .4s ease;
      background:conic-gradient(from var(--ang,0deg), #c6f24e, #4ee0f2, #8b6dff, #c6f24e);
      -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
      -webkit-mask-composite:xor; mask-composite:exclude;
    }
    .tile:hover::after{ opacity:1; animation:spinborder 3s linear infinite; }
    @keyframes spinborder{ to{ --ang:360deg; } }

    /* Apple-style app-icon: rounded square with a gradient + monoline SVG inside.
       Subtle inner highlight + drop shadow gives them iOS depth. */
    .tile .ico{
      width:60px; height:60px; border-radius:17px;
      display:inline-grid; place-items:center; margin-bottom:18px;
      position:relative;
      box-shadow:
        0 12px 28px -10px rgba(0,0,0,.55),
        inset 0 1px 0 rgba(255,255,255,.30),
        inset 0 -1px 0 rgba(0,0,0,.15);
      animation:floaty 3.6s ease-in-out infinite;
      transition:transform .35s cubic-bezier(.34,1.56,.64,1);
    }
    /* glossy highlight on the top half of the icon */
    .tile .ico::before{
      content:""; position:absolute; inset:1px; border-radius:16px;
      background:linear-gradient(180deg, rgba(255,255,255,.22), transparent 55%);
      pointer-events:none;
    }
    .tile .ico svg{
      width:32px; height:32px; color:#fff; stroke-width:1.8;
      position:relative; z-index:1;
      filter:drop-shadow(0 1px 1px rgba(0,0,0,.18));
    }
    .tile:hover .ico{
      animation:none;
      transform:scale(1.1) rotate(-5deg);
      box-shadow:
        0 18px 38px -12px rgba(0,0,0,.6),
        inset 0 1px 0 rgba(255,255,255,.35),
        inset 0 -1px 0 rgba(0,0,0,.15);
    }
    @keyframes floaty{ 0%,100%{ transform:translateY(0); } 50%{ transform:translateY(-6px); } }

    /* per-tile gradient — distinct so each skill is instantly recognisable */
    .ico-reading   { background:linear-gradient(135deg, #c6f24e, #84cc16); }
    .ico-listening { background:linear-gradient(135deg, #4ee0f2, #06b6d4); }
    .ico-articles  { background:linear-gradient(135deg, #fbbf24, #f97316); }
    .ico-writing   { background:linear-gradient(135deg, #8b6dff, #6d28d9); }
    /* Shadowing replaced the Speaking tile, but the gradient stayed behind on the
       retired `.ico-speaking` class — so Shadowing was the one tile on the grid
       with no fill at all: a pale glyph on a white plate, invisible in light
       theme. It inherits the slot (and the hue) its predecessor owned. */
    .ico-shadowing { background:linear-gradient(135deg, #fb7185, #e11d48); }
    .ico-vocab     { background:linear-gradient(135deg, #34d399, #0d9488); }
    /* Dark glyph on the bright plates. This rule used to read
       `.ico-reading svg, .ico-articles svg` — specificity (0,1,1), which loses
       to the base `.tile .ico svg{ color:#fff }` at (0,2,1), so it never applied
       and every glyph on the row rendered white. Measured white-on-gradient
       contrast before the fix: reading 1.30, listening 1.58, articles 1.67,
       vocab 1.92 — the Reading icon was effectively invisible on its own tile.
       Matching the base selector's specificity and sitting after it fixes all
       five; Writing keeps white because its violet plate is dark enough (3.67
       against the light stop, 7.10 against the dark one). */
    .tile .ico-reading svg,
    .tile .ico-listening svg,
    .tile .ico-articles svg,
    .tile .ico-vocab svg,
    .tile .ico-shadowing svg{ color:#14080d; }

    /* 3D fly-up entrance for tiles (overrides the generic reveal).
       Base state is VISIBLE — `.in` only PLAYS the entrance. A hidden base would
       strand these tiles invisible whenever the JS that adds `.in` doesn't run. */
    .grid .tile[data-reveal]{ opacity:1; filter:none; transform:none; transform-origin:center bottom; }
    .grid .tile[data-reveal].in{ animation:tileFlyUp .7s cubic-bezier(.2,.7,.2,1) both; }
    @keyframes tileFlyUp{
      from{ opacity:0; filter:blur(6px); transform:perspective(1100px) rotateX(-14deg) translateY(46px); }
      to{ opacity:1; filter:blur(0); transform:perspective(1100px) rotateX(0deg) translateY(0); }
    }

    .tile h4{ font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif; font-size:21px; margin-bottom:7px; }
    .tile p{ color:var(--muted); font-size:14.5px; }
    .tile .flag{ position:absolute; top:18px; right:18px; font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.5px; padding:4px 9px; border-radius:7px; z-index:3; }
    .flag.soon{ background:var(--violet); color:#fff; }
    .flag.free{ background:var(--lime); color:#0a0b10; }

    /* ============================ RECENT WRITING SAMPLES (homepage preview) ============================
       Premium sample cards: media area (Task 1 = universal chart art / Task 2 = essay art),
       gradient band badge, 3D tilt on hover, animated emerald edge, magnetic arrow, pulsing NEW pill. */
    .recent-samples-section{ padding-top:40px; }
    .recent-samples-section .rs-head{ text-align:center; margin-bottom:34px; }
    .recent-samples-section .rs-head h2{
      font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif;
      font-size:clamp(28px, 4.5vw, 42px); letter-spacing:-1px;
      margin:6px 0 10px;
      background:linear-gradient(120deg, var(--text) 0%, #34d399 55%, #c6f24e 100%);
      -webkit-background-clip:text; background-clip:text;
      -webkit-text-fill-color:transparent;
    }
    :root[data-theme="light"] .recent-samples-section .rs-head h2{
      background:linear-gradient(120deg, #0f172a 0%, #047857 55%, #4f7d18 100%);
      -webkit-background-clip:text; background-clip:text;
      -webkit-text-fill-color:transparent;
    }
    .recent-samples-section .rs-head .sub{ color:var(--muted); max-width:580px; margin:0 auto; }

    .rs-grid{
      display:grid; grid-template-columns:repeat(auto-fill, minmax(300px, 1fr));
      gap:20px;
      perspective:1200px;
    }

    /* ===== TWO LANES: Task 1 on one row, Task 2 on another ==================
       Each lane is a horizontal drag/scroll snap carousel with a gentle
       auto-drift (JS), a staggered entrance, edge-fade masking and a themed
       scrollbar. Grouping the cards by task keeps every row homogeneous so the
       heights line up and the section reads as two clean, alive shelves. */
    .rs-lanes{ display:flex; flex-direction:column; gap:34px; }
    .rs-lane{ position:relative; }
    .rs-lane[hidden]{ display:none !important; }

    .rs-lane-head{
      display:flex; align-items:center; gap:12px; flex-wrap:wrap;
      margin:0 0 12px; padding:0 4px;
    }
    .rs-lane-tag{
      font-size:11px; font-weight:800; letter-spacing:.6px; text-transform:uppercase;
      padding:5px 12px; border-radius:8px; color:#fff; white-space:nowrap;
    }
    .rs-lane-tag.t1{ background:linear-gradient(135deg,#60a5fa,#3b82f6); box-shadow:0 6px 16px -6px rgba(59,130,246,.6); }
    .rs-lane-tag.t2{ background:linear-gradient(135deg,#6ee7b7,#10b981); color:#04150d; box-shadow:0 6px 16px -6px rgba(16,185,129,.6); }
    .rs-lane-name{
      font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif; font-weight:800;
      font-size:clamp(16px,2.1vw,20px); letter-spacing:-.3px; color:var(--text);
    }
    .rs-lane-count{ font-size:12.5px; color:var(--muted); font-weight:600; }
    .rs-lane-hint{
      margin-left:auto; display:none; align-items:center; gap:6px;
      font-size:12px; font-weight:700; letter-spacing:.2px; color:var(--muted);
    }
    .rs-lane.rs-scrollable .rs-lane-hint{ display:inline-flex; }
    .rs-lane-hint-a{ display:inline-block; animation:rs-hintArrow 1.6s ease-in-out infinite; }
    @keyframes rs-hintArrow{ 0%,100%{ transform:translateX(0); opacity:.65; } 50%{ transform:translateX(6px); opacity:1; } }

    /* The scrolling row itself */
    .rs-row{
      display:flex; gap:20px;
      overflow-x:auto; overflow-y:hidden;
      scroll-snap-type:x proximity;
      padding:16px 8px 42px;          /* room so hover lift + shadow aren't clipped */
      scroll-padding-left:8px;
      cursor:grab;
      perspective:1200px;
      scrollbar-width:thin; scrollbar-color:#34d399 transparent;
      /* Directional edge-fade: JS toggles .rs-fl / .rs-fr so a side only fades
         when there's actually hidden content that way (the first & last cards
         never look clipped). */
      --fL:0px; --fR:0px;
      -webkit-mask-image:linear-gradient(90deg, transparent 0, #000 var(--fL), #000 calc(100% - var(--fR)), transparent 100%);
              mask-image:linear-gradient(90deg, transparent 0, #000 var(--fL), #000 calc(100% - var(--fR)), transparent 100%);
    }
    .rs-row.rs-fl{ --fL:44px; }
    .rs-row.rs-fr{ --fR:58px; }
    .rs-lane[data-task="t1"] .rs-row{ scrollbar-color:#60a5fa transparent; }
    .rs-row.rs-grabbing{ cursor:grabbing; scroll-snap-type:none; scroll-behavior:auto; }
    .rs-row::-webkit-scrollbar{ height:8px; }
    .rs-row::-webkit-scrollbar-track{ background:transparent; }
    .rs-row::-webkit-scrollbar-thumb{
      background:linear-gradient(90deg,#10b981,#34d399); border-radius:99px;
    }
    .rs-lane[data-task="t1"] .rs-row::-webkit-scrollbar-thumb{
      background:linear-gradient(90deg,#3b82f6,#60a5fa);
    }

    /* Cards become fixed-width shelf items; ~one card on phones. */
    .rs-row > .rs-card{
      flex:0 0 clamp(280px, 82vw, 336px);
      scroll-snap-align:start;
      /* horizontal entrance — slide in from the right, staggered by --i */
      animation-name:rs-cardInX;
    }
    /* don't let the pointer-drag select text inside the cards */
    .rs-row.rs-grabbing, .rs-row.rs-grabbing *{ user-select:none; }
    /* give the (imageless) Task 2 cards a bit more presence on their shelf */
    .rs-row > .rs-card.rs-textcard{ min-height:236px; }

    @media (prefers-reduced-motion: reduce){
      .rs-lane-hint-a{ animation:none; }
      .rs-row{ scroll-behavior:auto; }
    }
    @media (max-width:560px){
      .rs-row{ -webkit-mask-image:none; mask-image:none; }
    }

    /* CARD — premium sample card with:
       • Media area with the universal Task 1 chart art (or Task 2 essay art)
       • Prominent gradient BAND badge floating over the media
       • Static thin emerald border that brightens on hover
       • Animated bar that traces along the TOP edge on hover
       • Subtle 3D tilt so the card feels physical
       • Magnetic arrow that slides on hover
       • Reveal-on-scroll staggered slide-up (see the keyframes below) */
    .rs-card{
      position:relative; cursor:pointer; isolation:isolate;
      display:flex; flex-direction:column; min-height:380px;
      border-radius:20px;
      text-decoration:none !important; color:var(--text) !important;
      background:linear-gradient(180deg, rgba(24,20,38,.88), rgba(18,14,28,.78));
      border:1px solid rgba(52,211,153,.22);
      transform-style:preserve-3d;
      transition:
        transform .55s cubic-bezier(.22,.61,.36,1),
        box-shadow .45s cubic-bezier(.22,.61,.36,1),
        border-color .35s ease;
      overflow:hidden;
      animation:rs-cardIn .55s cubic-bezier(.22,.61,.36,1) backwards;
      animation-delay:calc(var(--i, 0) * 80ms);
    }

    /* ENTRANCE — the hidden start state lives INSIDE the keyframes, never in
       the base rule.

       This used to be `opacity:0` on .rs-card flipped to 1 by a JS-added
       `.rs-in`, which made every card's very existence depend on an
       IntersectionObserver callback arriving. When one didn't, the shelf
       rendered as a tall empty hole with a lane header floating above it —
       which is exactly what it did on tablet and phone. Now the card's resting
       state IS visible: if nothing ever tells it to reveal (dead observer, a
       renderer that reports a 0px viewport, JS that threw earlier in the file)
       the animation simply plays and ends visible.

       `backwards`, not `both`: a `both` fill keeps applying the `to` transform
       forever, which would out-rank and silently kill the :hover lift below.
       With `backwards` the fill only covers the stagger delay, and once the
       entrance is done the card is back on its own base style. */
    @keyframes rs-cardIn{
      from{ opacity:0; transform:translateY(22px); }
      to  { opacity:1; transform:none; }
    }
    @keyframes rs-cardInX{
      from{ opacity:0; transform:translateX(48px) translateY(8px); }
      to  { opacity:1; transform:none; }
    }
    /* JS holds the entrance until the shelf is actually looked at. It is a
       PAUSE, not a hidden state — every release path can fail and the card
       still finishes visible on its own. See revealCards() in
       js/modules/writing/06-writing-samples.js. */
    .rs-hold > .rs-card{ animation-play-state:paused; }
    /* Reduced motion: no entrance at all, so the hold can never apply either.
       `.rs-row > .rs-card` is listed explicitly because it out-specifies a bare
       `.rs-card` and would otherwise keep re-declaring the animation name. */
    @media (prefers-reduced-motion: reduce){
      .rs-card, .rs-row > .rs-card, .rs-hold > .rs-card{ animation:none; }
    }
    :root[data-theme="light"] .rs-card{
      background:#ffffff;
      border-color:rgba(4,120,87,.18);
      box-shadow:0 6px 20px -10px rgba(30,40,80,.12);
    }

    /* task-tinted border accent */
    .rs-card.t1{ border-color:rgba(96,165,250,.35); }
    .rs-card.t2{ border-color:rgba(52,211,153,.32); }

    /* Animated bar traces the TOP edge on hover */
    .rs-card::before{
      content:""; position:absolute; top:0; left:0; height:2px; width:100%;
      background:linear-gradient(90deg, transparent 0%, #10b981 20%, #c6f24e 60%, transparent 100%);
      transform:scaleX(0); transform-origin:left center;
      transition:transform .65s cubic-bezier(.22,.61,.36,1);
      pointer-events:none; z-index:3;
    }
    .rs-card:hover::before{ transform:scaleX(1); }
    .rs-card.t1::before{
      background:linear-gradient(90deg, transparent 0%, #60a5fa 20%, #c6f24e 60%, transparent 100%);
    }

    /* Soft glow underneath on hover */
    .rs-card::after{
      content:""; position:absolute; inset:-1px; border-radius:inherit;
      background:radial-gradient(60% 40% at 50% 110%, rgba(16,185,129,.25), transparent 70%);
      opacity:0; transition:opacity .45s ease;
      pointer-events:none; z-index:-1;
    }
    .rs-card.t1::after{
      background:radial-gradient(60% 40% at 50% 110%, rgba(59,130,246,.30), transparent 70%);
    }
    .rs-card:hover::after{ opacity:1; }

    /* Premium hover — the card LIFTS toward you (no 3D skew) with a blooming
       shadow and a soft top-edge highlight. The cursor-tracked .rs-glow below
       does the rest. The entrance animation owns `transform` while it runs, so
       a hover mid-entrance is ignored and the lift takes over once it ends. */
    .rs-card:hover{
      border-color:rgba(16,185,129,.6);
      box-shadow:
        0 34px 80px -30px rgba(16,185,129,.5),
        0 14px 34px -18px rgba(0,0,0,.55),
        inset 0 1px 0 rgba(255,255,255,.08);
    }
    /* Own snappy spring transition on hover (no entrance-stagger delay), and a
       gentle overshoot so the lift feels alive rather than mechanical. */
    .rs-card:hover{
      transform:translateY(-12px) scale(1.022);
      transition:
        transform .42s cubic-bezier(.34,1.38,.5,1),
        box-shadow .38s ease,
        border-color .3s ease;
    }
    .rs-card.t1:hover{
      border-color:rgba(96,165,250,.75);
      box-shadow:
        0 34px 80px -30px rgba(59,130,246,.5),
        0 14px 34px -18px rgba(0,0,0,.55),
        inset 0 1px 0 rgba(255,255,255,.08);
    }
    :root[data-theme="light"] .rs-card:hover{
      box-shadow:
        0 28px 60px -26px rgba(16,185,129,.32),
        0 12px 28px -16px rgba(30,40,80,.2),
        inset 0 1px 0 rgba(255,255,255,.9);
    }
    :root[data-theme="light"] .rs-card.t1:hover{
      box-shadow:
        0 28px 60px -26px rgba(59,130,246,.30),
        0 12px 28px -16px rgba(30,40,80,.2),
        inset 0 1px 0 rgba(255,255,255,.9);
    }

    /* Cursor-tracked surface glow (JS sets --mx / --my). Sits above the media
       for a glossy sheen, below the text (which is lifted to z-index:2). */
    .rs-glow{
      position:absolute; inset:0; z-index:1; pointer-events:none;
      border-radius:inherit; opacity:0;
      transition:opacity .4s ease;
      background:radial-gradient(230px circle at var(--mx,50%) var(--my,50%),
                  rgba(52,211,153,.20), transparent 60%);
      mix-blend-mode:screen;
    }
    .rs-card.t1 .rs-glow{
      background:radial-gradient(230px circle at var(--mx,50%) var(--my,50%),
                  rgba(96,165,250,.26), transparent 60%);
    }
    .rs-card.t2 .rs-glow{
      background:radial-gradient(230px circle at var(--mx,50%) var(--my,50%),
                  color-mix(in srgb, var(--tone,#8b6dff) 34%, transparent), transparent 60%);
    }
    .rs-card:hover .rs-glow{ opacity:1; }
    :root[data-theme="light"] .rs-glow{ mix-blend-mode:multiply; opacity:0; }
    :root[data-theme="light"] .rs-card:hover .rs-glow{ opacity:.6; }
    /* keep text crisp above the glow */
    .rs-card .rs-body, .rs-card .rs-cta,
    .rs-card .rs-tc-top, .rs-card .rs-tc-title, .rs-card .rs-preview{ position:relative; z-index:2; }
    @media (prefers-reduced-motion: reduce){
      .rs-card:hover{ transform:translateY(-5px); }
      .rs-glow{ display:none; }
    }

    /* Media area — the universal chart art / essay art */
    .rs-media{
      position:relative; overflow:hidden;
      aspect-ratio:5 / 3;
      background:
        radial-gradient(140% 100% at 0% 0%, rgba(198,242,78,.10), transparent 60%),
        radial-gradient(140% 100% at 100% 100%, rgba(139,109,255,.12), transparent 60%),
        linear-gradient(180deg, rgba(10,12,20,.28), rgba(10,12,20,.05));
      border-bottom:1px solid rgba(255,255,255,.06);
      color:#e5e7eb;
    }
    .rs-card.t1 .rs-media{
      background:
        radial-gradient(140% 100% at 0% 0%, rgba(96,165,250,.16), transparent 60%),
        radial-gradient(140% 100% at 100% 100%, rgba(198,242,78,.14), transparent 60%),
        linear-gradient(180deg, rgba(10,12,20,.28), rgba(10,12,20,.05));
    }
    :root[data-theme="light"] .rs-media{ color:#334155; border-bottom-color:rgba(15,23,42,.08); }
    .rs-svg{ width:100%; height:100%; display:block; padding:14px; }
    .rs-card:hover .rs-svg{ transform:scale(1.03); }
    .rs-svg{ transition:transform .5s cubic-bezier(.22,.61,.36,1); }

    /* Real Task 1 chart image — sits on a clean white 'paper' so the chart's own
       white background blends; whole chart stays visible (contain). Gentle zoom. */
    .rs-media-img{ background:#ffffff !important; }
    .rs-img{
      width:100%; height:100%; display:block;
      object-fit:contain; object-position:center;
      transition:transform .6s cubic-bezier(.22,.61,.36,1);
    }
    .rs-card:hover .rs-img{ transform:scale(1.045); }
    /* Graceful image fallback — hidden SVG art shown only when the real image
       fails to load (onerror in sampleCardHTML adds .rs-media-broken). Without
       this the card renders a broken-icon + raw alt text where the chart
       should be. */
    .rs-img-fb{ display:none; position:absolute; inset:0; }
    .rs-img-fb svg{ width:100%; height:100%; display:block; }
    .rs-media-broken .rs-img{ display:none; }
    .rs-media-broken .rs-img-fb{ display:block; }

    /* Floating BAND badge — top-right of the media */
    .rs-band{
      position:absolute; top:14px; right:14px; z-index:2;
      display:flex; flex-direction:column; align-items:center;
      background:linear-gradient(135deg, #fbbf24 0%, #f59e0b 60%, #ea580c 100%);
      color:#1a1200;
      border-radius:14px;
      padding:8px 10px 6px;
      box-shadow:
        0 10px 24px -8px rgba(251,191,36,.55),
        inset 0 -2px 0 rgba(0,0,0,.15),
        inset 0 1px 0 rgba(255,255,255,.4);
      min-width:52px;
      transform:rotate(-4deg);
      transition:transform .5s cubic-bezier(.22,.61,.36,1);
    }
    .rs-card:hover .rs-band{ transform:rotate(0deg) scale(1.05); }
    .rs-band-n{
      font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif;
      font-size:22px; font-weight:800; line-height:1; letter-spacing:-.5px;
    }
    .rs-band-l{
      font-size:9px; font-weight:800; letter-spacing:1.2px; opacity:.75;
      margin-top:2px;
    }

    /* Body — headline + preview */
    .rs-body{
      padding:18px 20px 12px;
      display:flex; flex-direction:column; gap:8px;
      flex:1;
    }
    .rs-top{
      display:flex; align-items:center; gap:6px; flex-wrap:wrap;
    }
    .rs-task{
      font-size:10.5px; font-weight:800; padding:4px 9px; border-radius:6px;
      letter-spacing:.6px; text-transform:uppercase;
    }
    .rs-task.t1{ background:linear-gradient(135deg,#60a5fa,#3b82f6); color:#fff; }
    .rs-task.t2{ background:linear-gradient(135deg,#6ee7b7,#34d399); color:#06281d; }
    .rs-cat{ font-size:11.5px; color:var(--muted); font-weight:600; padding:3px 8px; border-radius:6px; background:rgba(255,255,255,.05); }
    :root[data-theme="light"] .rs-cat{ background:rgba(15,23,42,.06); color:#475569; }
    /* PERF: the pulsing glow is a pseudo-element fading in and out, NOT an
       animated box-shadow.
       A box-shadow cannot be composited, so animating one repaints the badge on
       every single frame, forever. There are typically ~16 of these on the
       homepage at once, which was the single largest source of idle main-thread
       work on the page. transform and opacity are handled by the compositor, so
       this version costs the main thread nothing once it starts. Same look. */
    .rs-new{
      position:relative;
      background:linear-gradient(135deg, #10b981, #34d399);
      color:#02160c; font-size:9.5px; font-weight:800;
      padding:3px 8px; border-radius:99px; letter-spacing:.6px;
      box-shadow:0 4px 14px -3px rgba(16,185,129,.6);
      animation:rs-newPulse 2.4s ease-in-out infinite;
      text-transform:uppercase;
      margin-left:auto;
    }
    .rs-new::after{
      content:''; position:absolute; inset:0; border-radius:inherit;
      pointer-events:none; z-index:-1;
      box-shadow:0 6px 22px -2px rgba(16,185,129,.95);
      opacity:0; animation:rs-newGlow 2.4s ease-in-out infinite;
    }
    /* Split in two so BOTH halves are compositor-only: the badge scales, and the
       brighter shadow lives on ::after and simply fades up underneath it. */
    @keyframes rs-newPulse{
      0%,100%{ transform:scale(1); }
      50%   { transform:scale(1.06); }
    }
    @keyframes rs-newGlow{
      0%,100%{ opacity:0; }
      50%   { opacity:1; }
    }
    .rs-card h3{
      font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif;
      font-size:16px; line-height:1.3; letter-spacing:-.2px;
      color:var(--text);
      margin:2px 0 0;  /* 3 lines: 4 of 25 Task-1 prompts were cut mid-title at 2 */
      display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical;
      overflow:hidden;
    }
    /* `-webkit-line-clamp` below is INERT and cannot be made to work here:
       .rs-preview is a flex ITEM (of .rs-body on chart cards, of .rs-card on
       text cards) and flex items have their display blockified from
       `-webkit-box` to `flow-root`, which drops the clamp — so the ellipsis
       never appeared and the box height came from `flex:1` instead. A preview
       that needed more lines than the box had was therefore hard-cut mid-word,
       and it gets worse as the column narrows (the same sentence wraps to more
       lines on phone/tablet than on desktop).
       Fade the bottom edge so a cut reads as "continues" rather than "broken".
       When the text is shorter than the box the fade lands on empty space, so
       it costs nothing. The clamp declarations stay as progressive enhancement
       for any engine that does honour them. */
    .rs-preview{
      font-size:13px; line-height:1.55; color:var(--muted);
      display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical;
      overflow:hidden; flex:1;
      -webkit-mask-image:linear-gradient(#000 calc(100% - 1.1em), transparent 100%);
              mask-image:linear-gradient(#000 calc(100% - 1.1em), transparent 100%);
    }
    :root[data-theme="light"] .rs-card h3{ color:#0f172a; }
    :root[data-theme="light"] .rs-preview,
    :root[data-theme="light"] .rs-cat{ color:#475569; }

    /* CTA row */
    .rs-cta{
      padding:14px 20px 18px;
      display:flex; align-items:center; gap:8px;
      font-size:13.5px; font-weight:700;
      color:#10b981;
      border-top:1px solid rgba(255,255,255,.05);
      transition:gap .35s ease;
    }
    .rs-card.t1 .rs-cta{ color:#60a5fa; }
    :root[data-theme="light"] .rs-cta{ border-top-color:rgba(15,23,42,.08); color:#047857; }
    :root[data-theme="light"] .rs-card.t1 .rs-cta{ color:#2563eb; }
    .rs-card:hover .rs-cta{ gap:14px; }
    .rs-cta-arrow{
      display:inline-block;
      transition:transform .4s cubic-bezier(.2,.7,.2,1);
    }
    .rs-card:hover .rs-cta-arrow{ transform:translateX(6px); }

    /* ================= TASK 2 — text-forward card (no image) =================
       Leads with the topic name. Category-tinted accent bar + a big decorative
       quote mark that drifts on hover. Shorter than the image cards. */
    .rs-card.rs-textcard{
      min-height:0;
      padding:22px 22px 0;
      justify-content:flex-start;
      overflow:hidden;
    }
    .rs-card.rs-textcard.t2{ border-color:color-mix(in srgb, var(--tone, #8b6dff) 34%, transparent); }
    /* left accent bar that grows on hover */
    .rs-accent{
      position:absolute; left:0; top:0; bottom:0; width:4px;
      background:linear-gradient(180deg, var(--tone, #8b6dff), transparent 130%);
      transform:scaleY(.42); transform-origin:top center;
      transition:transform .5s cubic-bezier(.22,.61,.36,1);
    }
    .rs-card.rs-textcard:hover .rs-accent{ transform:scaleY(1); }
    /* oversized decorative quote mark */
    .rs-quote{
      position:absolute; top:-18px; right:6px; z-index:0;
      font-family:'Bricolage Grotesque', Georgia, 'Native Emoji', serif;
      font-size:150px; line-height:1; font-weight:800;
      color:var(--tone, #8b6dff); opacity:.10;
      pointer-events:none; user-select:none;
      transition:transform .6s cubic-bezier(.22,.61,.36,1), opacity .4s ease;
    }
    .rs-card.rs-textcard:hover .rs-quote{ transform:translateY(6px) rotate(-4deg) scale(1.06); opacity:.16; }
    .rs-card.rs-textcard::before{
      background:linear-gradient(90deg, transparent 0%, var(--tone, #8b6dff) 30%, #c6f24e 70%, transparent 100%);
    }
    .rs-card.rs-textcard:hover{ border-color:color-mix(in srgb, var(--tone, #8b6dff) 70%, transparent); }
    .rs-card.rs-textcard::after{
      background:radial-gradient(60% 40% at 50% 110%, color-mix(in srgb, var(--tone,#8b6dff) 28%, transparent), transparent 70%);
    }
    .rs-tc-top{
      position:relative; z-index:1;
      display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-bottom:14px;
    }
    .rs-band-inline{
      margin-left:auto;
      font-size:11px; font-weight:700; color:var(--muted);
      padding:3px 10px; border-radius:99px;
      background:color-mix(in srgb, var(--tone,#8b6dff) 14%, transparent);
      border:1px solid color-mix(in srgb, var(--tone,#8b6dff) 34%, transparent);
    }
    .rs-band-inline b{ color:var(--text); font-weight:800; }
    :root[data-theme="light"] .rs-band-inline b{ color:#0f172a; }
    .rs-tc-title{
      position:relative; z-index:1;
      font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif;
      font-size:20px !important; line-height:1.22; letter-spacing:-.4px;
      -webkit-line-clamp:3 !important;
      margin:0 0 10px !important;
      transition:color .3s ease;
    }
    .rs-card.rs-textcard:hover .rs-tc-title{ color:var(--tone, #8b6dff); }
    :root[data-theme="light"] .rs-card.rs-textcard:hover .rs-tc-title{ color:color-mix(in srgb, var(--tone,#8b6dff) 78%, #0f172a); }
    .rs-card.rs-textcard .rs-preview{ position:relative; z-index:1; -webkit-line-clamp:2; font-size:13px; }
    .rs-card.rs-textcard .rs-cta{
      position:relative; z-index:1; margin-top:auto;
      padding:14px 0 18px;
      border-top:1px solid rgba(255,255,255,.06);
      color:var(--tone, #8b6dff);
    }
    :root[data-theme="light"] .rs-card.rs-textcard .rs-cta{
      border-top-color:rgba(15,23,42,.08);
      color:color-mix(in srgb, var(--tone,#8b6dff) 72%, #0f172a);
    }

    /* Empty state */
    .rs-empty{
      grid-column:1/-1; text-align:center; padding:50px 20px;
      background:var(--glass); border:1px dashed var(--glass-line); border-radius:16px;
      color:var(--muted);
    }
    :root[data-theme="light"] .rs-empty{
      background:#ffffff; border-color:rgba(20,8,13,.12); color:#475569;
    }

    /* "See all" CTA — same emerald button style as before, wired to /writing/samples */
    .rs-more{ display:flex; justify-content:center; margin-top:30px; }
    .rs-see-more{
      position:relative; display:inline-flex; align-items:center; gap:12px;
      padding:14px 28px; border-radius:99px;
      background:linear-gradient(135deg, #10b981 0%, #c6f24e 100%);
      color:#02160c !important; text-decoration:none;
      font-weight:800; font-size:14.5px; letter-spacing:.3px;
      box-shadow:0 12px 30px -10px rgba(16,185,129,.5);
      overflow:hidden;
      transition:transform .35s cubic-bezier(.22,.61,.36,1), box-shadow .35s ease;
    }
    .rs-see-more::before{
      content:""; position:absolute; top:0; left:-120%; width:60%; height:100%;
      background:linear-gradient(115deg, transparent, rgba(255,255,255,.45), transparent);
      transform:skewX(-20deg);
      transition:left .8s cubic-bezier(.22,.61,.36,1);
    }
    .rs-see-more:hover{
      transform:translateY(-2px);
      box-shadow:0 18px 38px -10px rgba(16,185,129,.7);
    }
    .rs-see-more:hover::before{ left:140%; }
    .rs-see-more .rs-sm-arrow{
      display:inline-block; transition:transform .4s cubic-bezier(.2,.7,.2,1);
      font-size:18px;
    }
    .rs-see-more:hover .rs-sm-arrow{ transform:translateX(5px); }

    /* ============================ READING TESTS (free vs premium) ============================ */
    .tests{ display:grid; grid-template-columns:repeat(3,1fr); gap:16px; margin-top:44px; perspective:1100px; }
    .test{
      background:var(--bg-soft); border:1px solid var(--glass-line); border-radius:16px;
      padding:22px; position:relative; overflow:hidden; cursor:pointer; min-height:120px;
      transform-style:preserve-3d; will-change:transform;
      transition:opacity .6s ease, filter .6s ease, transform .3s cubic-bezier(.2,.7,.2,1),
                 border-color .3s ease, box-shadow .3s ease;
    }
    .test > *{ position:relative; z-index:1; }
    .test:hover{ border-color:rgba(198,242,78,.35); box-shadow:0 24px 54px -28px rgba(0,0,0,.85); }
    /* cursor spotlight (sits behind content; lock overlay uses ::after) */
    .test::before{
      content:""; position:absolute; inset:0; border-radius:inherit; z-index:0; pointer-events:none;
      opacity:0; transition:opacity .35s ease;
      background:radial-gradient(220px circle at var(--mx,50%) var(--my,50%), rgba(198,242,78,.16), transparent 62%);
    }
    .test:hover::before{ opacity:1; }
    /* 3D fly-up entrance. Base state VISIBLE — `.in` only plays the entrance, so
       a missed reveal can never leave these cards stranded invisible. */
    .tests .test[data-reveal]{ opacity:1; filter:none; transform:none; transform-origin:center bottom; }
    .tests .test[data-reveal].in{ animation:testFlyUp .7s cubic-bezier(.2,.7,.2,1) both; }
    @keyframes testFlyUp{
      from{ opacity:0; filter:blur(6px); transform:perspective(1100px) rotateX(-12deg) translateY(42px); }
      to{ opacity:1; filter:blur(0); transform:perspective(1100px) rotateX(0) translateY(0); }
    }
    .test .tnum{ font-family:'Bricolage Grotesque', 'Native Emoji', sans-serif; color:var(--muted); font-size:13px; font-weight:600; }
    .test h5{ font-size:16.5px; margin:6px 0 4px; }
    .test .meta{ font-size:12.5px; color:var(--muted); }
    .test .tag{ position:absolute; top:16px; right:16px; font-size:10px; font-weight:700; text-transform:uppercase; padding:4px 9px; border-radius:7px; letter-spacing:.5px; z-index:3; }
    .tag.free{ background:var(--lime); color:#0a0b10; }
    .tag.locked{ background:rgba(255,255,255,.08); color:var(--muted); }
    /* locked tests get blurred + a lock overlay */
    .test.locked .body{ filter:blur(3px); opacity:.5; user-select:none; }
    .test.locked::after{
      content:"🔒 Premium"; position:absolute; inset:0; display:grid; place-items:center; z-index:4;
      font-weight:700; font-size:14px; color:var(--text);
      background:rgba(10,11,16,.35); border-radius:16px;
    }
    .test.locked:hover::after{ background:rgba(198,242,78,.12); color:var(--lime); }

/* `section.block{ padding:80px 0 0 }` has no narrow override, so on a phone
   every page opened with 80px of nothing under an already 70px header — 150px
   (18% of the viewport) before the first word. */
@media (max-width:640px){
  section.block{ padding-top:34px; }
}
