/* ==========================================================================
   Blocs Master — design system
   Hand-written. One file, no framework, no build step.

   Contents
     1  Typefaces
     2  Tokens — dark (default) and light
     3  Reset
     4  Layout primitives
     5  Typography
     6  Buttons, badges, pills
     7  Cards and surfaces
     8  Header and navigation
     9  Hero
    10  Stats, course cards, marquee
    11  Pricing
    12  Value grid, quotes
    13  Course player and lesson list
    14  FAQ
    15  Forms (including Sitelok's own markup)
    16  Dashboard
    17  Footer
    18  Motion, print, utilities
   ========================================================================== */

/* ---------------------------------------------------------------- 1 fonts */
/* Geist — variable weight 100–900 in a single 29 KB file.                   */
@font-face{
  font-family:Geist;src:url(../fonts/geist-var.woff2) format("woff2-variations");
  font-weight:100 900;font-style:normal;font-display:swap;
}
@font-face{
  font-family:"Geist Mono";src:url(../fonts/geist-mono-var.woff2) format("woff2-variations");
  font-weight:100 900;font-style:normal;font-display:swap;
}
/* Instrument Serif — used only for accent phrases. Sparingly, on purpose.   */
@font-face{
  font-family:"Instrument Serif";src:url(../fonts/instrument-serif.woff2) format("woff2");
  font-weight:400;font-style:normal;font-display:swap;
}
@font-face{
  font-family:"Instrument Serif";src:url(../fonts/instrument-serif-italic.woff2) format("woff2");
  font-weight:400;font-style:italic;font-display:swap;
}

/* ---------------------------------------------------------------- 2 tokens */
:root{
  color-scheme:dark;

  /* --- surfaces -------------------------------------------------------- */
  /* Not pure black. A trace of blue keeps the whole page from looking grey
     and stops the brand gradient from floating off the background.        */
  --bg:#08080b;
  --bg-2:#0b0b10;
  --bg-3:#0e0e14;
  --surface:#101016;
  --surface-2:#16161e;
  --surface-3:#1d1d27;

  --line:rgba(255,255,255,.085);
  --line-2:rgba(255,255,255,.16);
  --line-soft:rgba(255,255,255,.05);

  /* --- text ------------------------------------------------------------ */
  --fg:#f6f6f9;
  --fg-2:#a9a9bb;
  --fg-3:#77778c;
  --fg-invert:#08080b;

  /* --- brand ----------------------------------------------------------- */
  --blue:#0a84ff;
  --indigo:#6b4dff;
  --purple:#a855f7;
  --accent:#b28dff;          /* readable violet for links and icons on dark */
  --accent-quiet:rgba(168,85,247,.14);
  --grad:linear-gradient(135deg,#0a84ff 0%,#6b4dff 52%,#a855f7 100%);
  --grad-text:linear-gradient(120deg,#4aa3ff 0%,#9b7bff 46%,#d08bff 100%);
  --grad-soft:linear-gradient(135deg,rgba(10,132,255,.13),rgba(168,85,247,.13));
  --glow:0 18px 50px -18px rgba(107,77,255,.65);

  /* --- status ---------------------------------------------------------- */
  --green:#34d399;
  --green-quiet:rgba(52,211,153,.13);
  --green-line:rgba(52,211,153,.32);
  --amber:#fbbf24;
  --red:#fb7185;

  /* --- elevation ------------------------------------------------------- */
  --sh-1:0 1px 2px rgba(0,0,0,.5);
  --sh-2:0 10px 30px -12px rgba(0,0,0,.75);
  --sh-3:0 30px 70px -25px rgba(0,0,0,.85);
  --edge:inset 0 1px 0 rgba(255,255,255,.055);   /* top-edge highlight */
  --grain-opacity:.035;
  --scrim:linear-gradient(0deg,rgba(4,4,8,.72),rgba(4,4,8,0) 58%);

  /* --- type ------------------------------------------------------------ */
  --font-sans:Geist,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --font-mono:"Geist Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  --font-serif:"Instrument Serif",Iowan Old Style,Georgia,serif;

  --t-display:clamp(2.85rem,7.4vw,5.4rem);
  --t-h1:clamp(2.15rem,4.8vw,3.4rem);
  --t-h2:clamp(1.7rem,3.4vw,2.55rem);
  --t-h3:clamp(1.14rem,1.7vw,1.32rem);
  --t-lede:clamp(1.06rem,1.5vw,1.28rem);
  --t-body:1.0125rem;
  --t-sm:.905rem;
  --t-xs:.8rem;
  --t-2xs:.72rem;

  /* --- geometry -------------------------------------------------------- */
  --r-xs:7px; --r-sm:10px; --r:14px; --r-lg:20px; --r-xl:28px; --r-2xl:36px;
  --maxw:1200px;
  --maxw-text:74ch;
  --pad:clamp(1.15rem,4vw,2.25rem);
  --header-h:68px;

  /* --- motion ---------------------------------------------------------- */
  --ease:cubic-bezier(.22,.61,.36,1);
  /* For looping ambient motion only. --ease is an ease-out, which is right for
     a thing that starts on a click and settles, and wrong for a thing that
     never stops: on an `alternate` loop it parks at both ends and the movement
     stops being noticeable. This is a sine in bezier form — no rest anywhere. */
  --drift:cubic-bezier(.42,0,.58,1);
  --ease-out:cubic-bezier(.16,1,.3,1);
  --fast:.16s; --med:.26s; --slow:.5s;
}

/* Light theme. Not a colour inversion — a separate, deliberately warm set.
   Off-white paper rather than #fff, ink rather than black, and real shadows
   doing the elevation work that borders do in the dark theme.             */
[data-theme=light]{
  color-scheme:light;

  --bg:#fbfbfd;
  --bg-2:#f4f4f8;
  --bg-3:#eeeef4;
  --surface:#ffffff;
  --surface-2:#f6f6fa;
  --surface-3:#eeeef4;

  --line:rgba(12,12,26,.09);
  --line-2:rgba(12,12,26,.18);
  --line-soft:rgba(12,12,26,.06);

  --fg:#0b0b14;
  --fg-2:#4b4b5e;
  --fg-3:#71718a;
  --fg-invert:#ffffff;

  --indigo:#5b3ce8;
  --purple:#8b3fe0;
  --accent:#6d3ae0;
  --accent-quiet:rgba(109,58,224,.09);
  --grad:linear-gradient(135deg,#0a6fe8 0%,#5b3ce8 52%,#8b3fe0 100%);
  --grad-text:linear-gradient(120deg,#0a6fe8 0%,#5b3ce8 48%,#9333ea 100%);
  --grad-soft:linear-gradient(135deg,rgba(10,111,232,.08),rgba(139,63,224,.08));
  --glow:0 18px 44px -20px rgba(91,60,232,.5);

  --green:#0f9d6e;
  --green-quiet:rgba(15,157,110,.1);
  --green-line:rgba(15,157,110,.28);
  --red:#e11d48;

  --sh-1:0 1px 2px rgba(16,16,40,.06);
  --sh-2:0 12px 28px -14px rgba(16,16,40,.22);
  --sh-3:0 34px 70px -30px rgba(16,16,40,.3);
  --edge:inset 0 1px 0 rgba(255,255,255,.9);
  --grain-opacity:.02;
  --scrim:linear-gradient(0deg,rgba(12,12,26,.6),rgba(12,12,26,0) 58%);
}

/* ---------------------------------------------------------------- 3 reset */
*,*::before,*::after{box-sizing:border-box}
html{
  -webkit-text-size-adjust:100%;scroll-behavior:smooth;
  scroll-padding-top:calc(var(--header-h) + 24px);
}
body{
  margin:0;min-height:100%;display:flex;flex-direction:column;
  background:var(--bg);color:var(--fg);
  font-family:var(--font-sans);font-size:var(--t-body);line-height:1.66;
  font-weight:400;font-feature-settings:"cv11","ss01";
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;overflow-x:hidden;
  position:relative;   /* containing block for .page-aurora */
  /* The theme cross-fade lives with the boot fade in §3b, so there is one
     transition declaration on body rather than two that overwrite each other. */
}
main{flex:1}

/* --------------------------------------------------------- 3b the boot fade
   The first half-second of a page load is honest but ugly: the fallback face
   paints, the web font swaps in and reflows the headline, and a moment later
   the cached price paints over "$349" in euros. Each of those is correct
   behaviour and each of them is a visible twitch. So the content starts
   transparent and fades in once the fonts have settled, which turns three
   small jumps into one calm arrival.

   The class is added by an inline script in the head — never by this
   stylesheet — which is the whole safety story. A visitor with JavaScript off,
   or one whose script throws before it gets that far, is never given the class
   and therefore never has anything hidden from them. The script also removes
   the class on a hard timer, so even a font that never loads still reveals the
   page. There is no state in which text can stay invisible.

   Only the *content* fades. `background` on body propagates to the canvas, so
   the page is the right colour from the first frame and it is the words that
   arrive, not the theme. */
html.bm-boot body{opacity:0}
body{opacity:1;transition:opacity .42s var(--ease),
     background-color var(--med) var(--ease),color var(--med) var(--ease)}

/* Reduced motion still wants the anti-jump, just not the animation: the reveal
   becomes a cut rather than a fade. Same for print, where a transition is
   meaningless and a half-finished one would be a disaster. */
@media(prefers-reduced-motion:reduce){
  body{transition-duration:.01ms}
}
@media print{
  html.bm-boot body{opacity:1}
}

/* A whisper of film grain over the whole page. It is what stops large flat
   areas of near-black from banding on cheap panels, and it is most of the
   reason the page reads as "made" rather than "generated".                */
body::after{
  content:"";position:fixed;inset:0;z-index:9999;pointer-events:none;
  opacity:var(--grain-opacity);
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/></filter><rect width='140' height='140' filter='url(%23n)' opacity='.55'/></svg>");
}

img,picture,video,iframe,svg{display:block;max-width:100%}
img{height:auto}
a{color:inherit;text-decoration:none}
button,input,select,textarea{font:inherit;color:inherit}
h1,h2,h3,h4,h5{margin:0;line-height:1.08;letter-spacing:-.028em;font-weight:600}
p,figure,blockquote,fieldset,dl,dd{margin:0}
ul,ol{margin:0;padding:0;list-style:none}
hr{border:0;border-top:1px solid var(--line);margin:0}
::selection{background:var(--accent-quiet);color:var(--fg)}
/* No border-radius here, deliberately. An outline already follows whatever
   radius the element has, so setting one in a :focus-visible rule does not
   round the ring — it reshapes the element itself the moment it takes focus.
   That is what squared off pill buttons and cards on keyboard focus, leaving a
   rounded ring floating around a shape that no longer matched it. */
:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
/* No :target scroll-margin here. The `scroll-padding-top` on html above is
   already applied to every scroll into this page — fragment links, :target,
   scrollIntoView, even the browser's own find-on-page — and a scroll-margin on
   the element is *added* to it rather than replacing it. Having both is how an
   anchor ends up landing ~184px down the page with a band of nothing above it.
   One offset, declared once, on the scroll container. */

/* ------------------------------------------------------- 4 layout */
.wrap{width:100%;max-width:var(--maxw);margin-inline:auto;padding-inline:var(--pad)}
.wrap-narrow{width:100%;max-width:820px;margin-inline:auto;padding-inline:var(--pad)}
.section{padding-block:clamp(3.6rem,8.5vw,7rem)}
.section-sm{padding-block:clamp(2.4rem,5vw,4.2rem)}
.section-tint{background:var(--bg-2);border-block:1px solid var(--line-soft)}
.stack{display:flex;flex-direction:column}
.gap-xs{gap:.5rem}.gap-sm{gap:.85rem}.gap{gap:1.4rem}.gap-lg{gap:2.4rem}
.center{text-align:center}
.grid{display:grid;gap:1.35rem}
.g-2{grid-template-columns:repeat(auto-fit,minmax(310px,1fr))}
.g-3{grid-template-columns:repeat(auto-fit,minmax(278px,1fr))}
.g-4{grid-template-columns:repeat(auto-fit,minmax(215px,1fr))}
.mt-1{margin-top:.6rem}.mt-2{margin-top:1.2rem}.mt-3{margin-top:2rem}
.mt-4{margin-top:3rem}.mt-5{margin-top:4.2rem}.mt-6{margin-top:5.5rem}
.mb-1{margin-bottom:.6rem}.mb-2{margin-bottom:1.2rem}
.mb-3{margin-bottom:2rem}.mb-4{margin-bottom:3rem}
.hide{display:none!important}

/* Section header — eyebrow, headline, lede, always the same rhythm. */
.sec-head{max-width:62ch;margin-inline:auto;text-align:center}
.sec-head .lede{margin-top:.85rem}
.sec-head .h2{margin-top:.7rem}

/* ------------------------------------------------------- 5 typography */
/* The eyebrow is the first thing above every headline on the site, so a flat
   grey was leaving the brand colour to be introduced further down the page.
   It now carries the gradient itself — clipped to the letterforms, with the
   flanking rules fading out of the same two colours. The plain accent colour
   below is the fallback for anything without background-clip:text; nothing is
   ever left as transparent-on-transparent. */
.eyebrow{
  font-family:var(--font-mono);font-size:var(--t-2xs);font-weight:500;
  letter-spacing:.2em;text-transform:uppercase;
  color:var(--accent);
  display:inline-flex;align-items:center;gap:.6rem;
}
@supports (background-clip:text) or (-webkit-background-clip:text){
  .eyebrow{
    background-image:var(--grad-text);
    -webkit-background-clip:text;background-clip:text;
    color:transparent;-webkit-text-fill-color:transparent;
  }
}
.eyebrow::before{
  content:"";width:18px;height:1px;flex-shrink:0;
  background:linear-gradient(90deg,transparent,var(--accent));
  opacity:.75;
}
.center .eyebrow::after{
  content:"";width:18px;height:1px;flex-shrink:0;
  background:linear-gradient(90deg,var(--accent),transparent);
  opacity:.75;
}
/* An eyebrow is inline-flex, so it centres itself happily inside any block that
   carries text-align:center. The moment its parent becomes a flex column — a
   price card, the auth panel — the eyebrow is blockified into a full-width flex
   row and text-align stops reaching it: the label sits hard against the left
   padding while the price underneath stays centred. Centring its own main axis
   is what actually holds it. On a shrink-wrapped inline-flex box this changes
   nothing, so it is safe to state everywhere the context is already centred. */
.center .eyebrow{justify-content:center}

/* --- where the gradient eyebrow stands down -------------------------------
   The gradient is worth having where the eyebrow is the only brand colour in
   the block. In a page hero the headline already carries the gradient and the
   primary button repeats it, so a third instance of the same ramp inside a
   single screenful stops reading as an accent and starts reading as noise.
   Same story inside a price card. In both, the eyebrow goes back to being a
   quiet grey label — which is all a kicker above a headline needs to be.
   `.eyebrow-quiet` is the manual opt-out for one-offs elsewhere.

   This has to sit *after* the flanking rules above, not before them. The two
   sets have identical specificity, so source order is the only tie-breaker —
   with the override first, `.center .eyebrow::after` won and the right-hand
   rule stayed brand-coloured while the left-hand one went grey, which is how a
   price card ended up with one grey dash and one violet one. */
.hero .eyebrow,
.price-card .eyebrow,
.eyebrow-quiet{
  background-image:none;
  color:var(--fg-3);
  -webkit-text-fill-color:currentColor;
}
.hero .eyebrow::before,.hero .eyebrow::after,
.price-card .eyebrow::before,.price-card .eyebrow::after,
.eyebrow-quiet::before,.eyebrow-quiet::after{opacity:.5}
/* Mirrored, so the pair fades away from the words in both directions. */
.hero .eyebrow::before,.price-card .eyebrow::before,.eyebrow-quiet::before{
  background:linear-gradient(90deg,transparent,var(--fg-4,var(--fg-3)));
}
.hero .eyebrow::after,.price-card .eyebrow::after,.eyebrow-quiet::after{
  background:linear-gradient(90deg,var(--fg-4,var(--fg-3)),transparent);
}

.hero-title{font-size:var(--t-display);letter-spacing:-.042em;line-height:.99;font-weight:600}
.h1{font-size:var(--t-h1);letter-spacing:-.035em}
.h2{font-size:var(--t-h2);letter-spacing:-.032em}
.h3{font-size:var(--t-h3);font-weight:600;letter-spacing:-.02em}
.h4{font-size:1.04rem;font-weight:600;letter-spacing:-.015em}

/* The one flourish. An italic serif phrase inside a sans headline reads as
   editorial rather than SaaS, and costs 22 KB. Optically sized up because
   Instrument Serif has a smaller x-height than Geist.                     */
.serif{
  font-family:var(--font-serif);font-style:italic;font-weight:400;
  font-size:1.1em;letter-spacing:-.01em;padding-right:.04em;
}

.lede{font-size:var(--t-lede);color:var(--fg-2);line-height:1.58;font-weight:400;letter-spacing:-.011em}
.muted{color:var(--fg-2)}
.dim{color:var(--fg-3)}
.small{font-size:var(--t-sm)}
.xs{font-size:var(--t-xs)}
.bold{font-weight:600}
.mono{font-family:var(--font-mono);font-size:var(--t-xs);letter-spacing:.02em}
.nums{font-variant-numeric:tabular-nums}
.balance{text-wrap:balance}
.prose{max-width:var(--maxw-text)}
.prose p+p{margin-top:1.05rem}

/* ---- legal documents (/terms/, /privacy/) -------------------------------
   Long-form reading rather than marketing: a slightly narrower measure, real
   space between clauses, and numbered section headings that can be linked to
   and quoted. Deliberately plain — a terms page that tries to look designed
   reads as though it is hiding something. */
.legal{max-width:70ch;margin-inline:auto}
.legal h2{
  font-size:var(--t-xl,1.35rem);font-weight:600;letter-spacing:-.02em;
  margin:3.2rem 0 .9rem;
}
.legal h2:first-child{margin-top:0}
.legal h2 .legal-n{
  font-family:var(--font-mono);font-size:.62em;color:var(--fg-3);
  margin-right:.6rem;vertical-align:.12em;
}
.legal h3{font-size:1.02rem;font-weight:600;margin:1.8rem 0 .55rem}
.legal p{color:var(--fg-2);line-height:1.72}
.legal p+p{margin-top:.95rem}
.legal ul{margin:.95rem 0 0;padding-left:1.15rem;list-style:disc}
.legal li{color:var(--fg-2);line-height:1.7;margin-bottom:.5rem}
.legal li::marker{color:var(--fg-3)}
.legal a{color:var(--accent)}
.legal strong{color:var(--fg);font-weight:600}
.legal hr{border:0;border-top:1px solid var(--line-1);margin:3rem 0}
/* Pulled-out clause for the things people actually argue about later —
   the refund rule, the licence limits. Visually distinct so nobody can
   claim it was buried in the middle of a paragraph. */
.legal-callout{
  margin:1.4rem 0;padding:1.05rem 1.25rem;border-radius:14px;
  border:1px solid var(--line-2);background:var(--surface-2);
}
.legal-callout p{color:var(--fg);margin:0}
.legal-toc{
  margin:0 0 3rem;padding:1.15rem 1.35rem;border-radius:16px;
  border:1px solid var(--line-2);background:var(--surface-2);
}
/* Numbered to match the numbered section headings, so "see section 5" is
   something a reader can actually follow from here. */
.legal-toc ol{margin:.5rem 0 0;padding-left:1.4rem;columns:2;column-gap:2rem;list-style:decimal}
.legal-toc li{margin-bottom:.35rem;font-size:var(--t-sm);break-inside:avoid}
.legal-toc li::marker{color:var(--fg-3);font-family:var(--font-mono);font-size:.8em}
.legal-toc a{color:var(--fg-2);text-decoration:none}
.legal-toc a:hover{color:var(--accent)}
@media (max-width:640px){
  .legal-toc ol{columns:1}
  .legal h2{margin-top:2.6rem}
}

.grad-text{
  background:var(--grad-text);-webkit-background-clip:text;background-clip:text;
  color:transparent;
}

/* Body links inside prose */
.link{
  color:var(--accent);text-decoration:underline;text-decoration-thickness:1px;
  text-underline-offset:.22em;text-decoration-color:color-mix(in srgb,var(--accent) 40%,transparent);
  transition:text-decoration-color var(--fast);
}
.link:hover{text-decoration-color:currentColor}

/* ------------------------------------------------------- 6 buttons */
.btn{
  position:relative;display:inline-flex;align-items:center;justify-content:center;
  gap:.5rem;padding:.82rem 1.45rem;border-radius:100px;
  border:1px solid var(--line-2);background:var(--surface-2);color:var(--fg);
  font-size:var(--t-sm);font-weight:500;letter-spacing:-.008em;line-height:1;
  cursor:pointer;white-space:nowrap;isolation:isolate;
  box-shadow:var(--sh-1),var(--edge);
  transition:transform var(--fast) var(--ease),box-shadow var(--med) var(--ease),
             background-color var(--fast) var(--ease),border-color var(--fast) var(--ease),
             filter var(--fast) var(--ease);
}
.btn:hover{transform:translateY(-1px)}
.btn:active{transform:translateY(0) scale(.988)}
.btn svg{flex-shrink:0}

/* background-origin is padding-box by default while background-clip is
   border-box, so a gradient sized against the padding box gets painted into the
   border strip as well — where it shows the *repeated* tile, i.e. the far end of
   the gradient. That is the stray coloured hairline down the left edge. Painting
   and clipping from the same box, with no repeat, removes it. */
.btn-primary{
  background:var(--grad);background-size:140% 140%;background-position:0% 50%;
  background-origin:border-box;background-repeat:no-repeat;
  border-color:transparent;color:#fff;
  box-shadow:var(--glow),inset 0 1px 0 rgba(255,255,255,.22);
  transition:transform var(--fast) var(--ease),box-shadow var(--med) var(--ease),
             background-position var(--slow) var(--ease);
}
.btn-primary:hover{background-position:100% 50%;box-shadow:0 22px 60px -18px rgba(107,77,255,.85),inset 0 1px 0 rgba(255,255,255,.28)}

.btn-ghost{background:transparent;border-color:var(--line-2);box-shadow:none}
.btn-ghost:hover{background:var(--surface-2);border-color:var(--fg-3)}

.btn-quiet{background:transparent;border-color:transparent;box-shadow:none;color:var(--fg-2);padding-inline:.7rem}
.btn-quiet:hover{color:var(--fg);background:var(--surface-2)}

.btn-lg{padding:1.02rem 1.95rem;font-size:.98rem}
.btn-sm{padding:.55rem 1rem;font-size:var(--t-xs)}
.btn-block{width:100%}
.btn[disabled],.btn[aria-disabled=true]{opacity:.45;pointer-events:none}

.btn-row{display:flex;flex-wrap:wrap;gap:.7rem}
.center .btn-row,.btn-row.center{justify-content:center}

/* ------------------------------------------------------- 6b badges */
.badge{
  display:inline-flex;align-items:center;gap:.4rem;padding:.3rem .66rem;
  border-radius:100px;font-family:var(--font-mono);font-size:var(--t-2xs);
  font-weight:500;letter-spacing:.09em;text-transform:uppercase;
  border:1px solid var(--line);background:var(--surface-2);color:var(--fg-2);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
}
.badge-member{
  background:var(--grad);background-origin:border-box;background-repeat:no-repeat;
  border-color:transparent;color:#fff;letter-spacing:.08em;
}
.badge-free,.badge-owned{background:var(--green-quiet);border-color:var(--green-line);color:var(--green)}
.badge-dot::before{content:"";width:5px;height:5px;border-radius:50%;background:currentColor}

/* ------------------------------------------------------- 7 cards */
.card{
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:1.6rem;box-shadow:var(--sh-1),var(--edge);
  transition:border-color var(--med) var(--ease),transform var(--med) var(--ease),
             box-shadow var(--med) var(--ease);
}
.card-hover:hover{border-color:var(--line-2);transform:translateY(-3px);box-shadow:var(--sh-2),var(--edge)}
.card-flush{padding:0;overflow:hidden}

/* ------------------------------------------------------- 8 header */
.site-header{
  position:sticky;top:0;z-index:100;
  background:color-mix(in srgb,var(--bg) 72%,transparent);
  backdrop-filter:saturate(180%) blur(18px);
  -webkit-backdrop-filter:saturate(180%) blur(18px);
  border-bottom:1px solid transparent;
  transition:border-color var(--med) var(--ease),background-color var(--med) var(--ease);
}
.site-header.scrolled{border-bottom-color:var(--line);background:color-mix(in srgb,var(--bg) 88%,transparent)}
/* Three columns rather than a flex row, so the menu sits in the true centre of
   the header instead of wherever the logo happens to push it. The two 1fr
   columns split the leftover space evenly, which keeps the middle column
   centred even though the brand and the account buttons are different widths.
   Below 900px this reverts to flex — see the media query further down — because
   the menu becomes a drop-down panel there and has no business being a column. */
.nav{
  display:grid;grid-template-columns:1fr auto 1fr;align-items:center;
  gap:1rem;height:var(--header-h);
}
.brand{
  display:flex;align-items:center;gap:.58rem;flex-shrink:0;
  font-weight:600;letter-spacing:-.035em;font-size:1.04rem;
  transition:opacity var(--fast)
}
.brand:hover{opacity:.78}
.brand-logo{width:28px;height:28px;border-radius:8px;flex-shrink:0}

.nav-links{display:flex;align-items:center;gap:.1rem;justify-content:center}
.nav-links .nav-sm-only{display:none}   /* shown only inside the mobile drop-down */
.nav-links a{
  position:relative;padding:.5rem .78rem;border-radius:8px;
  font-size:var(--t-sm);font-weight:450;color:var(--fg-2);
  transition:color var(--fast),background-color var(--fast);
}
.nav-links a:hover{color:var(--fg);background:var(--surface-2)}
.nav-links a[aria-current=page]{color:var(--fg)}
.nav-links a[aria-current=page]::after{
  content:"";position:absolute;left:.78rem;right:.78rem;bottom:.16rem;height:1.5px;
  border-radius:2px;background:var(--grad);
}
.nav-actions{display:flex;align-items:center;gap:.45rem;justify-content:flex-end}

/* Theme switch — a single quiet control, not a novelty. */
.theme-toggle{
  width:34px;height:34px;display:grid;place-items:center;flex-shrink:0;
  border-radius:100px;border:1px solid var(--line);background:transparent;
  color:var(--fg-2);cursor:pointer;
  transition:color var(--fast),background-color var(--fast),border-color var(--fast);
}
.theme-toggle:hover{color:var(--fg);background:var(--surface-2);border-color:var(--line-2)}
.theme-toggle svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.theme-toggle .ico-sun{display:none}
[data-theme=light] .theme-toggle .ico-sun{display:block}
[data-theme=light] .theme-toggle .ico-moon{display:none}

.nav-toggle{display:none;background:none;border:0;padding:.5rem;cursor:pointer;border-radius:8px}
.nav-toggle:hover{background:var(--surface-2)}
.nav-toggle span{display:block;width:19px;height:1.6px;background:var(--fg);border-radius:2px;transition:var(--med) var(--ease)}
.nav-toggle span+span{margin-top:5px}
.nav-toggle[aria-expanded=true] span:nth-child(1){transform:translateY(6.6px) rotate(45deg)}
.nav-toggle[aria-expanded=true] span:nth-child(2){opacity:0}
.nav-toggle[aria-expanded=true] span:nth-child(3){transform:translateY(-6.6px) rotate(-45deg)}

@media (max-width:900px){
  /* Back to a flex row: the menu leaves the flow entirely (it becomes the
     drop-down panel below) so there is no middle column left to centre. */
  .nav{display:flex}
  .nav-toggle{display:block;order:3}
  .nav-links{
    position:fixed;inset:var(--header-h) 0 auto;flex-direction:column;align-items:stretch;
    gap:0;background:var(--bg-2);border-bottom:1px solid var(--line);
    padding:.5rem var(--pad) 1.4rem;margin:0;box-shadow:var(--sh-2);
    transform:translateY(-10px);opacity:0;pointer-events:none;
    transition:var(--med) var(--ease);
  }
  .nav-links.open{transform:none;opacity:1;pointer-events:auto}
  .nav-links a{padding:.9rem .3rem;border-radius:0;border-bottom:1px solid var(--line-soft);font-size:1rem}
  .nav-links a[aria-current=page]::after{display:none}
  .nav-actions{margin-left:auto;order:2}
  .nav-actions .btn{padding:.55rem 1rem;font-size:var(--t-xs)}
  .nav-actions .btn-hide-sm{display:none}
  /* The Dashboard button is one of the .btn-hide-sm casualties above, so the
     drop-down carries its own copy — otherwise a signed-in phone visitor would
     have no route to the dashboard but the footer. */
  .nav-links .nav-sm-only{display:block}
}

/* --------------------------------------------- 8b animated page aurora */
/* Colour behind the top of every page: three big, slow-drifting washes in the
   brand blue, indigo and purple. They sit at z-index:-1 — above the canvas
   background, beneath every section — and are masked so the colour dissolves
   into the page instead of ending on a hard line.
   Only transforms are animated, so the whole thing is composited on the GPU
   and costs nothing on scroll.                                             */
.page-aurora{
  position:absolute;top:0;left:0;right:0;z-index:-1;
  height:min(1000px,112vh);
  overflow:hidden;pointer-events:none;contain:layout paint;
  -webkit-mask-image:linear-gradient(to bottom,#000 0,#000 44%,transparent 97%);
          mask-image:linear-gradient(to bottom,#000 0,#000 44%,transparent 97%);
}
.page-aurora::before,
.page-aurora::after,
.page-aurora .aurora-3{
  content:"";position:absolute;display:block;
  will-change:transform;
}
/* Ellipses rather than circles, so the rotation actually reads.

   On timing, because this was wrong for a long while and the reason is not
   obvious. These ran at 29–43s on var(--ease), which is an ease-*out*: fast
   away from the start, then a long decelerating tail. On a two-legged
   `alternate` loop that means each ellipse spends the great majority of its
   cycle very nearly still, and the eye reads "nothing is moving" — it registers
   change, not position. The curve below is a sine in disguise: no rest at
   either end, so the drift is continuous. Durations are roughly halved and
   deliberately coprime (15/19/23) so the three never fall into step and start
   pulsing together. */
.page-aurora::before{
  top:-30%;left:-10%;width:min(1180px,116vw);aspect-ratio:1.45;
  background:radial-gradient(closest-side,
    color-mix(in srgb,var(--blue) 42%,transparent),transparent 100%);
  animation:aurora-1 15s var(--drift) infinite alternate;
}
.page-aurora::after{
  top:-24%;right:-12%;width:min(1040px,108vw);aspect-ratio:1.25;
  background:radial-gradient(closest-side,
    color-mix(in srgb,var(--purple) 40%,transparent),transparent 100%);
  animation:aurora-2 19s var(--drift) infinite alternate;
}
.page-aurora .aurora-3{
  top:2%;left:32%;width:min(820px,90vw);aspect-ratio:1.1;
  background:radial-gradient(closest-side,
    color-mix(in srgb,var(--indigo) 34%,transparent),transparent 100%);
  animation:aurora-3 23s var(--drift) infinite alternate;
}
/* Waypoints at 25/50/75 as well as the ends: a two-keyframe path is a straight
   line back and forth, which reads as a mechanism. Four legs of unequal
   direction read as weather. Scale carries most of the visible change — the
   fill is a soft radial gradient, so growing it spreads the colour and the
   brightness under any given pixel shifts far more than translation alone
   would move it. */
@keyframes aurora-1{
  0%  {transform:translate3d(-6vw,-4%,0) scale(.92) rotate(-10deg)}
  25% {transform:translate3d(5vw,3%,0)   scale(1.16) rotate(6deg)}
  50% {transform:translate3d(14vw,9%,0)  scale(1.3)  rotate(17deg)}
  75% {transform:translate3d(6vw,2%,0)   scale(1.1)  rotate(4deg)}
  100%{transform:translate3d(-9vw,11%,0) scale(1.02) rotate(-14deg)}
}
@keyframes aurora-2{
  0%  {transform:translate3d(4vw,2%,0)    scale(1.14) rotate(7deg)}
  25% {transform:translate3d(-5vw,-3%,0)  scale(.96)  rotate(-8deg)}
  50% {transform:translate3d(-13vw,10%,0) scale(1.05) rotate(-19deg)}
  75% {transform:translate3d(-2vw,4%,0)   scale(1.24) rotate(-5deg)}
  100%{transform:translate3d(8vw,-5%,0)   scale(1.32) rotate(12deg)}
}
@keyframes aurora-3{
  0%  {transform:translate3d(-8vw,3%,0)  scale(.9)}
  25% {transform:translate3d(0,-6%,0)    scale(1.18)}
  50% {transform:translate3d(11vw,-9%,0) scale(1.28)}
  75% {transform:translate3d(3vw,4%,0)   scale(1.06)}
  100%{transform:translate3d(-12vw,12%,0) scale(.98)}
}
/* Light theme needs far less of it — the same intensity on white reads as a
   printing error rather than as light.                                     */
[data-theme=light] .page-aurora{opacity:.42;height:min(820px,92vh)}
/* Pages with no hero — a course, the dashboard, a form — get a shorter, quieter
   version. There is no big headline there to hold its own against the colour. */
body:not(:has(.hero)) .page-aurora{height:min(620px,66vh);opacity:.7}
[data-theme=light] body:not(:has(.hero)) .page-aurora{opacity:.3}
/* Colour, yes; movement, no. */
@media(prefers-reduced-motion:reduce){
  .page-aurora::before,.page-aurora::after,.page-aurora .aurora-3{animation:none}
}
@media(max-width:640px){
  .page-aurora{height:min(680px,80vh)}
}

/* --------------------------------------------- 8c the closing CTA band */
/* The last section before the footer, on the pages that end with an ask. It
   carries the same slow aurora the top of the page does, so a visitor who has
   scrolled the whole way arrives back in brand colour rather than fading out
   into a grey box — the page opens and closes on the same note.
   Two ellipses rather than the hero's three: this band is a fifth of the height,
   and a third layer only muddies it. As in the hero, nothing but transform is
   animated, so it composites on the GPU and costs nothing on scroll. */
.section-cta{
  position:relative;overflow:hidden;isolation:isolate;
  border-block:1px solid var(--line-soft);
  background-color:var(--bg-2);
  background-image:var(--grad-soft);
}
.section-cta>*{position:relative;z-index:1}
.section-cta::before,.section-cta::after{
  content:"";position:absolute;z-index:0;pointer-events:none;
  will-change:transform;
}
/* Anchored off the top and bottom edges so the brightest part of each ellipse
   sits outside the band and only its falloff crosses the text. */
.section-cta::before{
  top:-95%;left:-10%;width:min(940px,84vw);aspect-ratio:1.4;
  background:radial-gradient(closest-side,
    color-mix(in srgb,var(--blue) 40%,transparent),transparent 100%);
  animation:cta-drift-1 17s var(--drift) infinite alternate;
}
.section-cta::after{
  bottom:-105%;right:-8%;width:min(860px,80vw);aspect-ratio:1.25;
  background:radial-gradient(closest-side,
    color-mix(in srgb,var(--purple) 38%,transparent),transparent 100%);
  animation:cta-drift-2 21s var(--drift) infinite alternate;
}
/* Same reasoning as the hero above — sine curve, halved durations, waypoints
   rather than a straight there-and-back. The horizontal travel is wider here
   than in the hero because the band is short: an ellipse anchored mostly
   off-screen shows its movement through the edge sweeping sideways across the
   text, and a few vw of that is nothing. */
@keyframes cta-drift-1{
  0%  {transform:translate3d(-7vw,-3%,0) scale(.95) rotate(-7deg)}
  25% {transform:translate3d(4vw,4%,0)   scale(1.14) rotate(5deg)}
  50% {transform:translate3d(16vw,8%,0)  scale(1.28) rotate(15deg)}
  75% {transform:translate3d(5vw,1%,0)   scale(1.08) rotate(3deg)}
  100%{transform:translate3d(-10vw,-6%,0) scale(1.02) rotate(-12deg)}
}
@keyframes cta-drift-2{
  0%  {transform:translate3d(5vw,3%,0)    scale(1.16) rotate(6deg)}
  25% {transform:translate3d(-4vw,-4%,0)  scale(.97)  rotate(-6deg)}
  50% {transform:translate3d(-15vw,-9%,0) scale(1.08) rotate(-17deg)}
  75% {transform:translate3d(-3vw,2%,0)   scale(1.22) rotate(-4deg)}
  100%{transform:translate3d(9vw,6%,0)    scale(1.3)  rotate(11deg)}
}
/* Same reasoning as the hero: on white, this intensity reads as a misprint. */
/* Light mode drops the gradient wash — over a near-white surface it reads as a
   dirty smudge rather than a tint — and keeps only the two drifting ellipses,
   dimmed. Dimmed, not faint: at .42 the band was so close to plain grey that
   the animation was a rumour. */
[data-theme=light] .section-cta{background-image:none}
[data-theme=light] .section-cta::before,
[data-theme=light] .section-cta::after{opacity:.6}
@media(prefers-reduced-motion:reduce){
  .section-cta::before,.section-cta::after{animation:none}
}

/* ------------------------------------------------------- 9 hero */
.hero{
  position:relative;
  padding-top:clamp(3.8rem,9vw,7rem);padding-bottom:clamp(2.8rem,5vw,4rem);
}
.hero>*{position:relative;z-index:1}
.hero-cta{display:flex;flex-wrap:wrap;gap:.75rem;margin-top:2.1rem}
/* `.center` on an ancestor centres a button row; `.center` on the row itself
   has to work too, because a row that sits beside a centred .sec-head rather
   than inside a centred wrapper is otherwise silently left-aligned. */
.hero-center .hero-cta,.center .hero-cta,.hero-cta.center{justify-content:center}
/* Same reasoning as .course-meta: this is a sentence, not a label. */
.hero-note{
  margin-top:1.1rem;font-size:var(--t-xs);font-weight:450;
  letter-spacing:-.004em;color:var(--fg-3);font-variant-numeric:tabular-nums;
}
/* Three mono clauses separated by middots. On a narrow phone they will not fit
   on one line and shrinking the type to force it only makes them unreadable, so
   the tracking comes off and the line is allowed to wrap between clauses. */
@media(max-width:440px){.hero-note{letter-spacing:.015em;text-wrap:balance}}

/* Hero rhythm. The same relationship between eyebrow, headline and lede on
   every hero, so no page has to reach for a spacing utility to fix its own. */
.eyebrow+.hero-title{margin-top:1.2rem}
.hero-title+.lede{margin-top:1.55rem;max-width:62ch;margin-inline:auto}
.hero-title+.lede+.small{margin-top:1.5rem}

/* ------------------------------------------------------- 10 stats */
.stats{
  margin-top:clamp(2.6rem,5vw,3.8rem);display:grid;
  grid-template-columns:repeat(var(--stat-cols,4),1fr);gap:1px;
  background:var(--line-soft);border:1px solid var(--line-soft);
  border-radius:var(--r-lg);overflow:hidden;
}
.stat{background:var(--bg);padding:1.4rem .8rem;text-align:center;transition:background-color var(--med)}
[data-theme=light] .stat{background:var(--bg)}
.stat-n{
  font-size:clamp(1.7rem,3.2vw,2.4rem);font-weight:500;letter-spacing:-.045em;
  line-height:1;font-variant-numeric:tabular-nums;
}
.stat-l{
  font-family:var(--font-mono);font-size:var(--t-2xs);color:var(--fg-3);
  margin-top:.55rem;text-transform:uppercase;letter-spacing:.14em;font-weight:400;
}
@media(max-width:720px){
  .stats{grid-template-columns:repeat(2,1fr)}
}

/* ------------------------------------------------------- 10b course cards */
.course-card{
  display:flex;flex-direction:column;background:var(--surface);
  border:1px solid var(--line);border-radius:var(--r-lg);overflow:hidden;
  box-shadow:var(--sh-1),var(--edge);
  transition:border-color var(--med) var(--ease),transform var(--med) var(--ease),
             box-shadow var(--med) var(--ease);
}
.course-card:hover{border-color:var(--line-2);transform:translateY(-4px);box-shadow:var(--sh-2),var(--edge)}
.course-thumb{position:relative;aspect-ratio:16/9;background:var(--surface-2);overflow:hidden}
.course-thumb img{
  width:100%;height:100%;object-fit:cover;
  transition:transform .6s var(--ease-out),filter var(--med) var(--ease);
}
.course-card:hover .course-thumb img{transform:scale(1.045)}
.course-thumb::after{content:"";position:absolute;inset:0;background:var(--scrim)}
.course-thumb .badge{position:absolute;top:.8rem;left:.8rem;z-index:2}
/* Over artwork a translucent tint is not enough — a bright thumbnail swallows
   it. Give the badge its own dark surface in both themes. */
.course-thumb .badge-free,.course-thumb .badge-owned{
  background:rgba(6,6,12,.66);border-color:color-mix(in srgb,var(--green) 42%,transparent);
  color:#5fe0ac;
}
.course-body{padding:1.25rem 1.35rem 1.4rem;display:flex;flex-direction:column;flex:1;gap:.5rem}
.course-body .h3{transition:color var(--fast)}
/* Mono with positive tracking is right for a LABEL ("WHAT YOU GET") and wrong
   for a phrase ("36 lessons · 4h 20m · Intermediate"): the extra space between
   the figure and the word it belongs to makes the pair read as two unrelated
   things. Sans, tabular figures, tracking pulled slightly negative — the number
   and its noun now sit together, which is what makes it read expensive. */
.course-meta{
  display:flex;flex-wrap:wrap;align-items:center;
  /* The gap between two facts has to be clearly larger than the gap inside one
     fact, or "17 lessons 2h 28m" reads as five loose words rather than as two
     things. .38rem binds the icon to its label; 1.25rem separates the pairs. */
  gap:.4rem 1.25rem;
  margin-top:auto;padding-top:1rem;
  color:var(--fg-3);font-size:var(--t-xs);font-weight:450;
  letter-spacing:-.004em;font-variant-numeric:tabular-nums;
}
/* Direct children only. A bare `.course-meta span` also caught the text wrapper
   and the figure inside it, turning those into gapped flex boxes too — which is
   how a .38rem gap ended up wedged between a number and its own noun. */
.course-meta > span{display:inline-flex;align-items:center;gap:.38rem;white-space:nowrap}
.course-meta svg{opacity:.7;flex-shrink:0}
/* The label is ordinary inline text: one run, spaced by its own word spaces. */
.course-meta .meta-t{display:inline;white-space:nowrap}
/* The figure carries a touch more weight and colour than its noun, so the eye
   lands on the count without the line turning into a row of shouting numbers. */
.course-meta .nums{color:var(--fg-2);font-weight:550}

/* --- course page hero ----------------------------------------------------
   Centred on the same 1040 the player is capped at, so the title, blurb and
   meta row share their left and right edges with the video below them rather
   than running wider than it. */
.course-hero{
  width:100%;max-width:1040px;margin-inline:auto;text-align:center;
  align-items:center;
}
/* balance, not a hard ch cap. A fixed measure is exactly what drops a final
   short word onto a line of its own — "…and no monthly / fee." — which is the
   one thing a centred two-line blurb must not do. Balancing evens the rows out
   and the cap only stops a long tagline running the full 1040. */
.course-hero .lede{max-width:64ch;margin-inline:auto;text-wrap:balance}
.course-hero .course-meta{justify-content:center;padding-top:.2rem}

/* ------------------------------------------------------- 11 pricing */
/* Two tiers sit side by side and must line up despite having different numbers
   of bullets, so the cards stretch and the button block is pinned at the same
   height in both. One tier collapses to a single centred card. */
.price-grid{
  display:grid;gap:1.2rem;align-items:stretch;
  grid-template-columns:repeat(2,minmax(0,1fr));
}
.price-grid-one{grid-template-columns:minmax(0,1fr);max-width:560px;margin-inline:auto}
.price-grid>.price-card{display:flex;flex-direction:column}
/* Everything above the bullets is the same height in both cards, so a fixed gap
   is all it takes to start both lists on the same line. Deliberately NOT
   margin-top:auto — that pins the lists to the bottom instead, which opens a
   hole in the middle of whichever card has fewer bullets. */
.price-grid .check-list{padding-top:1.8rem}
@media(max-width:820px){
  .price-grid{grid-template-columns:minmax(0,1fr);max-width:520px;margin-inline:auto}
  /* Stacked, the featured plan should come first — it is the one to read. */
  .price-grid .price-card-feature{order:-1}
}

.price-card{
  position:relative;background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-2xl);padding:clamp(1.9rem,4vw,3.1rem);overflow:hidden;
  box-shadow:var(--sh-2),var(--edge);
}
/* The one you want people to buy. Three layers of colour, all painted as
   background images on the card itself so ::before and ::after stay free for
   the gradient hairline and the hover sheen: a violet glow rising from the
   bottom edge, a cooler blue bloom in the top corner, and a whole-card wash. */
.price-card-feature{
  border-color:transparent;box-shadow:var(--sh-3),var(--glow);
  background-color:var(--bg-3);
  background-image:
    radial-gradient(90% 55% at 50% 118%,color-mix(in srgb,var(--purple) 26%,transparent),transparent 72%),
    radial-gradient(70% 50% at 12% -8%,color-mix(in srgb,var(--blue) 20%,transparent),transparent 68%),
    var(--grad-soft);
}
[data-theme=light] .price-card-feature{background-color:var(--surface)}
/* Gradient hairline drawn as a masked border so the radius stays perfect. */
.price-card-feature::before{
  content:"";position:absolute;inset:0;padding:1.4px;border-radius:inherit;
  background:var(--grad);pointer-events:none;z-index:2;
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
          mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;
}
/* The price on the featured card carries the brand gradient. Nowhere else —
   it only reads as emphasis while it is the exception. */
.price-card-feature .price-n,
.price-card-feature .price-cur{
  background:var(--grad-text);-webkit-background-clip:text;background-clip:text;
  color:transparent;
}
.price-card>*{position:relative;z-index:1}
.price-row{display:flex;align-items:flex-start;gap:.28rem;justify-content:center;margin-block:.7rem .3rem}
.price-cur{font-size:1.1rem;font-weight:450;color:var(--fg-2);margin-top:.85rem;letter-spacing:-.02em}
.price-n{
  font-size:clamp(3.6rem,9vw,5.4rem);font-weight:500;letter-spacing:-.055em;
  line-height:1;font-variant-numeric:tabular-nums;
}
.price-note{color:var(--fg-3);font-size:var(--t-sm)}
/* "VAT included", set immediately under the number rather than down by the
   button. It is the one piece of small print that changes the meaning of the
   big number above it, so it has to be read at the same moment — a tax line
   discovered three inches lower has already been priced in wrongly. Uppercase
   and tracked out so it reads as a label, not as another sentence. */
.price-tax{
  display:block;margin-top:.15rem;
  font-family:var(--font-mono);font-size:.66rem;font-weight:500;
  letter-spacing:.09em;text-transform:uppercase;
  color:color-mix(in srgb,var(--accent) 70%,var(--fg-3));
}
.price-strike{
  color:var(--fg-3);font-size:var(--t-sm);text-decoration:line-through;
  text-decoration-color:color-mix(in srgb,var(--fg-3) 60%,transparent);
}

/* --- the owner's price ---------------------------------------------------
   Shown to somebody who already bought one of the older products and is being
   offered the membership at a discount. Three parts on one line: what it costs
   normally, what it costs them, and a word saying which is which. The old
   figure is deliberately quiet and the new one deliberately loud — an offer
   that shouts both numbers equally is asking the reader to do arithmetic. */
.upgrade-price{
  display:flex;align-items:baseline;flex-wrap:wrap;gap:.55rem;
}
.upgrade-price .was{
  color:var(--fg-3);font-size:1.05rem;font-weight:450;
  text-decoration:line-through;
  text-decoration-color:color-mix(in srgb,var(--fg-3) 55%,transparent);
  font-variant-numeric:tabular-nums;
}
.upgrade-price .now{
  font-size:1.75rem;font-weight:520;letter-spacing:-.035em;line-height:1;
  font-variant-numeric:tabular-nums;
  background:var(--grad-text);-webkit-background-clip:text;background-clip:text;
  color:transparent;
}
/* Under the button on a price card, where the full price is already set huge
   directly above: the discounted figure only has to be legible, not large. */
/* Two buttons of different widths stacking raggedly is the one thing that gives
   a panel like this away as unconsidered. Below the fold of a phone they match. */
.upgrade-lede{max-width:68ch}
.upgrade-actions{display:flex;flex-wrap:wrap;gap:.6rem;align-items:center}
@media(max-width:520px){
  .upgrade-actions{flex-direction:column;align-items:stretch}
  .upgrade-actions .btn{width:100%;justify-content:center}
}
.upgrade-price-inline{justify-content:center;gap:.4rem}
.upgrade-price-inline .now{font-size:1.15rem;letter-spacing:-.02em}

/* A price printed inside a sentence or a button label. It carries no styling of
   its own — it exists so the localiser in footer.php can find every price on
   the page, including the ones that are not in a price card, and swap them all
   or none. Tabular figures keep a button from shifting width as the number
   changes under it. */
.price-tag{font-variant-numeric:tabular-nums}

/* Only ever seen when cdn.paddle.com is blocked. Sized like fine print rather
   than like an error, because from the reader's side it is an instruction. */
.checkout-blocked{
  margin-top:.7rem;font-size:.82rem;line-height:1.55;color:var(--fg-2);
  text-align:left;text-wrap:pretty;
}

/* The two buy buttons inside the locked video panel on a course page. */
.lock-cta{
  display:flex;gap:.6rem;justify-content:center;flex-wrap:wrap;
}
@media(max-width:520px){.lock-cta{flex-direction:column;align-items:stretch}}

/* Badge in the top corner of a price card — "Best value", "Your plan". */
.price-badge{
  position:absolute;top:1.15rem;right:1.15rem;z-index:2;
}
.price-badge-best{
  background:var(--accent-quiet);color:var(--accent);
  border:1px solid color-mix(in srgb,var(--accent) 30%,transparent);
}
.price-card.is-owned{border-color:color-mix(in srgb,var(--green) 42%,transparent)}
/* On a phone the card is too narrow for a corner badge — it lands on top of the
   plan name. Drop it back into the flow above the name instead. */
@media(max-width:560px){
  /* align-self, because the card is a flex column and a bare span would
     otherwise stretch the full width of it. */
  .price-badge{position:static;align-self:center;margin-bottom:.9rem}
}
/* A button-shaped label that is not a button — the plan you already have. */
.btn.is-static{
  cursor:default;opacity:.6;pointer-events:none;
  display:flex;align-items:center;justify-content:center;
}

.check-list{display:flex;flex-direction:column;gap:.75rem;margin-top:1.8rem}
/* Three short checks stacked in one left-aligned column under a centred
   headline read as lopsided: the column's left edge sits well outside the
   headline above it, so the block looks pushed left even though it is centred.
   Laid out as a row, each item is its own column and the group is symmetrical
   against the header. Collapses back to a stack when there is no room. */
.check-list-row{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(238px,1fr));
  gap:1.15rem 1.7rem;margin-top:0;
}
@media(max-width:760px){.check-list-row{max-width:34rem;margin-inline:auto}}
.check-list li{display:flex;align-items:flex-start;gap:.72rem;font-size:.95rem;color:var(--fg-2);line-height:1.5}
.check{
  flex-shrink:0;width:19px;height:19px;border-radius:50%;margin-top:.2rem;
  background:var(--accent-quiet);border:1px solid color-mix(in srgb,var(--accent) 34%,transparent);
  display:grid;place-items:center;
}
.check svg{width:9px;height:9px;stroke:var(--accent);stroke-width:3.6;fill:none;stroke-linecap:round;stroke-linejoin:round}

/* Not included in this tier. Dimmed rather than struck through: the point is
   "this is the other plan", not "this is unavailable".
   Must come after .check — same specificity, so order is what wins. */
.check-list li.miss{color:var(--fg-3)}
.check-off{
  background:transparent;
  border-color:color-mix(in srgb,var(--fg-3) 24%,transparent);
}
.check-off svg{width:11px;height:11px;stroke:var(--fg-3);stroke-width:2.8}
/* Hairline where "included" ends and "not included" begins — the first .miss
   that follows a normal item. Cheaper than a heading and reads just as clearly. */
.check-list li:not(.miss)+li.miss{
  margin-top:.4rem;padding-top:1.1rem;border-top:1px solid var(--line);
}

/* ------------------------------------------------- 11b shining card edges */
/* A gradient hairline that lights up around a card on hover and slowly travels
   round it. Drawn as a masked pseudo-element so it follows the border radius
   exactly — a real border cannot hold a gradient.
   @property is what makes an angle animatable; a browser without it still gets
   the static fallback angle inside var() and simply fades the edge in. */
@property --edge-angle{syntax:"<angle>";inherits:false;initial-value:0deg}

.course-card,.value,.quote,.price-card,.product,.module{position:relative}
.course-card::after,.value::after,.quote::after,.price-card::after{
  content:"";position:absolute;inset:0;padding:1.3px;border-radius:inherit;
  pointer-events:none;z-index:4;opacity:0;
  /* Leaving takes longer than arriving. A symmetrical fade reads as a blink on
     the way out, because the pointer is already moving away and the eye is not
     tracking the card any more — the edge has to dim under its own momentum. */
  transition:opacity .62s cubic-bezier(.4,0,.6,1);
  /* The spin is declared here, on the resting state, and merely paused. It used
     to be declared on :hover, which meant that on mouse-out the animation was
     removed outright and --edge-angle snapped back to the static fallback in
     one frame: the bright arc teleported to the other side of the card while
     still at full opacity, which is the flash. Pausing holds the angle exactly
     where it was, so the edge fades out from the position it was last drawn
     in, and a re-hover picks the rotation back up mid-stride. */
  animation:edge-spin 5s linear infinite;
  animation-play-state:paused;
  background:conic-gradient(from var(--edge-angle,118deg),
    transparent 0deg,
    color-mix(in srgb,var(--blue) 92%,transparent) 52deg,
    color-mix(in srgb,var(--purple) 96%,transparent) 108deg,
    transparent 172deg,
    transparent 360deg);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
          mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;
}
.course-card:hover::after,.course-card:focus-visible::after,
.value:hover::after,.value:focus-visible::after,
.quote:hover::after,
.price-card:hover::after{
  opacity:1;
  transition-duration:.34s;      /* in quickly, out slowly — see above */
  animation-play-state:running;
}
@keyframes edge-spin{to{--edge-angle:360deg}}
/* Colour on hover is fine; a rotating light source is not. */
@media(prefers-reduced-motion:reduce){
  .course-card::after,.value::after,.quote::after,.price-card::after{animation:none}
}

/* ------------------------------------------------------- 12 value grid */
.value{
  position:relative;padding:1.6rem;border-radius:var(--r-lg);
  background:var(--surface);border:1px solid var(--line);
  box-shadow:var(--sh-1),var(--edge);
  display:flex;flex-direction:column;
  transition:border-color var(--med) var(--ease),transform var(--med) var(--ease);
}
/* Fixed internal rhythm, and everything from the meta line down is pinned to
   the bottom, so a row of cards lines up however long the copy runs. */
.value .h3{margin-top:0}
.value .h3+p{margin-top:.55rem}
.value:hover{border-color:var(--line-2);transform:translateY(-3px)}
.value-icon{
  width:40px;height:40px;border-radius:11px;background:var(--accent-quiet);
  border:1px solid color-mix(in srgb,var(--accent) 26%,transparent);
  display:grid;place-items:center;margin-bottom:1.05rem;
}
.value-icon svg{width:18px;height:18px;stroke:var(--accent);stroke-width:1.7;fill:none;stroke-linecap:round;stroke-linejoin:round}
.value-tag{
  font-family:var(--font-mono);font-size:var(--t-2xs);color:var(--fg-3);
  margin-top:auto;padding-top:1.1rem;letter-spacing:.1em;text-transform:uppercase;
}
.value-tag+p{margin-top:1.1rem}

/* A value card that links somewhere. The whole card is the target, so the text
   must not look like a link — only the row at the bottom does. */
.value-link{color:inherit;text-decoration:none}
.value-more{
  display:inline-flex;align-items:center;gap:.4rem;align-self:flex-start;
  margin-top:.9rem;font-size:var(--t-sm);font-weight:500;color:var(--accent);
  transition:color var(--fast);
}
.value-more svg{transition:transform var(--fast) var(--ease)}
.value-link:hover .value-more svg{transform:translateX(3px)}

/* The way out from a bullet list to the thing it describes. Under the pricing
   cards, where "70+ templates" needs somewhere to go and look. */
.included-links{
  display:flex;flex-wrap:wrap;justify-content:center;align-items:center;
  gap:.65rem 1.9rem;margin-top:1.7rem;font-size:var(--t-sm);
}
.included-links .link{display:inline-flex;align-items:center;gap:.38rem}
.included-links .link svg{opacity:.72;transition:transform var(--fast) var(--ease)}
.included-links .link:hover svg{transform:translateX(2px)}

/* ------------------------------------------------------- 12b quotes */
.quote{
  padding:1.7rem;border-radius:var(--r-lg);background:var(--surface);
  border:1px solid var(--line);box-shadow:var(--sh-1),var(--edge);
  display:flex;flex-direction:column;gap:1.05rem;
  break-inside:avoid;margin-bottom:1.35rem;
}
.quote::before{
  content:"\201C";font-family:var(--font-serif);font-size:3.2rem;line-height:.7;
  color:var(--accent);opacity:.4;height:1.1rem;
}
.quote p{color:var(--fg-2);font-size:.95rem;line-height:1.64}
/* The bottom margin is for the masonry columns; inside a grid the gap already
   handles it, and the leftover margin makes the row look uneven. */
.grid>.quote{margin-bottom:0}
.quote figcaption{margin-top:auto}
.quote-by{font-weight:550;font-size:var(--t-sm)}
/* Sans, not mono: "Blocs Website Builder, Founder and Director" is a job title,
   and mono tracking was pushing it onto a second line for no gain. */
.quote-role{
  color:var(--fg-3);font-size:var(--t-xs);letter-spacing:-.004em;
  margin-top:.2rem;line-height:1.35;
}
.quote-masonry{columns:3;column-gap:1.35rem}

/* --- instructor portrait -------------------------------------------------
   The source is a cut-out on transparency, so it is ringed rather than framed:
   a 2px gradient rim, the photograph circle-cropped inside it. A square card
   would have shown the ragged edge of the cut-out. */
.portrait{
  width:var(--portrait-size,176px);height:var(--portrait-size,176px);
  padding:2px;border-radius:50%;margin-inline:auto;flex-shrink:0;
  background:var(--grad);background-origin:border-box;background-repeat:no-repeat;
  box-shadow:var(--glow);
}
.portrait>span{
  display:block;width:100%;height:100%;border-radius:50%;
  overflow:hidden;background:var(--surface-2);
}
.portrait img{width:100%;height:100%;object-fit:cover;object-position:50% 12%;display:block}
@media(max-width:520px){.portrait{--portrait-size:132px}}

/* --- endorsements --------------------------------------------------------
   The two Cazoobi quotes and the member testimonials were rendering through the
   same card, so a first-party endorsement from the people who make Blocs looked
   like one more review. This card is deliberately a different object: no quote
   glyph, a lit top corner, a faintly tinted border and a portrait beside the
   name. It reads as a statement rather than a review.

   It used to make that distinction with a third typeface, which was one family
   too many — the difference now comes from surface, light and structure, and
   the whole site runs on two faces. */
.endorse{
  position:relative;display:flex;flex-direction:column;gap:1.35rem;
  padding:2rem;border-radius:var(--r-lg);
  background:
    linear-gradient(180deg,var(--grad-soft),transparent 55%),
    var(--surface);
  border:1px solid color-mix(in srgb,var(--accent) 22%,var(--line));
  box-shadow:var(--sh-1),var(--edge);
  overflow:hidden;
  transition:border-color var(--med) var(--ease),transform var(--med) var(--ease);
}
/* A soft light source at the top-left, where the gradient rule used to meet the top
   edge. It gives the card a sense of being lit rather than merely tinted, and
   it is what carries the "premium" read now that the serif is gone. */
.endorse::after{
  content:"";position:absolute;left:0;top:0;width:min(320px,70%);height:150px;
  pointer-events:none;
  background:radial-gradient(120% 100% at 0% 0%,
    color-mix(in srgb,var(--accent) 20%,transparent),transparent 70%);
  opacity:.85;transition:opacity var(--med) var(--ease);
}
.endorse>*{position:relative;z-index:1}
.endorse:hover{
  border-color:color-mix(in srgb,var(--accent) 42%,var(--line));
  transform:translateY(-2px);box-shadow:var(--sh-2),var(--edge);
}
.endorse:hover::after{opacity:1}
/* Set a step above body copy and tracked in slightly: at this size the sans
   reads as a considered statement rather than as UI text, which is the job the
   serif was doing. Weight 420 keeps it from tipping into a heading. */
.endorse blockquote p{
  font-size:1.1rem;line-height:1.6;font-weight:420;
  color:var(--fg);letter-spacing:-.016em;text-wrap:pretty;
}
.endorse figcaption{
  display:flex;align-items:center;gap:.85rem;margin-top:auto;
  padding-top:1.2rem;border-top:1px solid var(--line-soft);
}
/* Monogram in place of a photograph — neither person has supplied one, and an
   initial in the brand gradient is honest where a stock avatar would not be. */
.endorse-mark{
  flex-shrink:0;width:38px;height:38px;border-radius:50%;
  display:grid;place-items:center;
  background:var(--grad);background-origin:border-box;background-repeat:no-repeat;
  color:#fff;font-weight:600;font-size:.92rem;letter-spacing:-.01em;
  box-shadow:var(--glow);
}
/* A real portrait, where one exists. Slightly larger than the monogram it
   replaces — a face needs the extra pixels to be readable at this size — and
   ringed rather than glowing, so it sits on the card instead of floating. */
.endorse-photo{
  display:block;width:46px;height:46px;object-fit:cover;
  background:none;box-shadow:none;
  border:1px solid color-mix(in srgb,var(--accent) 30%,var(--line));
}
.endorse-who{min-width:0}
@media(max-width:640px){
  .endorse{padding:1.6rem 1.4rem 1.5rem;gap:1.1rem}
  .endorse blockquote p{font-size:1rem}
}
@media(max-width:980px){.quote-masonry{columns:2}}
@media(max-width:620px){.quote-masonry{columns:1}}

/* ------------------------------------------------------- 13 player */
.player{position:sticky;top:calc(var(--header-h) + 16px)}
.player-frame{
  position:relative;aspect-ratio:16/9;border-radius:var(--r-lg);overflow:hidden;
  border:1px solid var(--line);background:#000;box-shadow:var(--sh-3);
}
.player-frame iframe{width:100%;height:100%;border:0}
.player-title{margin-top:1.1rem;font-size:var(--t-h3);font-weight:550;letter-spacing:-.02em}

.lesson-layout{display:grid;grid-template-columns:1.6fr .95fr;gap:2.4rem;align-items:start}
@media(max-width:980px){.lesson-layout{grid-template-columns:1fr}.player{position:static}}

/* --- wide layout: big player, lesson list underneath ---------------------- */
/* The player is the reason the page exists, so it gets the full column width
   and the lessons run beneath it in two columns. */
.lesson-layout--wide{grid-template-columns:1fr;gap:2.8rem}
.lesson-layout--wide .player{position:static}
.lesson-layout--wide .player-stage,
.lesson-layout--wide .player-title,
.lesson-layout--wide .player>p{width:100%;max-width:1040px;margin-inline:auto}
.lesson-layout--wide .player-title{text-align:center}
/* Everything under the player is centred on it — the title, the download
   button and the blurb. The blurb is prose rather than a layout row, so it is
   also held to a readable measure instead of running the full 1040. */
.lesson-layout--wide .player>p{text-align:center}
.lesson-layout--wide .player>p.small{max-width:62ch}
/* One column, the same width as the player, so the list and the video share an
   edge. Two columns were tried and read as ragged — module lengths differ far
   too much for a row-based grid to balance. */
/* width:100% is not redundant. A grid item with auto inline margins opts out of
   stretch and falls back to fit-content — which collapsed this list to 340px.
   A definite width restores the intended 1040 and lets the margins centre it. */
.lesson-layout--wide .lesson-list{width:100%;max-width:1040px;margin-inline:auto}
.lesson-layout--wide .lesson-list>.module{margin-bottom:1rem}
.lesson-layout--wide .curriculum-head{
  display:flex;align-items:baseline;justify-content:space-between;gap:1rem;
  margin-bottom:1.1rem;padding-bottom:.7rem;border-bottom:1px solid var(--line-soft);
}

/* --- the stage ------------------------------------------------------------
   Reserves the 16:9 space up front so the page cannot jump while the embed
   loads. There was a mini-player here that lifted the frame out of flow and
   docked it into the corner on scroll; it has been removed. A video that
   follows you down the page is a thing to dismiss, not a convenience, and the
   list underneath already solves the problem it was solving — clicking a
   lesson now returns you to the player rather than bringing a small copy of
   the player to you.
   Note that the removal is only meaningful because the iframe was never
   re-parented in the first place: it is the same element in the same place in
   the DOM, so playback is untouched by any of this. */
.player-stage{aspect-ratio:16/9}
.player-stage>.player-frame{height:100%}

/* --- the gate -------------------------------------------------------------
   Where the free-account panel and the members-only panel go when there is no
   video to show. Both hold real content — an icon, a heading, a sentence, one
   or two buttons and a footnote — and content is the one thing a fixed aspect
   ratio cannot accommodate. At 390px wide, 16:9 gives 219px of box for 426px
   of panel, and `overflow:hidden` on the frame quietly ate both buy buttons on
   every phone that has ever loaded a locked course page.

   So the ratio becomes a floor rather than a rule: min-height holds the same
   reassuring block of space on a wide screen, where the panel has room to
   spare, and gets out of the way on a narrow one, where it does not. Flex
   rather than a percentage height because a percentage resolves against the
   parent's *height*, which is auto here — it would collapse straight back to
   the aspect ratio and undo the whole thing. */
.player-stage--gate{aspect-ratio:auto;display:flex;min-height:clamp(280px,40vw,520px)}
.player-stage--gate>.player-frame{aspect-ratio:auto;height:auto;flex:1;padding:1.75rem 1rem}

/* "Already have one? Log in." is the last line of both panels, and on a phone
   it wrapped between "Log" and "in" — an underlined link split across two lines
   reads as two broken links rather than one. The phrase moves as a unit. */
.player-stage--gate .link{white-space:nowrap}

.module{
  border:1px solid var(--line);border-radius:var(--r);overflow:hidden;
  background:var(--surface);margin-bottom:.9rem;box-shadow:var(--sh-1)
}
.module-head{
  padding:.85rem 1.1rem;background:var(--surface-2);border-bottom:1px solid var(--line);
  font-size:var(--t-sm);font-weight:550;letter-spacing:-.01em;
  display:flex;justify-content:space-between;gap:1rem;align-items:center;
}
.module-head .dim{font-family:var(--font-mono);font-size:var(--t-2xs);font-weight:400}
.module-head .dim.is-done{color:var(--green)}
.lesson{
  position:relative;display:flex;align-items:center;gap:.72rem;width:100%;
  text-align:left;padding:.7rem 1.1rem;background:none;border:0;
  border-bottom:1px solid var(--line-soft);cursor:pointer;font-size:.895rem;
  transition:background-color var(--fast),padding-left var(--fast) var(--ease);
}
.lesson:last-child{border-bottom:0}
.lesson:hover{background:var(--surface-2);padding-left:1.25rem}
.lesson::before{
  content:"";position:absolute;left:0;top:0;bottom:0;width:2px;
  background:var(--grad);opacity:0;transition:opacity var(--fast);
}
.lesson[aria-current=true]{background:var(--accent-quiet)}
.lesson[aria-current=true]::before{opacity:1}
.lesson[aria-current=true] .lesson-title{color:var(--fg);font-weight:550}
.lesson-ico{flex-shrink:0;width:16px;height:16px;stroke:var(--fg-3);fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.lesson[aria-current=true] .lesson-ico{stroke:var(--accent)}
.lesson-title{flex:1;color:var(--fg-2);line-height:1.42}
.lesson-dur{
  flex-shrink:0;color:var(--fg-3);font-family:var(--font-mono);
  font-size:var(--t-2xs);font-variant-numeric:tabular-nums;letter-spacing:.03em;
}
.lesson-locked{cursor:not-allowed;opacity:.5}
.lesson-locked:hover{background:none;padding-left:1.1rem}

/* --- watched / not watched ------------------------------------------------ */
/* The row is a flex pair: the lesson button, and a tick that toggles the
   watched state by hand. The border moves to the row so the two never
   double up on it. */
.lesson-row{
  display:flex;align-items:stretch;
  border-bottom:1px solid var(--line-soft);
  transition:background-color var(--fast);
}
.lesson-row:last-child{border-bottom:0}
.lesson-row .lesson{border-bottom:0;min-width:0}
.lesson-row:hover .lesson{background:var(--surface-2);padding-left:1.25rem}
.lesson-row:hover{background:var(--surface-2)}
/* The "now playing" tint belongs to the whole row, not just the button, or it
   stops dead in a hard line where the tick column starts. */
.lesson-row.is-current{background:var(--accent-quiet)}
.lesson-row.is-current .lesson[aria-current=true]{background:none}
.lesson-check{
  flex-shrink:0;display:grid;place-items:center;width:46px;
  background:none;border:0;cursor:pointer;padding:0;
  transition:background-color var(--fast);
}
.lesson-check svg{
  width:19px;height:19px;fill:none;stroke:var(--fg-3);stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round;opacity:.5;
  transition:stroke var(--fast),opacity var(--fast);
}
.lesson-check .tick{
  stroke-dasharray:14;stroke-dashoffset:14;
  transition:stroke-dashoffset var(--med) var(--ease);
}
.lesson-check:hover svg{opacity:1}
.lesson-row.is-watched .lesson-check svg{stroke:var(--green);opacity:1}
.lesson-row.is-watched .lesson-check .tick{stroke-dashoffset:0}
.lesson-row.is-watched .lesson-title{color:var(--fg-3)}
.lesson-row.is-watched .lesson-ico{opacity:.55}
@media(prefers-reduced-motion:reduce){.lesson-check .tick{transition:none}}

/* --- course progress bar -------------------------------------------------- */
.course-progress{margin-bottom:1.4rem}
.prog-track{
  height:5px;border-radius:999px;overflow:hidden;
  background:var(--surface-2);border:1px solid var(--line-soft);
}
.prog-fill{
  display:block;height:100%;width:0;border-radius:999px;
  background:var(--grad);
  transition:width var(--slow,.6s) var(--ease);
}
.prog-meta{
  display:flex;align-items:center;justify-content:space-between;
  gap:1rem;margin-top:.55rem;flex-wrap:wrap;
}
.prog-actions{display:flex;gap:1.1rem;align-items:center}
.prog-link{
  background:none;border:0;padding:0;cursor:pointer;
  font-family:var(--font-mono);font-size:var(--t-2xs);letter-spacing:.05em;
  color:var(--fg-3);text-transform:uppercase;
  transition:color var(--fast);
}
.prog-link:hover{color:var(--accent)}
.prog-link[data-prog-resume]{color:var(--accent)}
.prog-link[data-prog-resume]:hover{color:var(--fg)}

/* --- progress on a course card -------------------------------------------- */
.card-prog{
  display:flex;align-items:center;gap:.6rem;margin-top:.9rem;max-width:420px;
}
/* display:flex above beats the browser's [hidden]{display:none}, so say it. */
.card-prog[hidden]{display:none}
.card-prog .prog-track{flex:1;height:4px}
.card-prog span{
  font-family:var(--font-mono);font-size:var(--t-2xs);color:var(--fg-3);
  font-variant-numeric:tabular-nums;flex-shrink:0;
}
/* Written lesson (no video). Readable, not locked — full opacity, no hover. */
.lesson-text{cursor:default}
.lesson-text:hover{background:none;padding-left:1.1rem}
.lesson-text .lesson-dur{color:var(--accent)}

.locked-note{
  border:1px solid color-mix(in srgb,var(--accent) 30%,transparent);
  background:var(--grad-soft);border-radius:var(--r-lg);
  padding:1.7rem;text-align:center;
}

/* --------------------------------------------------------- 13a newsletter */
/* The list is the top of the funnel — every free course ends with somebody on
   it — so the block is built as an offer, not as a footer strip.

   It used to be two columns: copy on the left, field on the right. The two sides
   are never the same height — four lines of prose against one input — so with
   the columns vertically centred the block always had a hole in it, and the
   "Free. No spam, ever." line floated in the middle of that hole with nothing
   under it. A block whose emptiness moves around depending on how long the
   sentence is cannot be made to look composed.

   So: one centred column on a fixed measure. Chip, headline, a line of prose,
   the field, the reassurance — read in the order they matter, each one centred
   under the last. Nothing can leave a gap, because there is only one column and
   every piece of copy is free to be as long as it needs to be. */
.news-box{
  position:relative;overflow:hidden;isolation:isolate;
  padding:clamp(2.3rem,4.4vw,3.2rem) clamp(1.4rem,4vw,2.6rem);
  border:1px solid color-mix(in srgb,var(--accent) 22%,var(--line));
  border-radius:var(--r-xl);
  background:var(--grad-soft),var(--surface);
  box-shadow:var(--sh-1),var(--edge);
  text-align:center;
}
/* A single lit edge along the top, the same device the price cards use. It is
   what keeps a box this quiet from reading as a disabled panel. */
.news-box::before{
  content:"";position:absolute;inset:0 0 auto;height:1px;
  background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--accent) 65%,transparent),transparent);
}
/* Light falling from above the block, brightest behind the headline and gone by
   the time it reaches the field. isolation:isolate on the parent is what lets a
   negative z-index sit above the panel's own background instead of behind it. */
.news-box::after{
  content:"";position:absolute;z-index:-1;pointer-events:none;
  left:50%;top:-46%;width:min(720px,124%);aspect-ratio:2/1;transform:translateX(-50%);
  background:radial-gradient(closest-side,color-mix(in srgb,var(--accent) 26%,transparent),transparent 72%);
  opacity:.75;
}
.news-inner{
  position:relative;
  max-width:53ch;margin-inline:auto;
  display:flex;flex-direction:column;align-items:center;
}
/* The label. A pill instead of the flanked .eyebrow used elsewhere — in a
   centred stack the eyebrow's two dashes fight the headline underneath it. */
.news-chip{
  display:inline-flex;align-items:center;gap:.42rem;
  padding:.34rem .74rem .34rem .6rem;border-radius:100px;
  font-family:var(--font-mono);font-size:var(--t-2xs);font-weight:500;
  letter-spacing:.09em;text-transform:uppercase;
  color:var(--accent);
  background:var(--accent-quiet);
  border:1px solid color-mix(in srgb,var(--accent) 26%,transparent);
}
.news-chip svg{opacity:.9}
.news-head{
  margin-top:.95rem;
  font-size:clamp(1.4rem,2.6vw,1.85rem);font-weight:520;
  letter-spacing:-.028em;line-height:1.16;
}
.news-text{
  margin-top:.6rem;max-width:50ch;
  font-size:var(--t-sm);color:var(--fg-2);line-height:1.6;
}
/* The field and the button live inside one capsule and light up together, so
   the pair reads as a single control rather than as two things that happen to
   be next to each other. */
.news-form{
  display:flex;align-items:center;
  width:100%;max-width:430px;margin-top:1.55rem;
  padding:5px;border-radius:100px;
  border:1px solid var(--line-2);background:var(--bg-2);
  box-shadow:var(--edge);
  transition:border-color var(--fast) var(--ease),box-shadow var(--fast) var(--ease);
}
.news-form:focus-within{
  border-color:color-mix(in srgb,var(--accent) 58%,transparent);
  box-shadow:0 0 0 3px var(--accent-quiet),var(--edge);
}
.news-input{
  flex:1 1 auto;min-width:0;
  padding:.72rem .5rem .72rem 1.1rem;
  border:0;background:transparent;color:var(--fg);
  font:inherit;font-size:var(--t-sm);
}
.news-input::placeholder{color:var(--fg-3);opacity:.85}
.news-input:focus{outline:none}
/* Inside the capsule the button carries no drop glow — a shadow cast onto the
   surface it is sitting in looks like a rendering fault, not depth. */
.news-btn{
  flex:0 0 auto;white-space:nowrap;padding:.72rem 1.35rem;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.22);
}
.news-btn:hover{box-shadow:0 8px 22px -10px rgba(107,77,255,.9),inset 0 1px 0 rgba(255,255,255,.28)}
.news-note{margin-top:.9rem;font-size:var(--t-xs);color:var(--fg-3)}
.news-note svg{opacity:.6}

@media(max-width:560px){
  /* The capsule takes its chrome off and hands it to the input, so the two
     controls can stack without a pill drawn around the pair of them. */
  .news-form{
    flex-direction:column;gap:.55rem;max-width:none;
    padding:0;border:0;background:none;box-shadow:none;
  }
  .news-form:focus-within{box-shadow:none}
  .news-input{
    width:100%;padding:.85rem 1.05rem;text-align:center;
    border:1px solid var(--line-2);border-radius:100px;background:var(--bg-2);
    transition:border-color var(--fast),box-shadow var(--fast);
  }
  .news-input:focus{
    border-color:color-mix(in srgb,var(--accent) 58%,transparent);
    box-shadow:0 0 0 3px var(--accent-quiet);
  }
  .news-btn{width:100%;box-shadow:var(--glow),inset 0 1px 0 rgba(255,255,255,.22)}
}

/* --------------------------------------------- 13b thank-you / next steps */
/* The tick above the headline on the post-checkout page. Green rather than
   brand violet: this one moment is about reassurance, not about the brand. */
.thanks-mark{
  width:58px;height:58px;border-radius:50%;margin:0 auto 1.4rem;
  display:flex;align-items:center;justify-content:center;
  background:var(--green-quiet);border:1px solid var(--green-line);
}
.thanks-mark svg{
  width:26px;height:26px;fill:none;stroke:var(--green);stroke-width:2.4;
  stroke-linecap:round;stroke-linejoin:round;
}

/* Numbered "what happens next" list. Counters rather than an ordered-list
   marker so the number can sit in its own disc beside the text. */
.steps{list-style:none;counter-reset:step;display:flex;flex-direction:column;gap:.85rem}
.steps li{
  counter-increment:step;position:relative;
  padding:1.25rem 1.4rem 1.3rem 3.9rem;
  border:1px solid var(--line);border-radius:var(--r);
  background:var(--surface);box-shadow:var(--edge);
}
.steps li::before{
  content:counter(step);position:absolute;left:1.35rem;top:1.2rem;
  width:1.75rem;height:1.75rem;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  font-family:var(--font-mono);font-size:var(--t-xs);
  background:var(--accent-quiet);color:var(--accent);
}
.steps .step-h{font-weight:600;letter-spacing:-.015em}
.steps .step-h+p{margin-top:.4rem}
@media(max-width:520px){
  .steps li{padding:1.15rem 1.15rem 1.2rem 3.5rem}
  .steps li::before{left:1.1rem}
}

/* ------------------------------------------------------- 14 FAQ */
.faq-group+.faq-group{margin-top:2.8rem}
.faq-group h3{
  font-family:var(--font-mono);font-size:var(--t-2xs);text-transform:uppercase;
  letter-spacing:.18em;color:var(--fg-3);margin-bottom:.7rem;font-weight:400;
}
.faq-item{border-bottom:1px solid var(--line-soft)}
.faq-q{
  width:100%;display:flex;justify-content:space-between;align-items:flex-start;
  gap:1.2rem;padding:1.2rem 0;background:none;border:0;cursor:pointer;text-align:left;
  font-weight:500;font-size:1.01rem;line-height:1.45;letter-spacing:-.015em;
  transition:color var(--fast);
}
.faq-q:hover{color:var(--accent)}
.faq-ico{flex-shrink:0;width:17px;height:17px;margin-top:.26rem;stroke:var(--fg-3);stroke-width:1.9;fill:none;stroke-linecap:round;transition:transform var(--med) var(--ease),stroke var(--fast)}
.faq-q[aria-expanded=true] .faq-ico{transform:rotate(45deg);stroke:var(--accent)}
.faq-a{display:grid;grid-template-rows:0fr;transition:grid-template-rows var(--med) var(--ease)}
.faq-q[aria-expanded=true]+.faq-a{grid-template-rows:1fr}
.faq-a>div{overflow:hidden}
.faq-a p{color:var(--fg-2);padding-bottom:1.25rem;font-size:.95rem;line-height:1.68;max-width:68ch}
.faq-a a{color:var(--accent);text-decoration:underline;text-underline-offset:.2em}

/* ------------------------------------------------------- 15 forms */
.form-card{max-width:470px;margin-inline:auto}

/* --- the auth panels on /login/ and /signup/ ------------------------------
   A plain .card was doing the job of a front door: too tight at the edges, and
   flat against a page that has a gradient wash behind it. .auth-card gives the
   panel real breathing room, a hairline of brand colour along the top edge, and
   a soft inner highlight so it reads as a lit surface rather than a rectangle. */
.auth-card{
  position:relative;overflow:hidden;
  padding:2.4rem 2.2rem 2.2rem;
  border-radius:24px;
  background:
    radial-gradient(120% 100% at 50% -20%,var(--accent-quiet),transparent 62%),
    var(--surface);
  border:1px solid var(--line);
  box-shadow:var(--sh-2),var(--edge),inset 0 0 0 1px rgba(255,255,255,.02);
}
/* Gradient hairline across the top, fading out at both ends. */
.auth-card::before{
  content:"";position:absolute;top:0;left:12%;right:12%;height:1px;
  background:linear-gradient(90deg,transparent,var(--accent),transparent);
  opacity:.85;
}
.auth-card>:first-child{margin-top:0}
@media(max-width:520px){.auth-card{padding:1.8rem 1.3rem 1.6rem;border-radius:var(--r-lg)}}

/* Inside the panel the fields want more air between them than the default
   .sl-form rhythm gives — a login form is read one line at a time. */
.auth-card.sl-form input[type=text],
.auth-card.sl-form input[type=password],
.auth-card.sl-form input[type=email],
.auth-card.sl-form select,
.auth-card.sl-form textarea{
  padding:.92rem 1.05rem;margin-bottom:1.05rem;
}
.auth-card.sl-form label{margin-bottom:.45rem;letter-spacing:-.005em}
.auth-card.sl-form input[type=submit],
.auth-card.sl-form button[type=submit],
.auth-card.sl-form .btn-primary{padding:.95rem 1.5rem;margin-top:.35rem}

/* The header block above the form: eyebrow, headline, one line of copy. */
.auth-head{margin-bottom:1.9rem}
.auth-head .h1,.auth-head .h2{font-size:clamp(1.6rem,4vw,2rem);letter-spacing:-.035em}
.auth-head .eyebrow{justify-content:center}
.auth-foot{margin-top:1.6rem;padding-top:1.3rem;border-top:1px solid var(--line-soft)}
.field{display:flex;flex-direction:column;gap:.42rem;margin-bottom:1rem}
.field label{font-size:var(--t-sm);font-weight:500}
.field input,.field select,.field textarea{
  padding:.82rem 1rem;border-radius:var(--r-sm);border:1px solid var(--line-2);
  background:var(--bg-2);color:var(--fg);width:100%;
  transition:border-color var(--fast),box-shadow var(--fast);
}
.field input:focus,.field textarea:focus,.field select:focus{
  border-color:var(--accent);outline:none;
  box-shadow:0 0 0 3px var(--accent-quiet);
}
.alert{padding:.9rem 1.1rem;border-radius:var(--r-sm);font-size:var(--t-sm);margin-bottom:1.2rem;border:1px solid}
.alert-error{background:color-mix(in srgb,var(--red) 12%,transparent);border-color:color-mix(in srgb,var(--red) 35%,transparent);color:var(--red)}
.alert-info{background:var(--grad-soft);border-color:color-mix(in srgb,var(--accent) 30%,transparent);color:var(--fg-2)}

/* Sitelok writes its own markup into .sl-form — normalise it to match. */
.sl-form input[type=text],.sl-form input[type=password],.sl-form input[type=email],
.sl-form select,.sl-form textarea{
  padding:.82rem 1rem;border-radius:var(--r-sm);border:1px solid var(--line-2);
  background:var(--bg-2);color:var(--fg);width:100%;margin-bottom:.9rem;
  transition:border-color var(--fast),box-shadow var(--fast);
}
.sl-form input:focus,.sl-form select:focus,.sl-form textarea:focus{
  border-color:var(--accent);outline:none;box-shadow:0 0 0 3px var(--accent-quiet);
}
.sl-form input[type=submit],.sl-form button[type=submit],.sl-form .btn-primary{
  display:inline-flex;align-items:center;justify-content:center;
  padding:.85rem 1.5rem;border-radius:100px;border:0;background:var(--grad);
  color:#fff;font-weight:500;cursor:pointer;width:100%;
  box-shadow:var(--glow),inset 0 1px 0 rgba(255,255,255,.22);
}
.sl-form label{font-size:var(--t-sm);font-weight:500;display:block;margin-bottom:.35rem}
.sl-form a{color:var(--accent)}
.sl-form table{width:100%}
.sl-form .form-group{margin-bottom:.9rem}

/* --- taking the forms back from Sitelok ----------------------------------
   Everything above is written for markup we control. The login, sign-up and
   account forms are not that: Sitelok generates them, and it also generates a
   stylesheet from the appearance settings held against each form in its admin.
   That stylesheet is printed into <head> *after* main.css and its selectors
   carry an id — `div#slform_4 div.sltextfield_4 input[type="text"]`,
   `div#slform_4 #myButton_4`. Nothing class-scoped here can outrank that on
   specificity, and nothing at all can outrank it on source order, so the rules
   above never reached those three forms at all: black bold Arial labels sat on
   a dark card, inputs were square and white, the submit was a small blue box.

   !important is the honest tool for this, and the selectors are deliberately
   prefix-matched (`[id^="slform_"]`) rather than hard-coded to forms 4/5/10, so
   a form added in Sitelok later inherits the site's look without a code change.
   Scoped to .sl-form throughout — nothing outside those wrappers is touched. */
.sl-form [id^="slform_"]{
  width:100% !important;max-width:none !important;
  border:0 !important;padding:0 !important;margin:0 !important;
}

/* Field labels. Sitelok's are #000 bold 14px Arial; ours are quiet and inherit
   the site face so the eye lands on the value the visitor typed, not the label. */
.sl-form [class^="sltextfield_"] label,
.sl-form [class^="slcbfield_"] label{
  font:inherit !important;font-size:var(--t-sm) !important;font-weight:500 !important;
  color:var(--fg-2) !important;text-transform:none !important;
  margin:0 0 .45rem !important;padding:0 !important;letter-spacing:-.005em !important;
}
/* The required marker. Red on a dark panel reads as an error before anything
   has gone wrong; a dim asterisk says the same thing without the alarm. */
.sl-form [id^="slform_"] label em{
  font:inherit !important;font-size:.9em !important;color:var(--fg-3) !important;
  margin-left:.25em !important;vertical-align:baseline !important;
}

/* Field wrappers, and the reason sign-up and account never matched login.
   Sitelok's register and update designers hold a width *per field*, as a
   percentage, and write it against the wrapper's own id:

       #slfielddiv_10_0 { width:70%; max-width:400px }

   The login designer emits no such wrapper — which is the whole of why that
   one page's inputs have always sat flush with its button while the other two
   stopped short of theirs. An id beats anything class-scoped, and the input
   inside is only ever width:100% *of that wrapper*, so no amount of styling
   the input itself could ever have fixed it.

   Fixed here rather than by setting every field to 100% in two form designers,
   because a setting in an admin screen is a thing to remember for every field
   anyone adds later, and this is a thing that stays true on its own. Matched
   on the id prefix, so it covers text, select, checkbox and captcha rows in
   any form, including ones that do not exist yet. */
.sl-form [id^="slfielddiv_"]{width:100% !important;max-width:none !important}

.sl-form [class^="sltextfield_"]{margin:0 !important}
.sl-form [class^="sltextfield_"] input[type="text"],
.sl-form [class^="sltextfield_"] input[type="email"],
.sl-form [class^="sltextfield_"] input[type="password"],
.sl-form [id^="slform_"] input.slinput[type="text"],
.sl-form [id^="slform_"] input.slinput[type="email"],
.sl-form [id^="slform_"] input.slinput[type="password"]{
  font:inherit !important;font-size:.98rem !important;
  width:100% !important;box-sizing:border-box !important;
  padding:.92rem 1.05rem !important;margin:0 0 1.05rem !important;
  border:1px solid var(--line-2) !important;border-radius:var(--r-sm) !important;
  background:var(--bg-2) !important;background-image:none !important;color:var(--fg) !important;
  transition:border-color var(--fast),box-shadow var(--fast) !important;
}
.sl-form [id^="slform_"] input:focus{
  border-color:var(--accent) !important;outline:none !important;
  box-shadow:0 0 0 3px var(--accent-quiet) !important;
}
.sl-form [id^="slform_"] input::placeholder{color:var(--fg-3) !important}

/* Remember-me / terms rows: Sitelok stacks an inline checkbox against an inline
   bold label. A flex row aligns the box to the first line of the text instead. */
.sl-form [class^="slcbfield_"]{
  display:flex !important;align-items:center !important;gap:.6rem !important;
  margin:.1rem 0 1.1rem !important;
}
.sl-form [class^="slcbfield_"] label{
  display:inline !important;margin:0 !important;font-weight:400 !important;
  color:var(--fg-3) !important;
}
.sl-form [class^="slcbfield_"] input[type="checkbox"]{
  width:16px !important;height:16px !important;flex-shrink:0 !important;
  margin:0 !important;accent-color:var(--accent) !important;cursor:pointer !important;
}

/* The submit. Full width and pill-shaped, matching every other primary action
   on the site, so the form's last step looks like the site's own button. */
.sl-form [id^="myButton_"]{
  display:inline-flex !important;align-items:center !important;justify-content:center !important;
  width:100% !important;margin:.35rem 0 0 !important;padding:.95rem 1.5rem !important;
  font:inherit !important;font-size:.96rem !important;font-weight:500 !important;
  letter-spacing:-.01em !important;
  border:0 !important;border-radius:100px !important;
  background:var(--grad) !important;background-color:transparent !important;color:#fff !important;
  box-shadow:var(--glow),inset 0 1px 0 rgba(255,255,255,.22) !important;
  cursor:pointer !important;text-decoration:none !important;
  transition:transform var(--fast),box-shadow var(--fast) !important;
}
.sl-form [id^="myButton_"]:hover{
  background:var(--grad) !important;transform:translateY(-1px) !important;
  box-shadow:var(--glow),inset 0 1px 0 rgba(255,255,255,.28) !important;
}
.sl-form [id^="myButton_"]:active{top:0 !important;transform:translateY(0) !important}

/* Forgotten-password link — a browser-default blue underline otherwise. */
.sl-form [id^="slforgot_"]{
  display:block !important;margin:1rem 0 0 !important;
  font:inherit !important;font-size:var(--t-sm) !important;
  color:var(--fg-3) !important;text-align:center !important;
  text-decoration:none !important;cursor:pointer !important;
  border-bottom:0 !important;transition:color var(--fast) !important;
}
.sl-form [id^="slforgot_"]:hover{color:var(--accent) !important}

/* Validation messages. Kept red — that one really is an error — but sized and
   spaced like the rest of the form rather than 12px Arial jammed under a field. */
.sl-form [class^="slmsg_"],
.sl-form [class^="slformmsg_"]{
  font:inherit !important;font-size:var(--t-xs) !important;
  color:var(--red) !important;text-align:left !important;
  padding:0 !important;margin:-.75rem 0 .9rem !important;
}
.sl-form [class^="slformmsg_"]{
  text-align:center !important;margin:.9rem 0 0 !important;font-size:var(--t-sm) !important;
}
.sl-form [class^="slformmsg_"]:empty,.sl-form [class^="slmsg_"]:empty{margin:0 !important}

/* Sitelok's show-password eye is pulled back over the input with a negative
   margin. Keep it there, but clear of the wider padding, above the field, and
   legible on a dark background. */
.sl-form img[class^="viewpassimage_"]{
  position:relative !important;z-index:2 !important;
  margin-left:-38px !important;margin-right:22px !important;
  width:16px !important;cursor:pointer !important;
  filter:invert(1) !important;opacity:.5 !important;
}
[data-theme=light] .sl-form img[class^="viewpassimage_"]{filter:none !important;opacity:.45 !important}

/* --- /account/ panels ---------------------------------------------------- */
.acct-row{display:flex;gap:.9rem;align-items:flex-start}
.acct-ico{
  flex-shrink:0;display:grid;place-items:center;width:38px;height:38px;
  border-radius:12px;background:var(--grad-soft);border:1px solid var(--line);
  color:var(--accent);
}
.acct-ico svg{stroke:currentColor;fill:none;stroke-width:1.7}

/* ------------------------------------------------------- 16 dashboard */
.dash-head{
  border-radius:var(--r-2xl);padding:clamp(1.7rem,4vw,2.8rem);
  background:var(--bg-3);border:1px solid var(--line);
  position:relative;overflow:hidden;box-shadow:var(--sh-2),var(--edge);
}
[data-theme=light] .dash-head{background:var(--surface)}
.dash-head::after{
  content:"";position:absolute;inset:-60% -20% auto auto;width:460px;height:460px;
  pointer-events:none;
  background:radial-gradient(circle,color-mix(in srgb,var(--purple) 15%,transparent),transparent 65%);
}
.dash-head>*{position:relative;z-index:1}
.group-pills{display:flex;flex-wrap:wrap;gap:.45rem;margin-top:1.1rem}

.product{
  display:flex;gap:1.35rem;align-items:center;padding:1.25rem;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  box-shadow:var(--sh-1),var(--edge);
  transition:border-color var(--med) var(--ease),transform var(--med) var(--ease);
}
.product:hover{border-color:var(--line-2);transform:translateY(-2px)}
/* The course artwork carries the course name in the still, so at 72px it was
   unreadable and the row read as a list of grey squares. The wide thumbnail is
   now large enough for the title in the frame to be legible, and it fluid-scales
   so a narrow dashboard column never forces the text into a two-word ribbon. */
.product-thumb{
  flex-shrink:0;width:88px;height:88px;border-radius:15px;background:var(--surface-2);
  display:grid;place-items:center;overflow:hidden;border:1px solid var(--line);
}
.product-thumb img{width:100%;height:100%;object-fit:cover}
.product-thumb-wide{
  width:clamp(150px,22vw,196px);height:auto;aspect-ratio:16/9;border-radius:13px;
}
/* Stacked on a phone the thumbnail goes full width; matching the source ratio
   keeps the whole frame — and the course name inside it — visible. */
@media(max-width:520px){.product-thumb-wide{width:100%;max-width:none}}
.product-body{flex:1;min-width:0}
.product-actions{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:.85rem}
/* Version pill beside a product name — small, quiet, tabular, so "v3.2" reads
   as a fact about the file rather than as a second heading. */
.product-tag{
  display:inline-block;vertical-align:.14em;margin-left:.55rem;
  padding:.16rem .5rem;border-radius:999px;
  background:var(--accent-quiet);
  border:1px solid color-mix(in srgb,var(--accent) 28%,transparent);
  color:var(--accent);font-size:var(--t-2xs);font-weight:600;
  letter-spacing:.01em;font-variant-numeric:tabular-nums;
}
@media(max-width:520px){.product{flex-direction:column;align-items:flex-start}}

/* ------------------------------------------------------- 17 footer */
.site-footer{
  border-top:1px solid var(--line);background:var(--bg-2);
  padding-block:clamp(2.8rem,6vw,4rem) 2.2rem;margin-top:auto;
}
.footer-grid{display:grid;grid-template-columns:1.5fr repeat(4,1fr);gap:2.4rem}
@media(max-width:1040px){.footer-grid{grid-template-columns:repeat(4,1fr)}
  .footer-grid>:first-child{grid-column:1/-1}}
@media(max-width:820px){.footer-grid{grid-template-columns:1fr 1fr;gap:2rem}}
@media(max-width:480px){.footer-grid{grid-template-columns:1fr}}
.footer-h{
  font-family:var(--font-mono);font-size:var(--t-2xs);text-transform:uppercase;
  letter-spacing:.16em;color:var(--fg-3);font-weight:400;margin-bottom:1rem;
}
.footer-links{display:flex;flex-direction:column;gap:.6rem}
.footer-links a{color:var(--fg-2);font-size:var(--t-sm);transition:color var(--fast)}
.footer-links a:hover{color:var(--fg)}
.footer-bottom{
  margin-top:clamp(2.4rem,5vw,3.2rem);padding-top:1.5rem;border-top:1px solid var(--line-soft);
  display:flex;flex-wrap:wrap;gap:1rem;justify-content:space-between;align-items:center;
  color:var(--fg-3);font-size:var(--t-xs);
}

/* ------------------------------------------------------- 18 motion & misc */
.reveal{
  opacity:0;transform:translateY(16px);
  transition:opacity .6s var(--ease-out),transform .6s var(--ease-out);
  transition-delay:calc(var(--i,0) * 70ms);
}
.reveal.in{opacity:1;transform:none}

.skip{
  position:absolute;left:-999px;top:0;z-index:200;
  background:var(--surface);color:var(--fg);padding:.7rem 1.1rem;
  border-radius:0 0 10px 0;border:1px solid var(--line);
  font-weight:500;font-size:var(--t-sm);
}
.skip:focus{left:0}
/* Read by a screen reader, invisible to everybody else. Used for form labels
   whose purpose is already obvious from the layout — the newsletter field is
   one input beside one button, and a visible "Your email address" above it
   would be a caption on a thing that needs no caption. Clipped rather than
   display:none, which would take it out of the accessibility tree too. */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;
}
/* .anchor is a marker class on sections that fragment links point at. It no
   longer needs a scroll-margin — see the note beside :focus-visible. */
p>svg{display:inline-block;vertical-align:-.18em;margin-right:.35em;opacity:.75}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms!important;transition-duration:.01ms!important;
    scroll-behavior:auto!important;
  }
  .reveal{opacity:1;transform:none}
  .btn:hover,.card-hover:hover,.course-card:hover,.value:hover,.product:hover{transform:none}
  .course-card:hover .course-thumb img{transform:none}
}

/* ------------------------------------------------------- 19 small screens */
/* The type scale is tuned for a desk. On a phone the two smallest steps fall
   under 12px, which is where mono uppercase stops being an idiom and starts
   being a squint — the eyebrows, badges, stat labels, lesson durations and
   footer headings all live on those steps. Lifting the tokens rather than the
   thirty-odd rules that use them keeps the relationships intact. */
@media(max-width:820px){
  :root{
    --t-2xs:.765rem;   /* 12.24px */
    --t-xs:.835rem;    /* 13.36px */
    --t-sm:.925rem;
  }
}
@media(max-width:640px){
  /* Letter-spacing that reads as considered at 1440 reads as gappy at 390,
     where the same tracking eats a much larger share of the line. */
  .eyebrow,.stat-l,.footer-h{letter-spacing:.1em}
  .badge{letter-spacing:.06em}
}

/* Touch targets. Everything a finger has to hit gets at least 44px in the
   direction it is short in — the numbers below are hit area, not paint, so
   nothing here changes how the control looks on a desktop pointer.
   Scoped to coarse pointers so the header stays as tight as it was designed
   to be for a mouse. */
@media(hover:none) and (pointer:coarse){
  .brand{min-height:44px}
  .theme-toggle{width:42px;height:42px}
  .nav-toggle{
    width:44px;height:44px;display:grid;place-items:center;padding:0;
  }
  /* The header pair (Log in / Sign up) and every other small button. */
  .btn-sm,.nav-actions .btn{min-height:40px}
  .nav-links a{padding-block:1rem}
  /* A row in a curriculum is the single most-tapped thing on the site. */
  .lesson{min-height:46px}
  .lesson-check{width:52px}
  /* RESET / RESUME sit on a baseline row, so the extra height has to come from
     padding — a min-height would push the row apart instead of the hit box. */
  .prog-link{padding:.7rem .5rem;margin:-.7rem -.5rem}
  .footer-links{gap:.15rem}
  .footer-links a{display:block;padding-block:.55rem}
  .included-links a,.link{padding-block:.35rem}
}

@media print{
  .site-header,.site-footer,.btn,.theme-toggle,body::after{display:none}
  body{background:#fff;color:#000}
  .course-card,.card,.module,.price-card{box-shadow:none;border-color:#ccc}
}
