/* ══════════════════════════════════════════════════════════════════════════
   Typefaces. Self-hosted latin subsets — no external requests, no layout
   shift, and the page keeps working offline.

   Unbounded (display) and IBM Plex (text/mono) are both SIL OFL, which
   permits bundling and redistribution. That matters here: this repo is
   public, and the fashionable display faces from Fontshare are licensed
   free for USE but forbid redistributing the font files, which committing
   them to a public repo arguably is. See TRADEMARKS in README.md.

   font-display: swap keeps text readable during load; the fallbacks are
   metric-ish so the reflow is small.
   ══════════════════════════════════════════════════════════════════════════ */
@font-face {
  font-family: 'Unbounded';
  src: url('fonts/unbounded-latin.woff2') format('woff2');
  font-weight: 400 900;            /* variable */
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Sans';
  src: url('fonts/plex-sans-400-latin.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Sans';
  src: url('fonts/plex-sans-600-latin.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Sans';
  src: url('fonts/plex-sans-700-latin.woff2') format('woff2');
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('fonts/plex-mono-400-latin.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}

/* ══════════════════════════════════════════════════════════════════════════
   Tiny Machines Lab marketing site
   Palette, mascot artwork and mascot animations are taken from the Tiny Machines Lab
   app itself (robocode/src/renderer/index.css) so the site and the product
   read as one thing. Keep them in sync if the app's look changes.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  /* surfaces — straight from the app shell */
  --bg:        #080c14;
  --bg-2:      #0a0e18;
  --panel:     #101a30;
  --panel-2:   #141d38;
  --raise:     #1b2440;

  /* lines */
  --line:      #1a2236;
  --line-2:    #22304f;
  --line-3:    #33406b;

  /* type */
  --ink:       #e6ecfb;
  --body:      #c8d3e8;
  --muted:     #8ea0c4;
  --dim:       #5f7099;

  /* brand */
  --blue:      #4d8ef8;
  --blue-2:    #3B6FD4;
  --blue-3:    #274F9E;
  --blue-lite: #8ab4ff;
  --teal:      #38D9C4;
  --gold:      #ffd166;
  --amber:     #F59E0B;
  --purple:    #a855f7;
  --green:     #22c55e;
  --red:       #ef4444;

  --r-sm: 10px;
  --r:    16px;
  --r-lg: 24px;

  --shadow:   0 18px 44px rgba(0,0,0,.5);
  --shadow-s: 0 8px 24px rgba(0,0,0,.45);

  --display: 'Unbounded', ui-sans-serif, system-ui, sans-serif;
  --font:    'IBM Plex Sans', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --mono:    'IBM Plex Mono', ui-monospace, "Cascadia Code", Consolas, monospace;

  --wrap: 1180px;
  --nav-h: 68px;
}

/* ── reset ──────────────────────────────────────────────────────────────── */
* { box-sizing: border-box; margin: 0; padding: 0; }
/* No scroll-padding in panel mode: each panel already reserves nav clearance in
   its own top padding, and a scroll-padding here would offset every snap by
   that much, parking a sliver of the previous panel on screen. The non-snap
   fallback puts it back, where anchor links do need it. */
html { scroll-behavior: smooth; scroll-padding-top: 0; }
body {
  background: var(--bg);
  color: var(--body);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
code { font-family: var(--mono); }

::selection { background: rgba(77,142,248,.35); color: #fff; }

/* ── film grain ───────────────────────────────────────────────────────────
   A single fixed overlay of fractal noise, baked into a data-URI so it is a
   static image to the compositor rather than a filter recomputed per paint.
   Kept very low: the point is to take the plastic sheen off flat dark panels,
   not to be a visible effect. (The loud "grainy gradient blob" version of
   this is itself a 2026 template cliché — see TRADEMARKS/design notes.) */
body::after {
  content: ''; position: fixed; inset: 0; z-index: 200; pointer-events: none;
  opacity: .035; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media (prefers-reduced-motion: reduce) { body::after { display: none; } }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--bg-2); }
::-webkit-scrollbar-thumb { background: #2a3550; border-radius: 5px; }
::-webkit-scrollbar-thumb:hover { background: #3d4f6e; }

.skip {
  position: absolute; left: -999px; top: 8px; z-index: 200;
  background: var(--blue); color: #fff; padding: 10px 16px; border-radius: 8px;
}
.skip:focus { left: 8px; }

:focus-visible { outline: 2px solid var(--teal); outline-offset: 3px; border-radius: 6px; }

.wrap { width: min(100% - 40px, var(--wrap)); margin-inline: auto; }

/* ── icons ────────────────────────────────────────────────────────────────
   Phosphor duotone, from the inline sprite at the top of the body. Both paths
   inherit currentColor, so an icon is tinted by setting `color` on it — the
   faint duotone layer picks up the same hue at 20%. */
.icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.ic {
  width: 1em; height: 1em; flex: none;
  fill: currentColor; vertical-align: -0.125em;
}

/* ── type scale ───────────────────────────────────────────────────────────
   Two voices, not one: Unbounded carries the big statements (h1/h2) and IBM
   Plex Sans carries everything a reader actually reads, including the smaller
   headings. Making every heading the display face flattens the hierarchy —
   the contrast between the two is what reads as art-directed.

   Unbounded is a WIDE face, so these sizes are well below what a normal
   grotesque would take, with tighter tracking to pull the counters in. */
h1, h2 {
  font-family: var(--display);
  color: var(--ink); font-weight: 800;
  line-height: 1.04; letter-spacing: -0.035em;
}
h3, h4 {
  font-family: var(--font);
  color: var(--ink); font-weight: 700;
  line-height: 1.2; letter-spacing: -0.012em;
}
h1 { font-size: clamp(1.85rem, 3.3vw, 2.6rem); }
h2 { font-size: clamp(1.5rem, 2.5vw, 2.05rem); }
h3 { font-size: clamp(1.2rem, 1.9vw, 1.5rem); }
h4 { font-size: 1.05rem; }

/* One accent, not a three-stop gradient. The teal is the mascot's own eye
   colour, so the emphasis ties to the character rather than to a trend. */
.grad { color: var(--teal); }

/* ── buttons ────────────────────────────────────────────────────────────── */
.btn {
  --btn-bg: transparent;
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 13px 24px; border: 2px solid transparent; border-radius: 999px;
  font-weight: 800; font-size: .95rem; cursor: pointer; white-space: nowrap;
  background: var(--btn-bg);
  transition: transform .18s cubic-bezier(.34,1.18,.64,1), box-shadow .18s ease,
              background .18s ease, border-color .18s ease, filter .18s ease;
}
.btn:hover  { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }

.btn-primary {
  --btn-bg: linear-gradient(135deg, var(--blue) 0%, #2f6fe0 100%);
  color: #fff; box-shadow: 0 8px 22px rgba(77,142,248,.32);
}
.btn-primary:hover { box-shadow: 0 12px 30px rgba(77,142,248,.45); filter: brightness(1.07); }

.btn-ghost {
  color: var(--ink); border-color: var(--line-3); background: rgba(27,36,64,.6);
}
.btn-ghost:hover { border-color: var(--blue); background: rgba(77,142,248,.12); }

.btn-sm    { padding: 9px 18px; font-size: .875rem; }
.btn-block { width: 100%; }

.play {
  width: 0; height: 0; border-left: 9px solid var(--teal);
  border-top: 6px solid transparent; border-bottom: 6px solid transparent;
}

/* ── nav ────────────────────────────────────────────────────────────────── */
.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 100; height: var(--nav-h);
  display: flex; align-items: center;
  transition: background .3s ease, border-color .3s ease, backdrop-filter .3s ease;
  border-bottom: 1px solid transparent;
}
.nav.stuck {
  background: rgba(8,12,20,.82); backdrop-filter: blur(14px);
  border-bottom-color: var(--line);
}
.nav-in { display: flex; align-items: center; gap: 28px; }

.brand { display: flex; align-items: center; gap: 10px; margin-right: auto; }
.brand-mark {
  display: grid; place-items: center; width: 38px; height: 38px; border-radius: 11px;
  background: linear-gradient(150deg, #16223c, #0e1628);
  border: 1px solid var(--line-2);
  transition: transform .3s cubic-bezier(.34,1.3,.64,1);
}
.brand:hover .brand-mark { transform: rotate(-8deg) scale(1.06); }
.brand-name {
  font-family: var(--display); font-weight: 700; font-size: 1.02rem;
  color: var(--blue-lite); letter-spacing: -.03em;
  display: flex; align-items: center; gap: 8px;
}

.nav-links { display: flex; gap: 26px; }
.nav-links a {
  position: relative; font-weight: 600; font-size: .94rem; color: var(--muted);
  transition: color .2s ease;
}
.nav-links a::after {
  content: ''; position: absolute; left: 0; bottom: -6px; height: 2px; width: 0;
  background: linear-gradient(90deg, var(--blue), var(--teal)); border-radius: 2px;
  transition: width .25s ease;
}
.nav-links a:hover, .nav-links a.active { color: var(--ink); }
.nav-links a:hover::after, .nav-links a.active::after { width: 100%; }

.nav-toggle {
  display: none; flex-direction: column; gap: 5px; width: 42px; height: 42px;
  align-items: center; justify-content: center; background: none; border: 0; cursor: pointer;
}
.nav-toggle span {
  display: block; width: 22px; height: 2px; background: var(--ink); border-radius: 2px;
  transition: transform .25s ease, opacity .2s ease;
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* ══ HERO ═══════════════════════════════════════════════════════════════ */
.hero {
  position: relative; min-height: 100svh; display: flex; align-items: center;
  padding: calc(var(--nav-h) + 40px) 0 80px; overflow: hidden;
}
.hero-bg { position: absolute; inset: 0; pointer-events: none; }
.grid-fade {
  position: absolute; inset: 0;
  background-image:
    linear-gradient(rgba(51,64,107,.16) 1px, transparent 1px),
    linear-gradient(90deg, rgba(51,64,107,.16) 1px, transparent 1px);
  background-size: 54px 54px;
  mask-image: radial-gradient(ellipse 85% 65% at 50% 40%, #000 35%, transparent 100%);
  -webkit-mask-image: radial-gradient(ellipse 85% 65% at 50% 40%, #000 35%, transparent 100%);
}
.orb { position: absolute; border-radius: 50%; filter: blur(90px); opacity: .5; }
.orb-1 { width: 480px; height: 480px; background: #1d4ed8; top: -120px; left: -100px; animation: drift 22s ease-in-out infinite; }
.orb-2 { width: 400px; height: 400px; background: #0d9488; bottom: -140px; right: -60px; animation: drift 27s ease-in-out infinite reverse; }
.orb-3 { width: 320px; height: 320px; background: #7c3aed; top: 40%; right: 22%; opacity: .3; animation: drift 19s ease-in-out infinite 3s; }
@keyframes drift {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33%      { transform: translate(40px, -30px) scale(1.08); }
  66%      { transform: translate(-30px, 25px) scale(.94); }
}

.hero-in {
  position: relative; display: grid; grid-template-columns: 1.12fr .88fr;
  gap: 40px; align-items: center;
}

.eyebrow {
  display: inline-flex; align-items: center; gap: 9px; margin-bottom: 22px;
  padding: 7px 15px 7px 11px; border-radius: 999px;
  background: rgba(56,217,196,.09); border: 1px solid rgba(56,217,196,.28);
  color: var(--teal); font-size: .82rem; font-weight: 700; letter-spacing: .01em;
}
.dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--teal);
  box-shadow: 0 0 0 0 rgba(56,217,196,.6); animation: ping 2.2s ease-out infinite;
}
@keyframes ping {
  0%   { box-shadow: 0 0 0 0 rgba(56,217,196,.55); }
  70%  { box-shadow: 0 0 0 9px rgba(56,217,196,0); }
  100% { box-shadow: 0 0 0 0 rgba(56,217,196,0); }
}

/* headline words lift in one after another (delays set in main.js) */
.hero h1.split .w {
  display: inline-block;
  opacity: 0; transform: translateY(.42em) rotate(1.4deg);
  animation: wordIn .72s cubic-bezier(.2,.92,.28,1) both;
}
@keyframes wordIn {
  to { opacity: 1; transform: none; }
}

.lede { font-size: clamp(1.03rem, 1.35vw, 1.19rem); color: var(--muted); max-width: 52ch; margin-top: 22px; }

.hero-cta { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 34px; }

.hero-stats {
  display: flex; gap: 34px; margin-top: 48px; padding-top: 28px;
  border-top: 1px solid var(--line); list-style: none; flex-wrap: wrap;
}
.hero-stats li { display: flex; flex-direction: column; }
/* display face on the numerals, solid ink — gradient-filled text was the
   other instance of the tell this pass is removing */
.hero-stats strong {
  font-family: var(--display);
  font-size: 1.6rem; font-weight: 700; color: var(--ink); line-height: 1;
  letter-spacing: -.03em;
}
.hero-stats span { font-size: .8rem; color: var(--dim); font-weight: 600; margin-top: 6px; }

/* hero art */
.hero-art { position: relative; display: grid; place-items: center; min-height: 420px; }
.mascot-stage { position: relative; z-index: 2; }
.mascot-glow {
  position: absolute; inset: auto 0 4%; height: 60%; z-index: -1;
  background: radial-gradient(ellipse at 50% 80%, rgba(77,142,248,.32), transparent 70%);
  filter: blur(28px);
}

/* `translate` is set from JS for the pointer parallax and composes with the
   bob animation on `transform`; the transition smooths the settle-back when
   the pointer leaves the window. */
.chip {
  position: absolute; z-index: 3; padding: 9px 15px; border-radius: 11px;
  font-family: var(--mono); font-size: .78rem; font-weight: 700; color: #fff;
  box-shadow: var(--shadow-s); white-space: nowrap; border: 1px solid rgba(255,255,255,.14);
  will-change: translate;
}
.mascot-stage { transition: translate .5s cubic-bezier(.22,.9,.3,1); }
.chip::before {
  content: ''; position: absolute; left: 16px; top: -1px; width: 15px; height: 5px;
  border-radius: 0 0 5px 5px; background: inherit;
}
.chip-1 { background: #f5a623; top: 6%;  left: -4%;  animation: bob 5.5s ease-in-out infinite; }
.chip-2 { background: #4d8ef8; top: 30%; right: -8%; animation: bob 6.4s ease-in-out infinite .7s; }
.chip-3 { background: #06b6d4; bottom: 24%; left: -10%; animation: bob 5.9s ease-in-out infinite 1.4s; }
.chip-4 { background: #22c55e; bottom: 6%; right: -2%; animation: bob 6.9s ease-in-out infinite .3s; }
@keyframes bob {
  0%, 100% { transform: translateY(0) rotate(-1.5deg); }
  50%      { transform: translateY(-16px) rotate(1.5deg); }
}

.scroll-hint {
  position: absolute; bottom: 74px; left: 50%; transform: translateX(-50%);
  width: 26px; height: 42px; border: 2px solid var(--line-3); border-radius: 14px;
  display: grid; justify-items: center; padding-top: 8px; z-index: 3;
  transition: border-color .2s ease;
}
.scroll-hint:hover { border-color: var(--teal); }
.scroll-hint span {
  width: 4px; height: 8px; border-radius: 2px; background: var(--teal);
  animation: scrolly 1.9s ease-in-out infinite;
}
@keyframes scrolly {
  0%, 100% { transform: translateY(0); opacity: 1; }
  60%      { transform: translateY(13px); opacity: 0; }
}

/* ── marquee ──────────────────────────────────────────────────────────────
   Pinned to the hero's bottom edge. It has to be positioned rather than left
   in flow: the hero is a centred column, so an in-flow marquee sat flat
   against the stats row with no gap AND sized itself to its own scrolling
   track (~2500px) instead of the panel. .panel.hero below reserves the space
   it now occupies. */
.hero .marquee {
  position: absolute; left: 0; right: 0; bottom: 0;
  border-top: 1px solid var(--line); border-bottom: none;
}
.marquee {
  border-block: 1px solid var(--line); background: var(--bg-2);
  overflow: hidden; padding: 15px 0;
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.marquee-track {
  display: flex; align-items: center; gap: 26px; width: max-content;
  animation: slide 38s linear infinite;
}
.marquee-track span { font-weight: 800; font-size: .92rem; color: var(--dim); white-space: nowrap; }
.marquee-track i { color: var(--blue); font-size: .6rem; font-style: normal; }
@keyframes slide { to { transform: translateX(-50%); } }

/* ══ SNAP PANELS ════════════════════════════════════════════════════════
   The page reads as a deck: one screen per idea, and the scroll always lands
   on a panel rather than halfway between two. Long topics (the three feature
   deep-dives, the class tiers) are split across several panels so that every
   snap target still fits in a viewport — a snap area taller than the screen
   is what makes mandatory snapping feel like a trap.                        */
html { scroll-snap-type: y mandatory; }

.panel {
  position: relative;
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  padding: calc(var(--nav-h) + 26px) 0 46px;
  scroll-snap-align: start;
  scroll-snap-stop: always;          /* one gesture = exactly one panel */
}
.section-alt { background: var(--bg-2); border-block: 1px solid var(--line); }

/* ── progressive rendering ──────────────────────────────────────────────────
   Only the hero is laid out and painted on first load; every other panel is
   skipped by the renderer until it matters. main.js then walks the remaining
   panels during idle time and marks each .warm, which forces its layout and
   paint EARLY — so by the time you scroll to a panel it is already built and
   there is nothing to do but show it.

   contain-intrinsic-size stands in for a skipped panel's height, so the page
   never changes length underneath you (which would otherwise throw off both
   the scrollbar and the snap positions). */
.panel:not(.hero) {
  content-visibility: auto;
  contain-intrinsic-size: auto 100svh;
}
.panel.warm { content-visibility: visible; }

/* The hero keeps a full screen even where other panels stop snapping, and
   reserves room at the bottom for the scroll hint and the pinned marquee so
   neither lands on the stats row. Two classes so it outranks .panel above. */
.panel.hero { min-height: 100svh; padding-bottom: 128px; }

/* an off-screen panel's animations cost nothing while it waits its turn */
.panel:not(.active):not(.hero) * { animation-play-state: paused; }

/* panel-local spacing: the standalone-section rhythm is too generous once
   everything has to fit one screen */
.panel .sec-head    { margin-bottom: clamp(26px, 4vh, 48px); }
.panel .app-mock    { margin-bottom: 0; }
.panel .feat-split  { margin-bottom: 0; }

/* Section heads are LEFT-aligned and off-centre by default. Repeating a
   centred kicker → H2 → subhead on every screen is the single loudest
   structural tell; an asymmetric head reads as laid out rather than poured
   into a template. `.sec-head-mid` opts a panel back into centred, used
   sparingly so the rhythm varies instead of repeating. */
.sec-head { max-width: 720px; margin: 0 0 clamp(44px, 6vw, 72px); text-align: left; }
.sec-head p { color: var(--muted); margin-top: 16px; font-size: 1.04rem; max-width: 58ch; }
.sec-head-mid { margin-inline: auto; text-align: center; }
.sec-head-mid p { margin-inline: auto; }

/* the kicker sits on a rule, so it reads as a section marker not a label */
.kicker {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: 16px; font-size: .74rem; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase; color: var(--teal);
  font-family: var(--mono);
}
.kicker::after {
  content: ''; height: 1px; flex: 1; max-width: 90px;
  background: linear-gradient(90deg, var(--teal), transparent);
}
.sec-head-mid .kicker { justify-content: center; }
.sec-head-mid .kicker::after { display: none; }

/* ══ APP MOCK ═══════════════════════════════════════════════════════════ */
.app-mock {
  border: 1px solid var(--line-2); border-radius: var(--r-lg); overflow: hidden;
  background: var(--panel); box-shadow: var(--shadow); margin-bottom: 64px;
}
.mock-bar {
  display: flex; align-items: center; gap: 16px; padding: 0 16px; height: 48px;
  background: var(--bg-2); border-bottom: 1px solid var(--line-2);
}
.dots { display: flex; gap: 6px; }
.dots i { width: 11px; height: 11px; border-radius: 50%; background: #2a3550; }
.dots i:first-child { background: #f87171; }
.dots i:nth-child(2) { background: #fbbf24; }
.dots i:last-child  { background: #4ade80; }

.mock-tabs { display: flex; gap: 6px; align-items: flex-start; margin-left: 12px; align-self: flex-start; }
/* the app's real tab shape: square on top, rounded below, grows on hover */
.mock-tab {
  align-self: flex-start; display: flex; align-items: center; gap: 7px;
  height: 30px; padding: 0 15px; border: 1px solid var(--line-2); border-top: none;
  border-radius: 0 0 14px 14px; background: var(--panel-2); color: var(--muted);
  font-weight: 800; font-size: .82rem; cursor: pointer;
  transition: height .18s cubic-bezier(.34,1.1,.64,1), background .18s ease, color .18s ease;
}
.mock-tab:hover { height: 36px; color: var(--ink); }
.mock-tab.is-on {
  height: 40px; background: linear-gradient(180deg, var(--blue), #2f6fe0); color: #fff;
  border-color: transparent;
}
.mock-tab.is-on[data-mode="robot"] { background: linear-gradient(180deg, #06b6d4, #0891b2); }
.mock-file { margin-left: auto; font-family: var(--mono); font-size: .78rem; color: var(--dim); }

.mock-body {
  display: grid; grid-template-columns: 1fr auto 1fr; gap: 0; min-height: 330px;
}
.mock-pane { padding: 20px 22px; }
.mock-blocks { background:
  radial-gradient(circle at 1px 1px, rgba(51,64,107,.5) 1px, transparent 0) 0 0/22px 22px,
  var(--panel); }
.mock-code { background: #070b13; border-left: 1px solid var(--line-2); }
.pane-label {
  font-size: .72rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
  color: var(--dim); margin-bottom: 16px;
}
.pane-label em { font-style: normal; color: var(--teal); text-transform: none; letter-spacing: 0; font-weight: 600; }

.mock-arrow { display: grid; place-items: center; padding: 0 4px; background: var(--panel); }

/* ── the blocks ───────────────────────────────────────────────────────────
   Interlocking-block behaviour: a stack is FLUSH (no gaps), each block
   dropping a tab into the one below it, and a C-block physically wraps its
   children with a left spine and a closing bar underneath.
   (Describe the mechanics, not another vendor's product — see TRADEMARKS in
   README.md.) */
.stack, .cbody { display: flex; flex-direction: column; align-items: flex-start; gap: 0; }

.blk, .cwrap {
  position: relative; color: #fff; font-weight: 700; font-size: .84rem;
  white-space: nowrap;
  opacity: 0; transform: translateX(-22px) scale(.94);
  animation: snapIn .42s cubic-bezier(.34,1.5,.64,1) forwards;
}
.cwrap { display: flex; flex-direction: column; align-items: flex-start; }

.blk  { padding: 10px 17px; border-radius: 6px; background: var(--c); }
.chead { padding: 10px 17px; border-radius: 6px 6px 0 0; background: var(--c); }
.blk.hat { border-radius: 15px 15px 6px 6px; padding-top: 13px; }

/* the C shape: a spine down the left of the body, then a bar closing it off */
.cbody { border-left: 14px solid var(--c); align-self: stretch; }
.cfoot { height: 14px; border-radius: 0 0 6px 6px; background: var(--c); }

/* the interlocking tab. It hangs BELOW the block and overlaps whatever comes
   next, which is what makes a stack read as physically joined rather than as
   a pile of separate chips. Blocks carry a descending z-index (set in JS) so
   an earlier block's tab paints over the later one. */
.blk::after, .cfoot::after {
  content: ''; position: absolute; left: 14px; bottom: -4px;
  width: 15px; height: 4px; border-radius: 0 0 4px 4px; background: var(--c);
}
.cfoot { position: relative; }
/* a hat block starts a script — nothing sits above it to plug into */
.blk.hat::before { content: none; }
@keyframes snapIn {
  0%   { opacity: 0; transform: translateX(-22px) scale(.94); }
  70%  { opacity: 1; transform: translateX(3px) scale(1.03); }
  100% { opacity: 1; transform: translateX(0) scale(1); }
}

/* room for the longest program reserved up front — the demo clears and refills
   this on every loop, and without it the whole section jumped each time. */
.mock-code pre { min-height: 196px; overflow-x: auto; }
#codeOut {
  font-family: var(--mono); font-size: .82rem; line-height: 1.85; color: var(--body);
  white-space: pre; display: block;
}
#codeOut .ln { display: block; opacity: 0; animation: fadeLine .3s ease forwards; }
@keyframes fadeLine { to { opacity: 1; } }
.c-k { color: #c084fc; } .c-f { color: #60a5fa; } .c-n { color: #fbbf24; }
.c-s { color: #86efac; } .c-c { color: #5f7099; font-style: italic; }
.c-d { color: #f5a623; } .c-b { color: #38D9C4; }

/* ══ FEATURES ═══════════════════════════════════════════════════════════ */
.feat-split {
  display: grid; grid-template-columns: 1fr 1fr; gap: clamp(32px, 5vw, 72px);
  align-items: center; margin-bottom: clamp(60px, 8vw, 110px);
}
.feat-flip .feat-text { order: 2; }

.tag {
  display: inline-block; margin-bottom: 16px; padding: 6px 14px; border-radius: 999px;
  font-size: .78rem; font-weight: 800; letter-spacing: .02em;
}
.tag-blue { background: rgba(77,142,248,.12); color: var(--blue-lite); border: 1px solid rgba(77,142,248,.3); }
.tag-teal { background: rgba(56,217,196,.1);  color: var(--teal);      border: 1px solid rgba(56,217,196,.3); }
.tag-gold { background: rgba(255,209,102,.1); color: var(--gold);      border: 1px solid rgba(255,209,102,.3); }

.feat-text p { color: var(--muted); margin-top: 15px; }
.feat-text p code {
  background: var(--raise); padding: 2px 7px; border-radius: 5px;
  font-size: .88em; color: var(--teal); white-space: nowrap;
}

.ticks { list-style: none; margin-top: 22px; display: grid; gap: 11px; }
.ticks li { position: relative; padding-left: 30px; font-size: .93rem; color: var(--body); }
.ticks li::before {
  content: ''; position: absolute; left: 0; top: .4em; width: 17px; height: 17px;
  border-radius: 50%; background: rgba(34,197,94,.15); border: 1px solid rgba(34,197,94,.4);
}
.ticks li::after {
  content: ''; position: absolute; left: 5.5px; top: .62em; width: 5px; height: 9px;
  border-right: 2px solid var(--green); border-bottom: 2px solid var(--green);
  transform: rotate(42deg);
}

.feat-art { min-width: 0; }

/* code card */
.code-card {
  border: 1px solid var(--line-2); border-radius: var(--r); overflow: hidden;
  background: #070b13; box-shadow: var(--shadow);
}
.cc-bar {
  display: flex; align-items: center; gap: 6px; padding: 11px 15px;
  background: var(--bg-2); border-bottom: 1px solid var(--line-2);
}
.cc-bar i { width: 10px; height: 10px; border-radius: 50%; background: #2a3550; }
.cc-bar i:first-child { background: #f87171; }
.cc-bar i:nth-child(2) { background: #fbbf24; }
.cc-bar i:nth-child(3) { background: #4ade80; }
.cc-bar span { margin-left: auto; font-family: var(--mono); font-size: .76rem; color: var(--dim); }
.code-card pre { padding: 20px; overflow-x: auto; }
.code-card code { font-size: .84rem; line-height: 1.85; color: var(--body); }

/* wiring card — the backdrop is the app's own workspace grid, at the same 13px
   hole pitch the circuit editor snaps to, so the illustration reads as a
   screenshot of the editor rather than a diagram of it */
/* the SVG now carries the editor's own canvas — dark ground plus the 22px dot
   grid — so the card is just a frame around it */
.wire-card {
  border: 1px solid var(--line-2); border-radius: var(--r);
  background: var(--panel); padding: 14px; box-shadow: var(--shadow);
}
.wire-svg { width: 100%; height: auto; }
.wire-cap {
  display: flex; align-items: center; gap: 9px; margin-top: 12px;
  font-size: .8rem; font-weight: 700; color: var(--muted);
}
.sim-dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--green);
  animation: pulseDot 1.4s ease-in-out infinite;
}
@keyframes pulseDot { 0%,100% { opacity: 1; } 50% { opacity: .25; } }
/* the LED the circuit is actually driving — the app gives a lit LED a coloured
   halo and a drop-shadow, so this does the same */
.led-bulb { animation: ledFlick 1.8s ease-in-out infinite; }
@keyframes ledFlick {
  0%, 100% { filter: drop-shadow(0 0 8px rgba(239,68,68,.95)); }
  50%      { filter: drop-shadow(0 0 3px rgba(239,68,68,.4)); }
}
.led-halo { animation: ledHalo 1.8s ease-in-out infinite; }
@keyframes ledHalo {
  0%, 100% { opacity: .38; }
  50%      { opacity: .14; }
}

/* current-flow animation, straight from the app's circuit simulator */
@keyframes rc-flow { to { stroke-dashoffset: -32; } }
.rc-flow {
  stroke-dasharray: 7 9; stroke-linecap: round;
  animation: rc-flow .55s linear infinite;
}
.rc-flow-rev { animation-direction: reverse; }

/* the stylised diagram runs thicker strokes, so the dashes need to scale with
   them or the flow reads as a dotted line rather than moving current */
.wire-svg .rc-flow { stroke-dasharray: 13 15; animation-duration: .8s; }

/* shared-variable card */
.sync-card {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 14px;
  border: 1px solid var(--line-2); border-radius: var(--r); background: var(--panel);
  padding: 28px 20px; box-shadow: var(--shadow);
}
.sync-side {
  text-align: center; padding: 20px 12px; border-radius: var(--r-sm);
  background: var(--panel-2); border: 1px solid var(--line-2);
}
.sync-icon { font-size: 2rem; line-height: 1; color: var(--blue-lite); display: grid; place-items: center; }
.sync-side:last-child .sync-icon { color: var(--teal); }
.sync-name { font-weight: 800; color: var(--ink); margin-top: 8px; font-size: .95rem; }
.sync-val { margin-top: 10px; font-family: var(--mono); font-size: .84rem; color: var(--muted); }
.sync-val b { color: var(--teal); font-size: 1.05em; }
.sync-link { display: grid; justify-items: center; gap: 5px; }
.sync-link span {
  font-size: .68rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
  color: var(--teal);
}

/* Feature grid as a bento rather than six identical cards: the first card
   spans two columns and the last spans two, so the eye gets a size hierarchy
   instead of a uniform 3×2 wall. Six equal boxes is a named tell. */
.fgrid {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 18px;
}
.fcard { grid-column: span 2; }
.fcard:nth-child(1) { grid-column: span 3; }
.fcard:nth-child(2) { grid-column: span 3; }
.fcard:nth-child(6) { grid-column: span 6; }
/* the two wide ones get room to breathe and a larger lead line */
.fcard:nth-child(1) h4, .fcard:nth-child(2) h4 { font-size: 1.18rem; }
.fcard:nth-child(6) { display: grid; grid-template-columns: auto 1fr; gap: 0 20px; align-items: center; }
.fcard:nth-child(6) .fic { margin-bottom: 0; grid-row: span 2; }
.fcard:nth-child(6) p { margin-top: 4px; }
.fcard {
  position: relative; padding: 28px 24px; border-radius: var(--r);
  background: var(--panel); border: 1px solid var(--line-2); overflow: hidden;
  transition: transform .3s cubic-bezier(.34,1.18,.64,1), border-color .3s ease, box-shadow .3s ease;
}
.fcard::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--accent); opacity: 0; transition: opacity .3s ease;
}
.fcard:hover {
  transform: translateY(-5px); border-color: color-mix(in srgb, var(--accent) 45%, var(--line-2));
  box-shadow: var(--shadow);
}
.fcard:hover::before { opacity: 1; }
.fic {
  width: 46px; height: 46px; display: grid; place-items: center; border-radius: 13px;
  background: color-mix(in srgb, var(--accent) 13%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent);
  font-size: 1.6rem; margin-bottom: 16px;
  color: var(--accent);            /* both duotone layers follow this */
  transition: transform .3s cubic-bezier(.34,1.4,.64,1);
}
.fcard:hover .fic { transform: rotate(-7deg) scale(1.08); }
.fcard h4 { margin-bottom: 9px; }
.fcard p { font-size: .91rem; color: var(--muted); }

/* ══ SKILL TREE ═════════════════════════════════════════════════════════
   The app's path graph. Drawn as one SVG rather than positioned divs so it
   scales with the panel without any layout maths, and stays crisp doing it. */
.tree {
  border: 1px solid var(--line-2); border-radius: var(--r);
  background: var(--bg-2); padding: 10px 12px 4px; box-shadow: var(--shadow);
}
.tree-svg { width: 100%; height: auto; display: block; }

.tree-lvl {
  font-family: var(--font); font-size: 12px; font-weight: 700;
  letter-spacing: .14em; fill: #55628a;
}
.tree-title {
  font-family: var(--font); font-size: 13.5px; font-weight: 700; fill: var(--ink);
  dominant-baseline: middle;
}
.tree-glyph { font-size: 11px; font-weight: 700; text-anchor: middle; dominant-baseline: central; }
.tree-tag   { font-family: var(--font); font-size: 11px; font-weight: 800;
              text-anchor: middle; dominant-baseline: central; }

/* the edge into a ready-to-start project carries a travelling light, exactly
   as the app marks the one you can play next */
@keyframes rc-edge-pulse { to { stroke-dashoffset: -40; } }
.rc-edge-pulse {
  stroke-dasharray: 10 30; stroke-linecap: round;
  animation: rc-edge-pulse 1.1s linear infinite;
}

.tree-key {
  display: flex; gap: 22px; flex-wrap: wrap; justify-content: center;
  list-style: none; padding: 10px 0 8px; font-size: .82rem; color: var(--muted);
}
.tree-key li { display: flex; align-items: center; gap: 8px; }
.tree-key i  { width: 10px; height: 10px; border-radius: 3px; }

/* ══ CLASSES ════════════════════════════════════════════════════════════ */
/* two offers — come to us, or take the software — so two columns */
.cgrid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; align-items: stretch;
         max-width: 900px; margin-inline: auto; }
.clist b { color: var(--ink); font-weight: 700; }
.ccard {
  position: relative; display: flex; flex-direction: column; gap: 20px;
  padding: 30px 26px; border-radius: var(--r-lg);
  background: var(--panel); border: 1px solid var(--line-2);
  transition: transform .3s cubic-bezier(.34,1.18,.64,1), border-color .3s ease, box-shadow .3s ease;
}
.ccard:hover { transform: translateY(-6px); border-color: var(--line-3); box-shadow: var(--shadow); }
.ccard-feature {
  border-color: rgba(77,142,248,.45);
  background: linear-gradient(165deg, rgba(77,142,248,.09), var(--panel) 55%);
  box-shadow: 0 0 0 1px rgba(77,142,248,.16), var(--shadow);
}
.ccard-feature:hover { border-color: var(--blue); }
.cflag {
  position: absolute; top: -12px; left: 50%; transform: translateX(-50%);
  padding: 5px 15px; border-radius: 999px; white-space: nowrap;
  background: linear-gradient(135deg, var(--blue), var(--purple)); color: #fff;
  font-size: .72rem; font-weight: 800; letter-spacing: .04em;
  box-shadow: 0 5px 16px rgba(77,142,248,.42);
}
.cage {
  display: inline-block; margin-bottom: 12px; padding: 5px 13px; border-radius: 999px;
  background: var(--raise); border: 1px solid var(--line-2);
  font-size: .76rem; font-weight: 800; color: var(--teal); letter-spacing: .02em;
}
.cblurb { margin-top: 12px; font-size: .93rem; color: var(--muted); }

.clist { list-style: none; display: grid; gap: 10px; padding-top: 20px; border-top: 1px solid var(--line); }
.clist li { position: relative; padding-left: 24px; font-size: .89rem; }
.clist li::before {
  content: ''; position: absolute; left: 2px; top: .55em; width: 7px; height: 7px;
  border-radius: 2px; background: var(--blue); transform: rotate(45deg);
}

.cfoot { margin-top: auto; display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.cprice { font-weight: 900; font-size: 1.5rem; color: var(--ink); }
.cprice em { font-style: normal; font-size: .78rem; font-weight: 600; color: var(--dim); margin-left: 3px; }

/* two offerings, so two columns — a 3-column grid would leave a hole */
.extras { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-top: 26px; }
.extra { padding: 24px; border-radius: var(--r); background: var(--panel-2); border: 1px solid var(--line); }
.extra h4 {
  margin-bottom: 8px; font-size: 1rem;
  display: flex; align-items: center; gap: 10px;
}
.extra h4 .ic { font-size: 1.4rem; color: var(--teal); }
.extra p { font-size: .89rem; color: var(--muted); }

.trial {
  display: flex; align-items: center; gap: 28px; flex-wrap: wrap;
  margin-top: 40px; padding: 32px 36px; border-radius: var(--r-lg);
  background: linear-gradient(120deg, rgba(77,142,248,.13), rgba(56,217,196,.09));
  border: 1px solid var(--line-3);
}
.trial-mascot { width: 78px; flex: none; }
.trial-copy { flex: 1 1 300px; }
.trial-copy h3 { margin-bottom: 6px; }
.trial-copy p { color: var(--muted); font-size: .95rem; }

/* ══ CONTACT ════════════════════════════════════════════════════════════ */
.contact-grid { display: grid; grid-template-columns: .85fr 1.15fr; gap: 32px; align-items: start; }

.contact-cards { display: grid; gap: 12px; }
.con {
  display: flex; align-items: center; gap: 15px; padding: 17px 19px;
  border-radius: var(--r); background: var(--panel); border: 1px solid var(--line-2);
  transition: transform .22s ease, border-color .22s ease, background .22s ease;
}
a.con:hover { transform: translateX(4px); border-color: var(--blue); background: rgba(77,142,248,.07); }
.con-ic {
  width: 42px; height: 42px; flex: none; display: grid; place-items: center;
  border-radius: 12px; background: var(--raise); border: 1px solid var(--line-2);
  font-size: 1.35rem; color: var(--teal);
}
.con-l { display: block; font-size: .74rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--dim); }
.con-v { display: block; color: var(--ink); font-size: .97rem; margin-top: 2px; }

.socials { display: flex; gap: 10px; margin-top: 6px; flex-wrap: wrap; }
.socials a {
  padding: 9px 17px; border-radius: 999px; background: var(--panel-2);
  border: 1px solid var(--line-2); font-size: .85rem; font-weight: 700; color: var(--muted);
  transition: color .2s ease, border-color .2s ease, transform .2s ease;
}
.socials a:hover { color: var(--ink); border-color: var(--teal); transform: translateY(-2px); }

.portal-note { margin-top: 12px; font-size: .87rem; color: var(--dim); }
.portal-note a { color: var(--blue-lite); font-weight: 700; }
.portal-note a:hover { text-decoration: underline; }

.contact-form {
  padding: 30px; border-radius: var(--r-lg); background: var(--panel);
  border: 1px solid var(--line-2); display: grid; gap: 16px; box-shadow: var(--shadow);
}

/* The contact panel carries more than any other — cards, a full form AND the
   footer — so it runs on tighter spacing to stay within one screen. */
/* this panel runs closest to the edge of its screen, so it also takes a
   shorter bottom gutter than the rest of the deck */
#contact.panel         { padding-bottom: 32px; }
#contact .sec-head     { margin-bottom: clamp(12px, 1.7vh, 20px); }
#contact .contact-form { padding: 20px; gap: 11px; }
#contact .contact-form textarea { min-height: 84px; }
#contact .con          { padding: 12px 16px; }
#contact .con-ic       { width: 38px; height: 38px; font-size: 1.2rem; }
#contact .footer       { margin-top: clamp(10px, 1.5vh, 18px); padding-top: 13px; }
.contact-form .row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.contact-form label { display: grid; gap: 7px; }
.contact-form label > span { font-size: .82rem; font-weight: 700; color: var(--muted); }
.contact-form input, .contact-form select, .contact-form textarea {
  padding: 12px 15px; border-radius: var(--r-sm); background: var(--bg-2);
  border: 1px solid var(--line-2); color: var(--ink); font-size: .93rem;
  transition: border-color .2s ease, box-shadow .2s ease;
  width: 100%;
}
.contact-form textarea { resize: vertical; min-height: 100px; }
.contact-form input::placeholder, .contact-form textarea::placeholder { color: #47557a; }
.contact-form input:focus, .contact-form select:focus, .contact-form textarea:focus {
  outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px rgba(77,142,248,.16);
}
.contact-form input:user-invalid { border-color: var(--red); }
.form-note { font-size: .85rem; min-height: 1.2em; text-align: center; }
.form-note.ok  { color: var(--green); }
.form-note.err { color: #f87171; }

/* ── footer ─────────────────────────────────────────────────────────────────
   A single compact row pinned to the bottom of the contact panel — it has to
   share one screen with the contact cards and the form. */
/* Centred and full-width. It was a space-between row, which on this panel
   pushed the brand hard against the left edge and left the middle empty; and
   because the panel snaps, anything sitting low in it is awkward to reach. */
.footer {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  column-gap: 24px; row-gap: 8px;
  margin-top: clamp(14px, 2vh, 26px); padding-top: 15px;
  border-top: 1px solid var(--line); text-align: center;
}
.foot-legal { flex-basis: 100%; }
.footer .brand-name { font-size: 1rem; justify-content: center; }
.foot-links { display: flex; gap: 20px; flex-wrap: wrap; justify-content: center; }
.foot-links a { font-size: .88rem; color: var(--muted); transition: color .2s ease; }
.foot-links a:hover { color: var(--ink); }
.foot-copy { font-size: .82rem; color: var(--dim); }
/* non-affiliation notice — full width beneath the footer row, deliberately
   quiet but present on every page view */
.foot-legal {
  font-size: .68rem; line-height: 1.5; color: #4a5878;
  max-width: 86ch; margin-inline: auto;
}

/* ══ PER-PANEL MASCOT ═══════════════════════════════════════════════════
   Every panel gets the character in a corner, each in a different mood, so
   he's doing something new as you move through the deck rather than repeating
   one pose. Positioned clear of the content column and hidden once the layout
   stacks, where there's no spare margin for him. */
/* Content sits above him, so at this size he tucks BEHIND the cards where the
   gutter runs out instead of covering them. At 1440 the gutter beside the
   1180px column is only ~130px, and he's wider than that — peeking out from
   behind reads as deliberate, sitting on top of the copy does not. */
.panel > .wrap { position: relative; z-index: 1; }

.panel-bot {
  position: absolute; bottom: 3vh; width: 168px; z-index: 0;
  pointer-events: none; opacity: 0;
  transform: translateY(28px);
  transition: opacity .6s ease, transform .7s cubic-bezier(.22,.9,.3,1);
  filter: drop-shadow(0 14px 30px rgba(0,0,0,.5));
}
.panel-bot svg { width: 100%; height: auto; }
.panel.active .panel-bot { opacity: 1; transform: none; transition-delay: .35s; }
.pb-br { right: 2.5vw; }
.pb-bl { left: 2.5vw; }
/* vary the size a little so he isn't pasted at identical scale on every screen */
.panel:nth-of-type(even) .panel-bot { width: 146px; bottom: 5vh; }

/* he needs real margin beside the content column — below this the layout has
   none, so he'd sit on top of the copy */
@media (max-width: 1400px) { .panel-bot { width: 128px; } }
@media (max-width: 1400px) { .panel:nth-of-type(even) .panel-bot { width: 112px; } }
@media (max-width: 1180px) { .panel-bot { display: none; } }

/* ══ MASCOT ANIMATION ═══════════════════════════════════════════════════
   Lifted from robocode/src/renderer/index.css, unchanged in behaviour.
   THREE RULES:
   1. POSE = a static transform on the OUTER limb group, so mood changes glide.
   2. MOTION = a small looping animation on the INNER group, centred on zero,
      so it can start/stop mid-flight without a jump.
   3. Lengths are viewBox user units (~508 tall rendered at ~200px), so a
      motion needs roughly 5x a pixel value to read.                          */
.mascot { overflow: visible; }
.mascot .mascot-body, .mascot .mascot-eyes, .mascot .mascot-shadow,
.mascot .mascot-eye, .mascot .mascot-mouth, .mascot .mascot-sparkles circle {
  transform-box: fill-box; transform-origin: center;
}
.mascot .mascot-head  { transform-box: view-box; transform-origin: 340px 302px; }
.mascot .mascot-arm-l, .mascot .mascot-arm-l .mascot-arm-jab { transform-box: view-box; transform-origin: 203px 360px; }
.mascot .mascot-arm-r, .mascot .mascot-arm-r .mascot-arm-jab { transform-box: view-box; transform-origin: 477px 360px; }
.mascot .mascot-leg-l, .mascot .mascot-leg-l .mascot-leg-sway { transform-box: view-box; transform-origin: 303px 478px; }
.mascot .mascot-leg-r, .mascot .mascot-leg-r .mascot-leg-sway { transform-box: view-box; transform-origin: 377px 478px; }

.mascot .mascot-arm, .mascot .mascot-leg, .mascot .mascot-head,
.mascot .mascot-mouth, .mascot .mascot-eyes {
  transition: transform 300ms cubic-bezier(0.34, 1.18, 0.64, 1);
}

@keyframes mbFloat   { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-13px); } }
@keyframes mbShadow  { 0%, 100% { transform: scaleX(1); opacity: .3; } 50% { transform: scaleX(.86); opacity: .18; } }
@keyframes mbHead    { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-7px) rotate(-1.6deg); } }
@keyframes mbNod     { 0%, 100% { transform: rotate(0deg) translateY(0); } 50% { transform: rotate(2deg) translateY(-4px); } }
@keyframes mbBlink   { 0%, 92%, 100% { transform: scaleY(1); } 96% { transform: scaleY(.1); } }
@keyframes mbBulb    { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }
@keyframes mbTalk    { 0%, 100% { transform: scaleY(1); } 25% { transform: scaleY(3); } 60% { transform: scaleY(1.8); } }
@keyframes mbHop     { 0%, 100% { transform: translateY(0); } 30% { transform: translateY(-42px); } 55% { transform: translateY(0); } 70% { transform: translateY(-18px); } }
@keyframes mbSparkle { 0%, 100% { opacity: 0; transform: scale(.4); } 50% { opacity: 1; transform: scale(1.15); } }
@keyframes mbLean    { 0%, 100% { transform: rotate(-3deg); } 50% { transform: rotate(-6deg); } }

@keyframes mbSwing2  { 0%, 100% { transform: rotate(2deg); }  50% { transform: rotate(-2deg); } }
@keyframes mbSwing3  { 0%, 100% { transform: rotate(3deg); }  50% { transform: rotate(-3deg); } }
@keyframes mbSwing8  { 0%, 100% { transform: rotate(8deg); }  50% { transform: rotate(-8deg); } }
@keyframes mbSwing11 { 0%, 100% { transform: rotate(11deg); } 50% { transform: rotate(-11deg); } }
@keyframes mbJabR    { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(-8deg); } }
@keyframes mbJabL    { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(8deg); } }

/* baseline (idle) — alive but calm; different periods so it never reads rigid */
.mascot .mascot-body   { animation: mbFloat 3.2s ease-in-out infinite; }
.mascot .mascot-head   { animation: mbHead 4.1s ease-in-out infinite; }
.mascot .mascot-shadow { animation: mbShadow 3.2s ease-in-out infinite; }
.mascot .mascot-eye    { animation: mbBlink 5.5s ease-in-out infinite; }
.mascot .mascot-bulb,
.mascot .mascot-core   { animation: mbBulb 2.4s ease-in-out infinite; }
.mascot .mascot-arm-l .mascot-arm-jab  { animation: mbSwing2 4.4s ease-in-out infinite; }
.mascot .mascot-arm-r .mascot-arm-jab  { animation: mbSwing2 4.4s ease-in-out infinite .6s; }
.mascot .mascot-leg-l .mascot-leg-sway { animation: mbSwing2 3.7s ease-in-out infinite; }
.mascot .mascot-leg-r .mascot-leg-sway { animation: mbSwing2 3.7s ease-in-out infinite .4s; }
.mascot .mascot-sparkles   { opacity: 0; }
.mascot .mascot-eyes-happy { display: none; }

/* talking */
.mascot-talk .mascot-mouth { animation: mbTalk .32s ease-in-out infinite; opacity: 1; }
.mascot-talk .mascot-head  { animation: mbHead 4.1s ease-in-out infinite, mbNod .95s ease-in-out infinite; }

/* thinking */
.mascot-think .mascot-arm-r { transform: rotate(136deg); }
.mascot-think .mascot-arm-r .mascot-arm-jab { animation: mbSwing3 2.6s ease-in-out infinite; }
.mascot-think .mascot-head  { animation: mbHead 4.1s ease-in-out infinite, mbNod 5s ease-in-out infinite; }

/* pointing */
.mascot-point .mascot-body { animation: mbFloat 3.2s ease-in-out infinite, mbLean 1.1s ease-in-out infinite; }
.mascot-point .mascot-head { animation: none; transform: rotate(var(--head-turn, 0deg)); }
.mascot-point .mascot-eyes { transform: translateX(var(--eye-shift, 8px)); }
.mascot-point-r .mascot-arm-r { transform: rotate(var(--aim, -74deg)); }
.mascot-point-l .mascot-arm-l { transform: rotate(var(--aim, 74deg)); }
.mascot-point-r .mascot-arm-r .mascot-arm-jab { animation: mbJabR 1.1s ease-in-out infinite; }
.mascot-point-l .mascot-arm-l .mascot-arm-jab { animation: mbJabL 1.1s ease-in-out infinite; }

/* cheering */
.mascot-cheer .mascot-body { animation: mbHop .85s ease-in-out infinite; }
.mascot-cheer .mascot-head { animation: mbHead 1.2s ease-in-out infinite; }
.mascot-cheer .mascot-arm-l { transform: rotate(129deg); }
.mascot-cheer .mascot-arm-r { transform: rotate(-129deg); }
.mascot-cheer .mascot-arm-l .mascot-arm-jab,
.mascot-cheer .mascot-arm-r .mascot-arm-jab { animation: mbSwing11 .85s ease-in-out infinite; }
.mascot-cheer .mascot-leg-l { transform: rotate(9deg); }
.mascot-cheer .mascot-leg-r { transform: rotate(-9deg); }
.mascot-cheer .mascot-leg-l .mascot-leg-sway,
.mascot-cheer .mascot-leg-r .mascot-leg-sway { animation: mbSwing8 .85s ease-in-out infinite; }
.mascot-cheer .mascot-mouth { transform: scaleY(2.4); opacity: 1; }
.mascot-cheer .mascot-eyes { display: none; }
.mascot-cheer .mascot-eyes-happy { display: block; }
.mascot-cheer .mascot-sparkles { opacity: 1; }
.mascot-cheer .mascot-sparkles circle { animation: mbSparkle .9s ease-in-out infinite; }
.mascot-cheer .mascot-sparkles circle:nth-child(2) { animation-delay: .25s; }
.mascot-cheer .mascot-sparkles circle:nth-child(3) { animation-delay: .5s; }

/* eye expressions — each keeps a blink baked in so it never goes glassy */
@keyframes mbEyeTalk  { 0%, 88%, 100% { transform: scaleY(1); } 40% { transform: scaleY(1.1); } 94% { transform: scaleY(.08); } }
@keyframes mbEyeThink { 0%, 92%, 100% { transform: scaleY(.68); } 96% { transform: scaleY(.08); } }
@keyframes mbEyeFocus { 0%, 92%, 100% { transform: scale(1.08, .78); } 96% { transform: scale(1.08, .08); } }
@keyframes mbGlance   { 0%, 100% { transform: translate(0,0); } 50% { transform: translate(10px, -9px); } }

.mascot-talk  .mascot-eye  { animation: mbEyeTalk 4.6s ease-in-out infinite; }
.mascot-think .mascot-eye  { animation: mbEyeThink 5.5s ease-in-out infinite; }
.mascot-think .mascot-eyes { animation: mbGlance 2.6s ease-in-out infinite; }
.mascot-point .mascot-eye  { animation: mbEyeFocus 5.5s ease-in-out infinite; }

/* ── scroll reveal ──────────────────────────────────────────────────────────
   In panel mode this runs BOTH ways: content rises in as its panel becomes
   active and sinks back out as you leave, so each screen feels composed
   rather than merely uncovered. Outside panel mode (small screens, reduced
   motion) the JS observer adds .in once and it stays. */
.reveal {
  opacity: 0; transform: translateY(28px);
  transition: opacity .6s ease, transform .72s cubic-bezier(.22,.9,.3,1);
}
.reveal.in,
.panel.active .reveal { opacity: 1; transform: none; }

/* leaving upward: sink the other way, so the motion tracks the scroll */
.panel.above .reveal { transform: translateY(-28px); }

/* stagger whatever sits in a row, so a grid cascades instead of popping */
.panel.active .reveal:nth-child(1) { transition-delay: .04s; }
.panel.active .reveal:nth-child(2) { transition-delay: .10s; }
.panel.active .reveal:nth-child(3) { transition-delay: .16s; }
.panel.active .reveal:nth-child(4) { transition-delay: .22s; }
.panel.active .reveal:nth-child(5) { transition-delay: .28s; }
.panel.active .reveal:nth-child(6) { transition-delay: .34s; }

/* ── panel dots ─────────────────────────────────────────────────────────── */
.dots-nav {
  position: fixed; right: 20px; top: 50%; transform: translateY(-50%);
  z-index: 95; display: grid; gap: 13px;
}
.dots-nav button {
  width: 9px; height: 9px; padding: 0; border-radius: 50%; cursor: pointer;
  background: transparent; border: 1.5px solid var(--line-3);
  transition: background .25s ease, border-color .25s ease, transform .25s cubic-bezier(.34,1.4,.64,1);
}
.dots-nav button:hover { border-color: var(--blue); transform: scale(1.25); }
.dots-nav button.on {
  background: var(--teal); border-color: var(--teal); transform: scale(1.4);
  box-shadow: 0 0 12px rgba(56,217,196,.55);
}

/* placeholder copy the school still needs to fill in — a faint dashed underline
   so it's obvious at a glance what is real and what is a stand-in. */
[data-placeholder] { border-bottom: 1px dashed rgba(255,209,102,.45); }

/* ══ RESPONSIVE ═════════════════════════════════════════════════════════ */

/* Snapping only makes sense while a panel genuinely fits the screen. Once the
   layouts stack (narrow) or the window is short, a panel outgrows the viewport
   and mandatory snapping would fight the reader — so it reverts to an ordinary
   scrolling page, keeping every animation. */
@media (max-width: 1000px), (max-height: 700px) {
  html { scroll-snap-type: none; scroll-padding-top: calc(var(--nav-h) + 16px); }
  .panel {
    min-height: 0; padding: clamp(64px, 9vw, 100px) 0;
    scroll-snap-align: none; scroll-snap-stop: normal;
  }
  .panel .sec-head   { margin-bottom: clamp(40px, 6vw, 64px); }
  .panel .app-mock   { margin-bottom: 56px; }
  .panel .feat-split { margin-bottom: clamp(56px, 8vw, 96px); }
  .dots-nav { display: none; }
  .panel.above .reveal { transform: none; }   /* one-shot reveal, no sink-out */
}

@media (max-width: 1000px) {
  .hero-in { grid-template-columns: 1fr; text-align: center; gap: 24px; }
  .hero-copy { display: flex; flex-direction: column; align-items: center; }
  .lede { margin-inline: auto; }
  .hero-cta, .hero-stats { justify-content: center; }
  .hero-art { min-height: 340px; order: -1; }
  /* the bento's 3/2/6-column spans would overflow a 2-column grid — drop back
     to equal cards once there isn't width for a size hierarchy */
  .fgrid  { grid-template-columns: repeat(2, 1fr); }
  .fcard, .fcard:nth-child(1), .fcard:nth-child(2), .fcard:nth-child(6) { grid-column: auto; }
  .fcard:nth-child(6) { display: block; }
  .fcard:nth-child(6) .fic { margin-bottom: 16px; }
  .sec-head { text-align: center; margin-inline: auto; }
  .sec-head p { margin-inline: auto; }
  .kicker { justify-content: center; }
  .kicker::after { display: none; }
  .cgrid  { grid-template-columns: 1fr; max-width: 460px; margin-inline: auto; }
  .extras { grid-template-columns: 1fr; }
  .contact-grid { grid-template-columns: 1fr; }
  .feat-split { grid-template-columns: 1fr; }
  .feat-flip .feat-text { order: 0; }
}

@media (max-width: 780px) {
  .nav-links {
    position: fixed; inset: var(--nav-h) 0 auto 0; flex-direction: column; gap: 0;
    background: rgba(8,12,20,.97); backdrop-filter: blur(16px);
    border-bottom: 1px solid var(--line); padding: 8px 20px 20px;
    transform: translateY(-120%); transition: transform .32s cubic-bezier(.22,.9,.3,1);
  }
  .nav-links.open { transform: none; }
  .nav-links a { padding: 14px 0; border-bottom: 1px solid var(--line); font-size: 1rem; }
  .nav-links a::after { display: none; }
  .nav-cta { display: none; }
  .nav-toggle { display: flex; }

  /* stack the two panes; the arrow becomes a full-width divider pointing down
     (rotate the glyph, not the container — rotating the box left a pale band
     lying across the code pane) */
  .mock-body { grid-template-columns: 1fr; min-height: 0; }
  .mock-arrow { padding: 8px 0; border-top: 1px solid var(--line-2); }
  .mock-arrow svg { transform: rotate(90deg); }
  .mock-code { border-left: none; border-top: 1px solid var(--line-2); }
  .mock-blocks { overflow-x: auto; }
  .blk { font-size: .78rem; padding: 9px 14px; }
  .mock-file { display: none; }        /* no room beside the tabs */
  /* give the chips the outer edges and pull the mascot in, so they stop
     landing on top of it once the hero stacks */
  .mascot-stage .mascot { width: 148px; height: auto; }
  .hero-art { min-height: 280px; }
  .chip-1, .chip-3 { left: 0; }
  .chip-2, .chip-4 { right: 0; }
  .chip { font-size: .7rem; padding: 7px 11px; }
}

@media (max-width: 560px) {
  .fgrid { grid-template-columns: 1fr; }
  .contact-form .row { grid-template-columns: 1fr; }
  .contact-form { padding: 22px; }
  .hero-stats { gap: 22px; }
  .trial { flex-direction: column; text-align: center; padding: 26px; }
  .trial .btn { width: 100%; }
}

/* ── reduced motion: keep the character, drop the motion ────────────────── */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; scroll-snap-type: none; }
  .panel { min-height: 0; padding: 80px 0; scroll-snap-align: none; }
  .dots-nav { display: none; }
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .mascot * { animation: none !important; transition: none !important; }
  .reveal { opacity: 1; transform: none; }
}
