/* ==========================================================================
   Fluentbyte — base layer
   Design tokens, reset, typography, buttons, shared primitives.
   ========================================================================== */

:root {
  /* Neutrals — dark is the default canvas, like an Apple product page. */
  --bg: #000000;
  --bg-raise: #0a0a0c;
  --bg-card: #111114;
  --bg-card-hi: #17171c;
  --hairline: rgba(255, 255, 255, 0.1);
  --hairline-soft: rgba(255, 255, 255, 0.06);

  --text: #f5f5f7;
  --text-dim: #a1a1a8;
  --text-mute: #86868b; /* 5.3:1 on black — #6e6e78 failed AA at 13px */

  /* Track accents */
  --html: #ff6a3d;
  --css: #38a1ff;
  --js: #f7df1e;
  --python: #4bc98a;

  --accent: #ffffff;

  /* Links and destructive actions. Both were hardcoded at their dark-mode
     values and did not re-darken for light, so `.link-arrow` measured 2.5:1
     and the Reset/Delete controls 2.8:1 on white — both well under AA. */
  --link: #4aa8ff; /* 8.3:1 on black */
  --danger: #ff6a3d; /* 7.4:1 on black */

  /* The code surface is its own pair, not the page's. It stays dark in both
     themes on purpose — a light code block beside a dark one reads as a
     different component. Tokenised because the old rule keyed off
     `html:not([data-theme="dark"])`, which also matched the default `auto`,
     so the darker value only ever applied to an explicit dark choice. */
  --code-bg: #0d0d10;
  --code-fg: #e6e6ea;

  /* Type scale — tight tracking on the big sizes is what sells the look. */
  --font:
    -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Segoe UI', Roboto,
    Helvetica, Arial, sans-serif;
  --mono:
    'SF Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, 'Cascadia Code', 'Roboto Mono',
    monospace;

  --step-hero: clamp(2.75rem, 9vw, 7rem);
  --step-xl: clamp(2rem, 5.5vw, 4rem);
  --step-lg: clamp(1.5rem, 3.2vw, 2.25rem);
  --step-md: clamp(1.15rem, 1.8vw, 1.4rem);
  --step-base: 1.0625rem;
  --step-sm: 0.9375rem;
  --step-xs: 0.8125rem;

  /* Rhythm */
  --gutter: clamp(1.25rem, 5vw, 4rem);
  --measure: 46rem;
  --radius: 18px;
  --radius-sm: 11px;
  --radius-lg: 28px;

  /* Motion — one shared easing family keeps everything feeling related. */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-soft: cubic-bezier(0.33, 0, 0.15, 1); /* gentler than the old 0.4,0,0.2,1 */
  --ease-in: cubic-bezier(0.7, 0, 0.84, 0);
  --dur-fast: 220ms; /* was 180 — read as a snap rather than a movement */
  --dur: 480ms;
  --dur-slow: 900ms;

  --shadow: 0 30px 80px -30px rgba(0, 0, 0, 0.85);
  --nav-h: 56px;

  /* Shared semantic aliases and safe defaults for values components override
     at runtime. Keeping them rooted makes every var() resolve on first paint. */
  --surface: var(--bg-card);
  --line: var(--hairline);
  --muted: var(--text-mute);
  --code: var(--code-bg);
  --brand: var(--css);
  --success: var(--python);
  --delay: 0ms;
  --p: 0;
  --pct: 0;
  --score: 0;
  --shift: 0;
  --v: 0;
  --stack-gap: 1rem;
  --mx: 50%;
  --my: 0%;

  color-scheme: dark;
}

html[data-large-text] {
  font-size: 112.5%;
}
html[data-high-contrast] {
  --line: color-mix(in srgb, currentColor 45%, transparent);
  --muted: color-mix(in srgb, var(--text) 82%, transparent);
}
html[data-calm] .toast,
html[data-calm] [data-stagger],
html[data-calm] [data-reveal] {
  transition-duration: 120ms !important;
  animation: none !important;
}
.command-palette {
  width: min(620px, calc(100% - 2rem));
  max-height: min(70vh, 620px);
  border: 1px solid var(--line);
  border-radius: 18px;
  background: var(--surface);
  color: var(--text);
  padding: 0;
  box-shadow: 0 24px 80px rgb(0 0 0 / 0.35);
}
.command-palette::backdrop {
  background: rgb(0 0 0 / 0.55);
  backdrop-filter: blur(5px);
}
.command-palette form {
  padding: 1rem;
}
.command-palette label {
  display: grid;
  gap: 0.45rem;
  font-weight: 750;
}
.command-palette input {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 0.9rem;
  background: var(--bg);
  color: var(--text);
  font: inherit;
}
.command-results {
  display: grid;
  gap: 0.35rem;
  margin-top: 0.75rem;
}
.command-results a {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  border-radius: 10px;
  padding: 0.75rem;
}
.command-results a:hover,
.command-results a:focus,
.command-results a[data-active] {
  background: var(--bg);
}
.command-results span,
.command-palette form > p {
  color: var(--muted);
  font-size: 0.82rem;
}

/* Light mode: follows the system, overridable by the theme toggle. */
/* Light theme.
   The track accents are re-darkened here: the vivid dark-mode values are
   chosen against black and fail contrast badly on white — #f7df1e (JS
   yellow) lands around 1.1:1, which is unreadable at chip sizes. These
   replacements all clear 4.5:1 while staying recognisably the same hue. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme='dark']) {
    --bg: #ffffff;
    --bg-raise: #f5f5f7;
    --bg-card: #ffffff;
    --bg-card-hi: #f5f5f7;
    --hairline: rgba(0, 0, 0, 0.12);
    --hairline-soft: rgba(0, 0, 0, 0.06);
    --text: #1d1d1f;
    --text-dim: #4b4b52;
    --text-mute: #6b6b70;
    --accent: #1d1d1f;

    --html: #c2410c;
    --css: #0369a1;
    --js: #7a5c00;
    --python: #15803d;

    --link: #0369a1; /* 5.9:1 on white; #4aa8ff was 2.5:1 */
    --danger: #b23c0b; /* 5.9:1 on white; #ff6a3d was 2.8:1 */
    --code-bg: #17171c; /* a touch lifted so it is not a hole in a white page */

    --shadow: 0 24px 60px -28px rgba(0, 0, 0, 0.28);
    color-scheme: light;
  }
}

:root[data-theme='light'] {
  --bg: #ffffff;
  --bg-raise: #f5f5f7;
  --bg-card: #ffffff;
  --bg-card-hi: #f5f5f7;
  --hairline: rgba(0, 0, 0, 0.12);
  --hairline-soft: rgba(0, 0, 0, 0.06);
  --text: #1d1d1f;
  --text-dim: #4b4b52;
  --text-mute: #6b6b70;
  --accent: #1d1d1f;

  --html: #c2410c;
  --css: #0369a1;
  --js: #7a5c00;
  --python: #15803d;

  --link: #0369a1;
  --danger: #b23c0b;
  --code-bg: #17171c;

  --shadow: 0 24px 60px -28px rgba(0, 0, 0, 0.28);
  color-scheme: light;
}

:root[data-theme='dark'] {
  color-scheme: dark;
}

/* --------------------------------------------------------------- reset -- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 1rem);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--step-base);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img,
svg,
canvas,
iframe {
  display: block;
  max-width: 100%;
}
button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}
h1,
h2,
h3,
h4,
p,
figure,
ul,
ol {
  margin: 0;
}
ul,
ol {
  padding-left: 1.25em;
}
a {
  color: inherit;
  text-decoration: none;
}

/* Line breaking.

   Plain `wrap` gives the last line whatever happens to be left, which across
   this site left 19 paragraphs ending in a stub — the hero lede finished with
   "depth to match." alone, 23% of the measure, and a sidebar line ran to 11%.

   `pretty` is the running-text tool: a little lookahead to avoid a short last
   line, with no cap on paragraph length. `balance` equalises every line
   instead and browsers cap it at a handful, so it belongs on headings and
   short lead-ins — it is already on .display and .title, and .lede joins them
   below. Both degrade to `wrap` where unsupported, which is today's rendering,
   so this is purely additive. */
p,
li {
  text-wrap: pretty;
}

/* Colour changes (theme switch, hover) ease rather than snap. Deliberately
   scoped to colour so layout-affecting properties are never animated. */
body,
.nav,
.card,
.viz-card,
.setting-group,
.contact-row,
.side {
  transition:
    background-color var(--dur) var(--ease-soft),
    color var(--dur) var(--ease-soft),
    box-shadow var(--dur) var(--ease-soft);
}

:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
  border-radius: 6px;
}

::selection {
  background: rgba(120, 170, 255, 0.35);
}

/* ------------------------------------------------------------ typography */

.hero-type {
  font-size: var(--step-hero);
  line-height: 0.95;
  letter-spacing: -0.045em;
  font-weight: 700;
}

.display {
  font-size: var(--step-xl);
  line-height: 1.04;
  letter-spacing: -0.035em;
  font-weight: 700;
  text-wrap: balance;
}

.title {
  font-size: var(--step-lg);
  line-height: 1.15;
  letter-spacing: -0.02em;
  font-weight: 650;
  text-wrap: balance;
}

.lede {
  font-size: var(--step-md);
  line-height: 1.45;
  letter-spacing: -0.012em;
  color: var(--text-dim);
  max-width: 34ch;
  /* Short lead-in copy, two to four lines, often centred — balancing the whole
     block reads better here than only protecting the last line. */
  text-wrap: balance;
}

.eyebrow {
  font-size: var(--step-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--text-mute);
}

.muted {
  color: var(--text-dim);
}
.small {
  font-size: var(--step-sm);
}

.gradient-text {
  background: linear-gradient(96deg, #ff6a3d, #f7df1e 34%, #38a1ff 68%, #4bc98a);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;

  /* The gradient is painted into the element's background box and only then
     clipped to the glyph shapes, so a glyph pixel outside that box is never
     painted at all — it does not fall back to the text colour, because the
     text colour is transparent. It just disappears.

     `.hero-type` sets line-height to 0.95, which at 112px leaves a 106.4px box
     for text whose natural height is 132px. That is 12.8px of overflow above
     and below, and the descender of the y in "Actually understand it." lived
     in the bottom half of it — sheared off flat at the baseline.

     Grow the paint area and take the same amount back off the margin: the
     background box gets taller, the layout does not move. On the 404 page the
     element is an inline span, where vertical margins are ignored and the
     padding still extends the background box, so the same rule works there. */
  padding-block: 0.16em;
  margin-block: -0.16em;
}

/* ---------------------------------------------------------------- layout */

.wrap {
  width: min(100% - var(--gutter) * 2, 1180px);
  margin-inline: auto;
}

.wrap-narrow {
  width: min(100% - var(--gutter) * 2, 760px);
  margin-inline: auto;
}

.stack > * + * {
  margin-top: var(--stack-gap, 1rem);
}
.row {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  flex-wrap: wrap;
}
.center {
  text-align: center;
}
.center .lede {
  margin-inline: auto;
}

/* --------------------------------------------------------------- buttons */

.btn {
  --btn-bg: var(--text);
  --btn-fg: var(--bg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  padding: 0.7em 1.35em;
  border: 0;
  border-radius: 980px;
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: var(--step-sm);
  font-weight: 590;
  letter-spacing: -0.01em;
  cursor: pointer;
  transition:
    transform var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-soft),
    box-shadow var(--dur-fast) var(--ease-soft),
    opacity var(--dur-fast) var(--ease-soft);
  will-change: transform;
}
.btn:active {
  transform: translateY(0) scale(0.98);
}
.btn[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
  transform: none;
}

.btn-ghost {
  --btn-bg: transparent;
  --btn-fg: var(--text);
  box-shadow: inset 0 0 0 1px var(--hairline);
}

.btn-sm {
  padding: 0.45em 0.95em;
  font-size: var(--step-xs);
}

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  color: var(--link);
  font-size: var(--step-sm);
  font-weight: 500;
}
.link-arrow::after {
  content: '›';
  transition: transform var(--dur-fast) var(--ease-out);
  font-size: 1.25em;
  line-height: 0;
}

/* ----------------------------------------------------------------- chips */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.28em 0.7em;
  border-radius: 980px;
  background: var(--hairline-soft);
  box-shadow: inset 0 0 0 1px var(--hairline);
  font-size: var(--step-xs);
  font-weight: 550;
  letter-spacing: -0.005em;
  color: var(--text-dim);
  white-space: nowrap;
}
.chip .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.chip[data-track='html'] {
  color: var(--html);
}
.chip[data-track='css'] {
  color: var(--css);
}
.chip[data-track='js'] {
  color: var(--js);
}
.chip[data-track='python'] {
  color: var(--python);
}

/* ----------------------------------------------------------------- cards */

.card {
  background: var(--bg-card);
  border-radius: var(--radius-lg);
  box-shadow: inset 0 0 0 1px var(--hairline-soft);
  padding: clamp(1.25rem, 3vw, 2rem);
}

/* ------------------------------------------------------------------ code */

pre,
code,
kbd {
  font-family: var(--mono);
  font-size: 0.875em;
}

kbd {
  display: inline-block;
  min-width: 1.6em;
  padding: 0.15em 0.45em;
  border-radius: 6px;
  background: var(--hairline-soft);
  box-shadow:
    inset 0 0 0 1px var(--hairline),
    0 1px 0 var(--hairline);
  font-size: 0.78em;
  line-height: 1.5;
  text-align: center;
  color: var(--text);
  white-space: nowrap;
}

.code {
  position: relative;
  background: var(--code-bg);
  color: var(--code-fg);
  border-radius: var(--radius-sm);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
  overflow: hidden;
}

.code pre {
  margin: 0;
  padding: 1rem 1.1rem;
  overflow-x: auto;
  line-height: 1.6;
  tab-size: 2;
}

/* A mask leaves the element's own background visible beneath the final 34px,
   so the cue always matches light, dark, and code-panel themes. JavaScript
   removes this class at the scroll end; no animation is involved. */
.has-scroll-right {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent 100%);
  mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent 100%);
}

@media (prefers-reduced-motion: reduce) {
  .has-scroll-right {
    transition: none !important;
  }
}

.code-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.5rem 0.65rem 0.5rem 1rem;
  background: rgba(255, 255, 255, 0.04);
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.07);
  font-size: var(--step-xs);
  color: #9a9aa4;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-family: var(--font);
  font-weight: 600;
}

.copy-btn {
  background: transparent;
  border: 0;
  color: #9a9aa4;
  cursor: pointer;
  padding: 0.25em 0.6em;
  border-radius: 7px;
  font-size: 11px;
  letter-spacing: 0.04em;
  transition:
    background-color var(--dur-fast),
    color var(--dur-fast);
}

/* Syntax highlighting — hand-rolled, one palette for every language. */
.tok-com {
  color: #8b949e;
  font-style: italic;
} /* 6.3:1 / 5.8:1 — #6b7280 was 4.0/3.7 */
.tok-str {
  color: #7ee787;
}
.tok-num {
  color: #ffab70;
}
.tok-key {
  color: #ff7b9c;
}
.tok-fn {
  color: #7fb5ff;
}
.tok-tag {
  color: #ff7b9c;
}
.tok-atr {
  color: #ffd479;
}
.tok-pun {
  color: #8b8b95;
}
.tok-sel {
  color: #ffd479;
}
.tok-prp {
  color: #7fb5ff;
}

/* ------------------------------------------------------------------- nav */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  height: var(--nav-h);
  z-index: 100;
  display: flex;
  align-items: center;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  box-shadow: inset 0 -1px 0 var(--hairline-soft);
  transition:
    transform var(--dur) var(--ease-out),
    background-color var(--dur) var(--ease-soft);
}
.nav[data-hidden='true'] {
  transform: translateY(-100%);
}

.nav .wrap {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  width: min(100% - 2rem, 1180px);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-weight: 660;
  letter-spacing: -0.022em;
  font-size: var(--step-base);
  transition: opacity var(--dur-fast) var(--ease-soft);
}

/* Larger and higher-contrast: at 22px the conic gradient muddied into a grey
   dot at a glance. The inner ring lifts it off dark and light backgrounds. */
.brand-mark {
  width: 30px;
  height: 30px;
  border-radius: 9px;
  background: conic-gradient(
    from 210deg,
    var(--html),
    var(--js),
    var(--css),
    var(--python),
    var(--html)
  );
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.28),
    0 2px 8px -2px rgba(0, 0, 0, 0.5);
  flex: none;
  transition: transform var(--dur) var(--ease-out);
}

/* The footer mark can be larger still — there is room. */
.footer .brand {
  font-size: var(--step-sm);
}
.footer .brand-mark {
  width: 26px;
  height: 26px;
  border-radius: 8px;
}

.nav-links {
  display: flex;
  gap: 0.85rem;
  margin-left: auto;
  align-items: center;
  min-width: 0;
  font-size: var(--step-xs);
  color: var(--text-dim);
}
.nav-links a {
  transition: color var(--dur-fast);
}

/* The half of the bar that is allowed to scroll. Everything whose width is
   content-dependent — a display name, the level pill — belongs here. The
   dropdown does not: see the note on the narrow-screen rules below. */
.nav-controls {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  min-width: 0;
}

.icon-btn {
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 0;
  background: var(--hairline-soft);
  cursor: pointer;
  transition:
    background-color var(--dur-fast),
    transform var(--dur-fast) var(--ease-out);
}
.icon-btn:active {
  transform: scale(0.94);
}

/* Scroll progress hairline, sitting on the bottom edge of the nav. It rides
   up with the nav when that auto-hides, rather than stranding a stray line
   across the content. */
.scroll-rail {
  position: fixed;
  top: var(--nav-h);
  left: 0;
  right: 0;
  height: 2px;
  z-index: 101;
  pointer-events: none;
  transition: transform var(--dur) var(--ease-out);
}
.nav[data-hidden='true'] + .scroll-rail {
  transform: translateY(calc(-1 * var(--nav-h)));
}
.scroll-rail i {
  display: block;
  height: 100%;
  width: 100%;
  transform: scaleX(var(--p, 0));
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--html), var(--js), var(--css), var(--python));
}

/* ---------------------------------------------------------------- footer */

.footer {
  padding: 4rem 0 3rem;
  box-shadow: inset 0 1px 0 var(--hairline-soft);
  color: var(--text-mute);
  font-size: var(--step-xs);
}
.footer .wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2rem;
  align-items: center;
}
.copyright {
  color: var(--text-mute);
  font-size: var(--step-xs);
}

/* --------------------------------------------------------------- utility */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: fixed;
  top: 0.5rem;
  left: 0.5rem;
  z-index: 200;
  padding: 0.5em 1em;
  border-radius: 10px;
  background: var(--text);
  color: var(--bg);
  transform: translateY(-200%);
  transition: transform var(--dur-fast) var(--ease-out);
}
.skip-link:focus {
  transform: none;
}

/* ------------------------------------------- modal sheet, choices, toast --
   Shared: the placement dialog on the learn page and the account dialog on
   every page both use these. */

.sheet {
  position: fixed;
  inset: 0;
  z-index: 400;
  display: grid;
  place-items: center;
  padding: 1.5rem;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease-soft);
}
.sheet.open {
  opacity: 1;
  pointer-events: auto;
}

.sheet-card {
  width: min(100%, 30rem);
  padding: 1.75rem;
  border-radius: 24px;
  background: var(--bg-card);
  box-shadow:
    inset 0 0 0 1px var(--hairline),
    var(--shadow);
  transform: translateY(16px) scale(0.97);
  transition: transform var(--dur) var(--ease-out);
  display: grid;
  gap: 1rem;
  max-height: calc(100svh - 3rem);
  overflow-y: auto;
}
.sheet.open .sheet-card {
  transform: none;
}

.sheet-card h2 {
  font-size: var(--step-lg);
  letter-spacing: -0.02em;
  font-weight: 660;
}
.sheet-card p {
  color: var(--text-dim);
  font-size: var(--step-sm);
  line-height: 1.55;
}

.choices {
  display: grid;
  gap: 0.5rem;
}
.choice {
  display: grid;
  gap: 0.15rem;
  padding: 0.85rem 1rem;
  border-radius: 14px;
  border: 0;
  background: var(--hairline-soft);
  box-shadow: inset 0 0 0 1px transparent;
  text-align: left;
  cursor: pointer;
  color: var(--text);
  transition:
    background-color var(--dur-fast),
    transform var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast);
}
.choice[aria-pressed='true'] {
  box-shadow: inset 0 0 0 1.5px var(--css);
}
.choice b {
  font-size: var(--step-sm);
  font-weight: 600;
}
.choice span {
  font-size: var(--step-xs);
  color: var(--text-mute);
  line-height: 1.45;
}

.toast {
  position: fixed;
  left: 50%;
  bottom: 1.5rem;
  translate: -50% 0;
  z-index: 300;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.7rem 1.1rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bg-card) 88%, transparent);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  box-shadow:
    inset 0 0 0 1px var(--hairline),
    var(--shadow);
  font-size: var(--step-sm);
  opacity: 0;
  transform: translateY(14px);
  pointer-events: none;
  transition:
    opacity var(--dur) var(--ease-out),
    transform var(--dur) var(--ease-out);
  max-width: calc(100vw - 2rem);
  text-align: center;
}
.toast.show {
  opacity: 1;
  transform: none;
}

/* ------------------------------------------------------- nav dropdown -- */

.nav-menu {
  position: relative;
}

.nav-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  padding: 0.4em 0.7em 0.4em 0.9em;
  min-height: 34px;
  border: 0;
  border-radius: 980px;
  background: var(--hairline-soft);
  box-shadow: inset 0 0 0 1px var(--hairline);
  color: var(--text-dim);
  font-size: var(--step-xs);
  font-weight: 560;
  cursor: pointer;
  transition:
    background-color var(--dur-fast),
    color var(--dur-fast);
}
.nav-trigger[aria-expanded='true'] {
  background: var(--hairline);
  color: var(--text);
}

.nav-trigger .chev {
  transition: transform var(--dur-fast) var(--ease-out);
  flex: none;
}
.nav-trigger[aria-expanded='true'] .chev {
  transform: rotate(180deg);
}

.nav-panel {
  position: absolute;
  top: calc(100% + 0.55rem);
  right: 0;
  z-index: 120;
  min-width: 13rem;
  padding: 0.4rem;
  border-radius: 16px;
  background: color-mix(in srgb, var(--bg-card) 92%, transparent);
  backdrop-filter: blur(22px) saturate(180%);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  box-shadow:
    inset 0 0 0 1px var(--hairline),
    var(--shadow);
  display: grid;
  gap: 1px;

  opacity: 0;
  transform: translateY(-6px) scale(0.97);
  transform-origin: top right;
  pointer-events: none;
  transition:
    opacity 200ms var(--ease-out),
    transform 240ms var(--ease-out);
}
.nav-menu[data-open='true'] .nav-panel {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.nav-panel a {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 0.7rem;
  border-radius: 11px;
  font-size: var(--step-sm);
  font-weight: 520;
  color: var(--text-dim);
  white-space: nowrap;
  transition:
    background-color var(--dur-fast) var(--ease-soft),
    color var(--dur-fast) var(--ease-soft),
    transform var(--dur-fast) var(--ease-out);
}
.nav-panel a:focus-visible {
  background: var(--hairline-soft);
  color: var(--text);
}
.nav-panel a[aria-current='page'] {
  color: var(--text);
  font-weight: 600;
}
.nav-panel a[aria-current='page']::after {
  content: '';
  margin-left: auto;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--css);
}

.nav-panel hr {
  border: 0;
  height: 1px;
  background: var(--hairline-soft);
  margin: 0.3rem 0.2rem;
}

@media (max-width: 380px) {
  /* Only here is there truly not enough room beside brand + menu + theme. */
  .auth-btn {
    max-width: 9ch;
  }
}

@media (max-width: 560px) {
  .nav-panel {
    min-width: 12rem;
  }
  .nav-panel a {
    min-height: 44px;
  }
  .nav-trigger {
    min-height: 40px;
  }
  /* Room for a full name — "Alex Peterson" measures 121px here. */
  .auth-btn {
    max-width: 16ch;
    min-height: 40px;
  }
}

/* ------------------------------------------------------------ auth chrome */

.auth-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.35em 0.9em;
  border: 0;
  border-radius: 980px;
  background: var(--hairline-soft);
  box-shadow: inset 0 0 0 1px var(--hairline);
  color: var(--text-dim);
  font-size: var(--step-xs);
  font-weight: 560;
  cursor: pointer;
  /* Sized for an email prefix originally; a display name like "Alex Peterson"
     is 13 characters and was being clipped at 12ch. Room for a real name,
     still capped so a long one cannot push the nav around. */
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition:
    background-color var(--dur-fast) var(--ease-soft),
    color var(--dur-fast) var(--ease-soft),
    transform var(--dur-fast) var(--ease-out);
}
.auth-btn:active {
  transform: translateY(0);
}
.auth-btn[data-signed-in]::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--python);
  flex: none;
}

/* Form fields inside the auth sheet. */
.field {
  display: grid;
  gap: 0.35rem;
}
.field > span {
  font-size: var(--step-xs);
  font-weight: 600;
  color: var(--text-dim);
}
.field input {
  width: 100%;
  padding: 0.65em 0.85em;
  border: 0;
  border-radius: 11px;
  background: var(--hairline-soft);
  box-shadow: inset 0 0 0 1px var(--hairline);
  font-size: var(--step-sm);
  transition: box-shadow var(--dur-fast);
}
.field input:focus {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--css);
}
.field small {
  font-size: var(--step-xs);
  color: var(--text-mute);
  line-height: 1.45;
}

.auth-msg {
  padding: 0.6rem 0.8rem;
  border-radius: 11px;
  font-size: var(--step-xs);
  line-height: 1.5;
  background: rgba(255, 106, 61, 0.14);
  box-shadow: inset 0 0 0 1px rgba(255, 106, 61, 0.45);
  color: var(--text);
}
.auth-msg[data-tone='ok'] {
  background: rgba(75, 201, 138, 0.14);
  box-shadow: inset 0 0 0 1px rgba(75, 201, 138, 0.45);
}

/* ==========================================================================
   Narrow screens
   Measured at 390px before writing any of this: the nav row overflowed its
   container by 159px, which `overflow-x: hidden` on <body> silently clipped —
   the level pill and the theme toggle were on the page but unreachable.
   ========================================================================== */

@media (max-width: 720px) {
  /* The brand stays put and the controls scroll. A hamburger would need JS
     and a focus trap to hold five links; this needs neither. */
  .nav .wrap {
    gap: 0.75rem;
  }

  .nav-links {
    flex: 1 1 auto;
    min-width: 0;
    justify-content: flex-end;
    gap: 0.5rem;
  }

  /* Scrolling lives on .nav-controls, not on .nav-links. `overflow-x: auto`
     computes the other axis to `auto` as well, so this box clips vertically —
     which is what swallowed the dropdown when the menu was still a child of
     it: the panel opens below the 44px-tall row and was never painted. The
     menu is now a sibling of this box and escapes both the clip and the mask. */
  .nav-controls {
    flex: 0 1 auto;
    min-width: 0;
    justify-content: flex-end;
    gap: 0.5rem;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* Fade the left edge so a scrolled-away control reads as "more here"
       rather than as a hard crop. */
    mask-image: linear-gradient(to right, transparent, #000 18px);
    -webkit-mask-image: linear-gradient(to right, transparent, #000 18px);
  }
  .nav-controls::-webkit-scrollbar {
    display: none;
  }
  .nav-controls > * {
    flex: none;
  }
  .nav-menu {
    flex: none;
  }
}

/* Below this the word "Menu" costs more than it explains — the chevron plus
   the panel it opens carry it. Hidden the accessible way, so the button keeps
   its name for a screen reader. */
@media (max-width: 400px) {
  .nav-trigger-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
  .nav-trigger {
    width: 44px;
    padding-inline: 0;
    justify-content: center;
  }
  .nav-trigger .chev {
    margin: 0;
  }
}

@media (max-width: 560px) {
  /* The brand wordmark is the first thing to go — the mark alone still
     identifies the site and buys ~80px for the controls. */
  .brand {
    font-size: 0;
    gap: 0;
  }
  .brand-mark {
    font-size: 1rem;
  }

  :root {
    --step-hero: clamp(2.25rem, 11vw, 3.25rem);
  }
}

/* Touch targets. Applied by capability rather than width, because a small
   window on a desktop does not need 44px and a large tablet does. */
@media (hover: none) and (pointer: coarse) {
  .nav-links a,
  .nav-links button,
  .icon-btn,
  .auth-btn,
  .footer a {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
  .icon-btn {
    min-width: 44px;
    justify-content: center;
  }

  /* The lesson drawer is the densest list on the site — 56 rows a thumb has
     to hit accurately while the drawer is scrolling. */
  .side-link {
    min-height: 44px;
  }
  .pager a {
    min-height: 44px;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  /* Buttons were missed by the original pass: .btn-sm renders at 32px, which
     is well under the 44px minimum. It is the size used for most of the
     controls on the settings page. */
  .btn,
  .btn-sm,
  .copy-btn,
  .choice {
    min-height: 44px;
  }
  .btn,
  .btn-sm {
    padding-inline: 1.15em;
  }
  .copy-btn {
    display: inline-flex;
    align-items: center;
    padding-inline: 0.85em;
  }

  /* The wordmark is a link; 30px is a small target for the primary way home. */
  .brand {
    min-height: 44px;
  }

  /* Landed at 43px — one pixel short, and it is the first thing a keyboard
     or switch user reaches. */
  .skip-link {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
}

/* ==========================================================================
   Hover affordances

   Gathered here rather than left beside their components because they all
   answer the same question: is there a pointer that can hover? A touch tap
   in iOS Safari latches :hover onto the tapped element until you tap
   somewhere else, so a lift or a nudge stays applied — the theme toggle sat
   at scale(1.08), and the lesson row you just opened kept a highlight that
   read as "you are here". Everything below is decoration; the pressed and
   focus states that carry meaning live with their components.
   ========================================================================== */

@media (hover: hover) and (pointer: fine) {
  .btn:hover {
    transform: translateY(-1.5px) scale(1.02);
  }
  .btn-ghost:hover {
    --btn-bg: var(--hairline-soft);
  }
  .btn[disabled]:hover {
    transform: none;
  }

  .link-arrow:hover::after {
    transform: translateX(3px);
  }
  .copy-btn:hover {
    background: rgba(255, 255, 255, 0.09);
    color: #fff;
  }

  .brand:hover {
    opacity: 0.82;
  }
  .brand:hover .brand-mark {
    transform: rotate(-8deg) scale(1.06);
  }

  .icon-btn:hover {
    background: var(--hairline);
    transform: scale(1.08);
  }
  .footer a:hover {
    color: var(--text);
  }
  .nav-links a:hover {
    color: var(--text);
  }

  .choice:hover {
    background: var(--hairline);
    transform: translateX(3px);
  }
  .nav-trigger:hover {
    background: var(--hairline);
    color: var(--text);
  }
  .nav-panel a:hover {
    background: var(--hairline-soft);
    color: var(--text);
    transform: translateX(2px);
  }
  .auth-btn:hover {
    background: var(--hairline);
    color: var(--text);
    transform: translateY(-1px);
  }
}

/* Touch needs the feedback hover used to give, and it has to be instant —
   a 220ms ease reads as lag when the finger is already lifting. */
@media (hover: none) {
  a,
  button,
  summary,
  [role='button'] {
    -webkit-tap-highlight-color: transparent;
  }

  .btn:active,
  .btn-sm:active {
    transform: scale(0.97);
    transition-duration: 0ms;
  }
  .icon-btn:active,
  .nav-trigger:active,
  .auth-btn:active,
  .copy-btn:active,
  .choice:active,
  .nav-panel a:active {
    background: var(--hairline);
    transition-duration: 0ms;
  }
}

/* Safe areas — the fixed nav and the footer must clear a notch and a home
   indicator. Pairs with viewport-fit=cover in the page <head>. */
.nav .wrap {
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}
.footer {
  padding-bottom: max(2rem, env(safe-area-inset-bottom));
}

/* The three floating layers were all positioned against the raw viewport, so
   on a notched phone the toast sat under the home indicator, the skip link
   sat under the notch in landscape, and the dialog could run its own edges
   into both. */
.toast {
  bottom: max(1.5rem, calc(env(safe-area-inset-bottom) + 0.75rem));
}

.skip-link {
  top: max(0.5rem, env(safe-area-inset-top));
  left: max(0.5rem, env(safe-area-inset-left));
}

.sheet {
  padding: max(1.5rem, env(safe-area-inset-top)) max(1.5rem, env(safe-area-inset-right))
    max(1.5rem, env(safe-area-inset-bottom)) max(1.5rem, env(safe-area-inset-left));
}

/* A phone in landscape has ~390px of height, most of which the keyboard takes
   the moment a field in here is focused. svh keeps the card inside what is
   actually visible, and the inner scroll does the rest. */
@media (max-width: 560px), (max-height: 640px) {
  .sheet-card {
    padding: 1.35rem;
    border-radius: 20px;
    max-height: calc(100svh - 2rem);
  }
}

@media (max-width: 560px) {
  .footer .wrap {
    display: grid;
    grid-template-columns: 1fr;
    justify-items: start;
    gap: 1rem;
  }
  .footer .wrap > span:last-child {
    margin-left: 0 !important;
  }
  .sheet {
    place-items: start center;
    overflow-y: auto;
  }
  .sheet-card {
    width: 100%;
    gap: 0.8rem;
  }
}

@media (max-width: 360px), (max-height: 700px) {
  .sheet {
    padding: max(0.75rem, env(safe-area-inset-top)) 0.75rem
      max(0.75rem, env(safe-area-inset-bottom));
  }
  .sheet-card {
    padding: 1rem;
    border-radius: 18px;
    max-height: calc(100svh - 1.5rem);
  }
  .sheet-card h2 {
    font-size: 1.45rem;
  }
  .sheet-card p {
    line-height: 1.42;
  }
  .choice {
    padding: 0.7rem 0.8rem;
  }
}

/* --------------------------------------------------------------- hero try --
   An editor in the first viewport. Sized so that on a 390px phone the task,
   the code and the Run button all land above the fold — the point is that
   nothing has to be scrolled to before something can be done. */

.hero-try {
  margin: 1.5rem auto 0;
  max-width: 40rem;
  text-align: left;
}
.hero-try-task {
  display: block;
  font-size: var(--step-sm);
  color: var(--text-dim);
  margin-bottom: 0.5rem;
}
.hero-try-task code {
  font-family: var(--mono);
  color: var(--text);
}
.hero-try-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem;
  align-items: stretch;
}
#hero-code {
  width: 100%;
  min-height: 96px;
  resize: vertical;
  padding: 0.7rem 0.8rem;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--bg-card);
  box-shadow: inset 0 0 0 1px var(--hairline);
  color: var(--text);
  font-family: var(--mono);
  font-size: var(--step-sm);
  line-height: 1.5;
  tab-size: 2;
}
#hero-code:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}
#hero-preview {
  width: 100%;
  min-height: 96px;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--bg-card);
  box-shadow: inset 0 0 0 1px var(--hairline);
}
.hero-try-row {
  margin-top: 0.6rem;
  align-items: center;
  gap: 0.75rem;
}

@media (max-width: 700px) {
  .hero-try {
    margin-top: 1.1rem;
  }
  .hero-try-grid {
    grid-template-columns: 1fr;
  }
  #hero-code,
  #hero-preview {
    min-height: 78px;
  }
  /* 16px keeps iOS from zooming the whole page when the field is focused. */
  #hero-code {
    font-size: 16px;
  }
}
