/* DYSHEA v3 — 3D academy landing.
 *
 * Structure borrowed from the reference: four full-bleed sections, liquid
 * glass chrome, Anton headings with a cursive accent laid over them.
 * Everything else is DYSHEA: our palette, our mascot, our subject.
 *
 * The reference used recorded video for its backgrounds. We render the real
 * thing instead — one WebGL canvas per section, with the actual Dishi model in
 * the hero. Lighter than five MP4s and it reacts to the cursor.
 */

:root {
  /* Brand, straight from css/styles.css so the two pages cannot drift. */
  --bg-deep: #0F0824;
  --primary: #6D28D9;
  --dark-purple: #2E1065;
  --neon: #A855F7;
  --lavender: #E9D5FF;
  --gold: #FBBF24;
  --mint: #34D399;

  /* The reference put cream on near-black; ours is lavender on deep purple. */
  --ink: var(--lavender);
  --ink-dim: rgba(233, 213, 255, 0.62);

  --shell: 1831px;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);

  /* The desktop social rail is taken out of flow into the top-right corner, so
     the corner belongs to it. The language switcher has to be told how much of
     the corner is spoken for — as these numbers, not as a magic margin that
     stops matching the moment an icon changes size. */
  --rail-inset: 64px;
  --rail-size: 56px;
}

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

html { scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

body {
  margin: 0;
  background: var(--bg-deep);
  color: var(--ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3, .font-anton, nav a {
  font-family: 'Anton', 'Oswald', Impact, sans-serif;
  font-weight: 400;
  text-transform: uppercase;
  margin: 0;
}

/* The cursive accent. In the reference it is acid green on black; here it is
   our neon purple, which needs a glow to hold its own against the 3D behind
   it — exclusion blending alone left it muddy over a purple scene. */
.script {
  font-family: 'Condiment', cursive;
  text-transform: none;
  color: var(--neon);
  text-shadow: 0 0 24px rgba(168, 92, 247, 0.55);
  pointer-events: none;
}

a { color: inherit; text-decoration: none; }

/* ---------------------------------------------------------- liquid glass */

.glass {
  background: rgba(255, 255, 255, 0.012);
  background-blend-mode: luminosity;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: none;
  box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.1);
  position: relative;
  overflow: hidden;
}

/* The lit rim. A masked gradient border reads as a bevel catching light,
   which a plain 1px border never does. */
.glass::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.4px;
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.45) 0%,
    rgba(233, 213, 255, 0.16) 20%,
    rgba(255, 255, 255, 0) 40%,
    rgba(255, 255, 255, 0) 60%,
    rgba(233, 213, 255, 0.16) 80%,
    rgba(255, 255, 255, 0.45) 100%);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask-composite: exclude;
  pointer-events: none;
}

/* ------------------------------------------------------------- structure */

.section {
  position: relative;
  isolation: isolate;
}

.section > canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: -1;
}

.shell {
  max-width: var(--shell);
  margin: 0 auto;
  padding: 0 20px;
}

@media (min-width: 768px) { .shell { padding: 0 40px; } }
@media (min-width: 1280px) { .shell { padding: 0 64px; } }

/* Grain. Sits above everything, lightens only — stops the large flat purple
   fields from banding on cheap panels. */
.grain {
  position: fixed;
  inset: 0;
  z-index: 50;
  pointer-events: none;
  opacity: 0.55;
  mix-blend-mode: lighten;
  background-image: var(--grain-src);
  background-size: 220px 220px;
}

/* -------------------------------------------------------------- section 1 */

#hero {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  border-bottom-left-radius: 32px;
  border-bottom-right-radius: 32px;
  overflow: hidden;
}

/* Grid, not space-between. Once the social rail is taken out of flow only two
   children remain, and space-between throws the nav to the right edge — under
   the rail. Three columns keep the nav optically centred whether the rail is
   in the row or not. */
.topbar {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 24px;
  padding-top: 28px;
  position: relative;
}

@media (min-width: 1024px) {
  .topbar { grid-template-columns: 1fr auto 1fr; }
  .topbar > .brand { justify-self: start; }
  .topbar > nav.mainnav { grid-column: 2; justify-self: center; }
}

.brand { font-size: 16px; letter-spacing: 0.02em; }
.brand b { font-family: 'Anton', 'Oswald', Impact, sans-serif; }
.brand span { color: var(--neon); }

nav.mainnav {
  border-radius: 28px;
  padding: 22px 52px;
  display: none;
}

/* On a phone the same nav becomes a panel under the bar. One list, so the
   links can never drift apart between the two layouts. */
nav.mainnav.is-open {
  display: block;
  position: absolute;
  top: calc(100% + 12px);
  left: 0;
  right: 0;
  z-index: 40;
  padding: var(--sp-4, 16px);
  border-radius: 20px;
}

nav.mainnav.is-open ul { flex-direction: column; gap: 4px; }
nav.mainnav.is-open a { display: block; padding: 14px 12px; font-size: 15px; letter-spacing: 0.02em; }
nav.mainnav.is-open a:hover { background: rgba(255, 255, 255, 0.06); border-radius: 12px; }

.nav-toggle {
  display: grid;
  gap: 5px;
  justify-self: end;
  padding: 12px;
  border: 0;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.06);
  cursor: pointer;
}
.nav-toggle span {
  display: block;
  width: 20px;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
  transition: transform 0.2s var(--ease), opacity 0.2s var(--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); }

@media (min-width: 1024px) {
  nav.mainnav { display: block; }
  .nav-toggle { display: none; }
}

nav.mainnav ul {
  display: flex;
  gap: 34px;
  list-style: none;
  margin: 0;
  padding: 0;
}

nav.mainnav a {
  font-size: 13px;
  letter-spacing: 0.06em;
  transition: color 0.2s var(--ease);
}

nav.mainnav a:hover, nav.mainnav a:focus-visible { color: var(--neon); }

/* Two columns from 1024 up: copy on the left, the right half left empty for
   Dishi. The first pass centred him behind the heading and the type ran
   straight across his face — the character was decoration hidden by text
   instead of the subject of the shot. The 3D camera offsets to match. */
.hero-body {
  flex: 1;
  display: grid;
  grid-template-columns: 1fr;
  align-items: center;
  padding: 40px 0 90px;
}

@media (min-width: 1024px) {
  .hero-body { grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); }
}

.hero-head {
  position: relative;
  font-size: clamp(44px, 7.4vw, 124px);
  line-height: 0.94;
  letter-spacing: -0.022em;
}

.hero-head .muted {
  color: transparent;
  -webkit-text-stroke: 1.5px rgba(233, 213, 255, 0.55);
}

.hero-head .script {
  display: block;
  margin-top: 0.08em;
  font-size: clamp(26px, 3.4vw, 62px);
  transform: rotate(-1.5deg);
  -webkit-text-stroke: 0;
}

.hero-sub {
  margin-top: 30px;
  max-width: 44ch;
  font-size: 14px;
  line-height: 1.75;
  text-transform: uppercase;
  color: var(--ink-dim);
  letter-spacing: 0.04em;
}

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

/* Proof strip under the buttons — concrete numbers instead of adjectives. */
.hero-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 34px;
  margin-top: 52px;
  padding-top: 26px;
  border-top: 1px solid rgba(233, 213, 255, 0.12);
}

.hero-stats div strong {
  display: block;
  font-family: 'Anton', 'Oswald', Impact, sans-serif;
  font-size: clamp(26px, 3vw, 40px);
  line-height: 1;
  color: var(--ink);
}

.hero-stats div span {
  display: block;
  margin-top: 7px;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 15px 26px;
  border-radius: 16px;
  border: 0;
  cursor: pointer;
  font-family: 'Anton', 'Oswald', Impact, sans-serif;
  font-size: 14px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  transition: transform 0.2s var(--ease), box-shadow 0.2s var(--ease), background 0.2s var(--ease);
}

.btn-solid {
  background: linear-gradient(135deg, var(--neon), var(--primary));
  color: #fff;
  box-shadow: 0 10px 40px rgba(168, 85, 247, 0.35);
}

.btn-solid:hover { transform: translateY(-2px); box-shadow: 0 16px 52px rgba(168, 85, 247, 0.5); }

.btn-glass { color: var(--ink); border-radius: 16px; }
.btn-glass:hover { background: rgba(255, 255, 255, 0.07); }

/* Social rail — vertical on desktop, horizontal under the heading on mobile.
   Three stacked 56px buttons are ~192px tall. Left inside the topbar's flex
   row that forced the whole bar to that height and pushed it down across the
   hero copy, which is what made the icons look like they were sitting on the
   text. Taking it out of flow fixes the cause rather than nudging the gap. */
.rail {
  display: flex;
  gap: 12px;
}

.rail.desktop { display: none; flex-direction: column; }

@media (min-width: 1024px) {
  .rail.desktop {
    display: flex;
    position: absolute;
    top: 28px;
    right: var(--rail-inset);
    z-index: 4;
  }
}

/* Copy sits above the canvas; the rail lives in the right margin beside it. */
.hero-body > div { position: relative; z-index: 3; }

.rail.mobile { margin-top: 32px; justify-content: flex-start; }
@media (min-width: 1024px) { .rail.mobile { display: none; } }

.icon-btn {
  width: 56px;
  height: 56px;
  border-radius: 16px;
  display: grid;
  place-items: center;
  color: var(--ink);
  transition: background 0.2s var(--ease), transform 0.2s var(--ease);
}

.icon-btn:hover { background: rgba(255, 255, 255, 0.1); transform: translateY(-2px); }
.icon-btn svg { width: 20px; height: 20px; }

.scroll-hint {
  position: absolute;
  left: 50%;
  bottom: 26px;
  transform: translateX(-50%);
  font-size: 10px;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--ink-dim);
  animation: drift 2.6s ease-in-out infinite;
}

@keyframes drift {
  0%, 100% { transform: translate(-50%, 0); opacity: 0.5; }
  50% { transform: translate(-50%, 7px); opacity: 1; }
}

/* -------------------------------------------------------------- section 2 */

#meet { padding: 88px 0; }
@media (min-width: 768px) { #meet { padding: 128px 0; } }

.meet-top {
  display: flex;
  flex-direction: column;
  gap: 36px;
  justify-content: space-between;
}

@media (min-width: 1024px) { .meet-top { flex-direction: row; align-items: flex-end; } }

.meet-head {
  position: relative;
  font-size: clamp(32px, 5.4vw, 60px);
  line-height: 1.05;
}

.meet-head .script {
  position: absolute;
  right: -0.4em;
  bottom: -0.45em;
  font-size: clamp(36px, 6vw, 68px);
  transform: rotate(-2deg);
}

.meet-copy {
  max-width: 300px;
  font-size: 14px;
  line-height: 1.75;
  text-transform: uppercase;
  color: var(--ink-dim);
  letter-spacing: 0.03em;
}

/* Live clip stage. The reference had static NFT tiles here; this section is
   the one place the rigged character is the subject rather than the backdrop. */
.stage-row {
  margin-top: 64px;
  display: grid;
  grid-template-columns: 1fr;
  gap: 22px;
  align-items: stretch;
}

@media (min-width: 900px) {
  .stage-row { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); gap: 30px; }
}

.stage {
  position: relative;
  border-radius: 30px;
  min-height: 380px;
  overflow: hidden;
  background:
    radial-gradient(110% 90% at 50% 15%, rgba(168, 85, 247, 0.22), transparent 62%),
    linear-gradient(170deg, #1a0f3d, #0c0520);
}

@media (min-width: 900px) { .stage { min-height: 520px; } }

.stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

.stage-tag {
  position: absolute;
  left: 20px;
  bottom: 18px;
  z-index: 2;
  font-size: 10px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--ink-dim);
  padding: 7px 13px;
  border-radius: 999px;
  background: rgba(15, 8, 36, 0.62);
}

.clip-list { display: grid; gap: 10px; align-content: start; }

.clip {
  display: grid;
  grid-template-columns: 52px 1fr;
  grid-template-rows: auto auto;
  align-items: center;
  gap: 2px 16px;
  padding: 14px 18px;
  border-radius: 20px;
  cursor: pointer;
  text-align: left;
  color: var(--ink);
  transition: background 0.25s var(--ease), transform 0.25s var(--ease);
}

.clip img { grid-row: 1 / 3; width: 52px; height: auto; }
.clip b { font-family: 'Anton', 'Oswald', Impact, sans-serif; font-size: 16px; letter-spacing: 0.04em; text-transform: uppercase; }
.clip small { font-size: 12px; color: var(--ink-dim); }

.clip:hover { background: rgba(255, 255, 255, 0.06); transform: translateX(3px); }

.clip[aria-pressed="true"] {
  background: rgba(168, 85, 247, 0.14);
  box-shadow: inset 0 0 0 1.4px rgba(168, 85, 247, 0.55);
}

.clip[aria-pressed="true"] b { color: var(--neon); }

/* -------------------------------------------------------------- section 3 */

#levels { padding: 88px 0 120px; }

.levels-head {
  display: flex;
  flex-direction: column;
  gap: 30px;
  justify-content: space-between;
  margin-bottom: 52px;
}

@media (min-width: 1024px) { .levels-head { flex-direction: row; align-items: flex-end; } }

.levels-title {
  font-size: clamp(32px, 5.4vw, 60px);
  line-height: 1.05;
}

.levels-title .indent { display: block; margin-left: 48px; }
@media (min-width: 768px) { .levels-title .indent { margin-left: 96px; } }
@media (min-width: 1024px) { .levels-title .indent { margin-left: 128px; } }

/* "SEE ALL" treatment from the reference: one big word, two stacked small
   ones, a heavy accent bar underneath. */
.seeall { display: inline-block; cursor: pointer; }

.seeall-row { display: flex; align-items: center; gap: 12px; }
.seeall-big { font-size: clamp(32px, 5vw, 60px); line-height: 1; }
.seeall-stack { display: flex; flex-direction: column; line-height: 1; }
.seeall-stack span { font-size: clamp(18px, 2.6vw, 36px); }

.seeall-bar {
  margin-top: 12px;
  height: 8px;
  width: 100%;
  border-radius: 4px;
  background: var(--neon);
  transform-origin: left;
  transition: transform 0.35s var(--ease);
}

.seeall:hover .seeall-bar { transform: scaleX(1.06); }

.level-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
}

@media (min-width: 768px) { .level-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .level-grid { grid-template-columns: repeat(3, 1fr); } }

/* Level cards.
 *
 * Every card is the same object with one variable: --accent, set per level in
 * main.js. That is what makes six tiles read as one family while still being
 * told apart — the structure, spacing and type never change, only the hue of
 * the glyph, the rule and the progress bar.
 *
 * The card is a three-row grid so the footers line up across the row no matter
 * how long a subtitle runs. Ragged baselines were what made the old row look
 * untidy more than the artwork did. */
.level-card {
  --accent: var(--neon);
  display: grid;
  grid-template-rows: auto 1fr auto;
  border-radius: 28px;
  padding: 20px;
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease);
}

.level-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 24px 60px -28px color-mix(in srgb, var(--accent) 70%, transparent);
}

/* The code panel. Tinted with the level's accent so the hue carries the
   identity without colouring the whole card. */
.level-visual {
  position: relative;
  border-radius: 20px;
  padding: 18px;
  cursor: pointer;
  overflow: hidden;
  background:
    radial-gradient(120% 130% at 12% 0%,
      color-mix(in srgb, var(--accent) 26%, transparent), transparent 62%),
    linear-gradient(165deg, rgba(26, 15, 61, 0.9), rgba(13, 6, 32, 0.95));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent);
}

/* The level's syntax mark, sunk into the corner as a watermark. It says what
   the level is about before a single word is read. */
.level-glyph {
  position: absolute;
  right: 10px;
  bottom: -18px;
  font-family: 'Anton', 'Oswald', Impact, sans-serif;
  font-size: 84px;
  line-height: 1;
  letter-spacing: -0.04em;
  color: color-mix(in srgb, var(--accent) 20%, transparent);
  pointer-events: none;
  user-select: none;
  transition: transform 0.4s var(--ease);
}

.level-card:hover .level-glyph { transform: translateY(-6px) scale(1.06); }

.level-head {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}

.level-num {
  font-family: 'Anton', 'Oswald', Impact, sans-serif;
  font-size: 12px;
  letter-spacing: 0.18em;
  color: var(--accent);
}

.level-state {
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 5px 10px;
  border-radius: 999px;
  color: var(--ink-dim);
  box-shadow: inset 0 0 0 1px rgba(233, 213, 255, 0.16);
  white-space: nowrap;
}

.level-state.live {
  color: var(--mint);
  box-shadow: inset 0 0 0 1px rgba(52, 211, 153, 0.4);
}

/* Real code, so it must not be re-wrapped or squeezed — it scrolls inside its
   own box instead of forcing the card wider. */
.level-code {
  position: relative;
  z-index: 1;
  margin: 0;
  min-height: 108px;
  overflow-x: auto;
  font-size: 12px;
  line-height: 1.72;
  color: rgba(233, 213, 255, 0.9);
  scrollbar-width: none;
}

.level-code::-webkit-scrollbar { display: none; }
.level-code code { font-family: inherit; }
.level-code i { font-style: normal; }
.level-code .c-kw { color: var(--accent); }
.level-code .c-str { color: var(--gold); }
.level-code .c-com { color: rgba(233, 213, 255, 0.38); }

.level-meta { padding: 20px 4px 16px; }
.level-meta h3 { font-size: 21px; letter-spacing: 0.01em; }
.level-meta p { margin: 7px 0 0; font-size: 13px; line-height: 1.6; color: var(--ink-dim); }

.level-skills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 14px 0 0;
  padding: 0;
  list-style: none;
}

.level-skills li {
  font-size: 10px;
  letter-spacing: 0.06em;
  padding: 4px 9px;
  border-radius: 7px;
  color: var(--ink-dim);
  background: rgba(255, 255, 255, 0.045);
}

.level-foot {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 4px 2px;
  border-top: 1px solid rgba(233, 213, 255, 0.1);
}

.level-progress { flex: 1; min-width: 0; }

.level-track {
  height: 4px;
  border-radius: 2px;
  overflow: hidden;
  background: rgba(233, 213, 255, 0.12);
}

.level-track span {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
  transition: width 0.6s var(--ease);
}

.level-foot small {
  display: block;
  margin-top: 9px;
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

.enter {
  flex: none;
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  color: #12061f;
  background: var(--accent);
  transition: transform 0.25s var(--ease);
}

.enter:hover { transform: scale(1.1); }

/* Levels still being authored are dimmed — the page must not imply that
   content exists where it does not. */
.level-card.pending .level-visual { opacity: 0.72; }
.level-card.pending .enter { background: rgba(233, 213, 255, 0.16); color: var(--ink); }

/* -------------------------------------------------------------- section 4 */

#join {
  position: relative;
  padding: 120px 0 140px;
  overflow: hidden;
}

/* Dishi on the left, the type on the right. Below 1024 the character is
   dropped rather than stacked — the section is already tall on a phone and a
   second full-height image only pushes the call to action off the screen. */
.join-inner {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  gap: 32px;
}

.join-dishi { display: none; }

@media (min-width: 1024px) {
  .join-dishi {
    display: block;
    flex: 0 1 380px;
    margin-right: auto;
    align-self: center;
  }

  .join-dishi img {
    display: block;
    width: 100%;
    height: auto;
    filter: drop-shadow(0 30px 60px rgba(109, 40, 217, 0.5));
    animation: dishi-float 7s ease-in-out infinite;
  }
}

@media (min-width: 1024px) { .join-inner { padding-right: 12%; padding-left: 15%; } }

.join-head {
  position: relative;
  text-align: right;
  font-size: clamp(22px, 4.6vw, 60px);
  line-height: 1.08;
}

.join-head .lead { display: block; margin-bottom: 18px; }
@media (min-width: 768px) { .join-head .lead { margin-bottom: 44px; } }

.join-head .script {
  position: absolute;
  left: -0.2em;
  top: -1.05em;
  font-size: clamp(17px, 4.6vw, 68px);
  transform: rotate(-2deg);
}

.join-actions { display: flex; justify-content: flex-end; gap: 14px; margin-top: 44px; flex-wrap: wrap; }

/* The closing rail used to be absolutely positioned over the section, which
   put it straight on top of the footer line. It is content, not decoration —
   so it sits in the flow beside the footer and can never cover anything. */
.join-rail {
  border-radius: 20px;
  display: none;
  flex-direction: row;
}

@media (min-width: 700px) { .join-rail { display: flex; } }

.join-rail a {
  width: 78px;
  height: 62px;
  display: grid;
  place-items: center;
  color: var(--ink);
  border-right: 1px solid rgba(255, 255, 255, 0.1);
  transition: background 0.2s var(--ease);
}

.join-rail a:last-child { border-right: 0; }
.join-rail a:hover { background: rgba(255, 255, 255, 0.08); }
.join-rail svg { width: 22px; height: 22px; }

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

.foot {
  border-top: 1px solid rgba(233, 213, 255, 0.1);
  padding: 30px 0 46px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: space-between;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* ------------------------------------------------------------- reveal-in */

/* Content is visible by default and only hidden once JS has an observer ready
   to bring it back — `reveal-armed` is set on <html> by wireReveal().
   It used to be the other way round: everything started at opacity 0 and
   waited to be revealed, so anything that stopped the observer firing left the
   entire page blank except the header. A scroll animation must never be the
   thing standing between a reader and the content. */
.reveal-armed [data-reveal] {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 0.75s var(--ease), transform 0.75s var(--ease);
}

.reveal-armed [data-reveal].shown { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .reveal-armed [data-reveal] { opacity: 1; transform: none; }
}


/* ============================================ shared section furniture === */

.eyebrow {
  display: inline-block;
  font-family: 'Anton', 'Oswald', Impact, sans-serif;
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--neon);
  margin-bottom: 18px;
}

.big-head {
  font-size: clamp(30px, 4.6vw, 68px);
  line-height: 1.02;
  letter-spacing: -0.018em;
  margin-bottom: 22px;
}

.big-head .script { font-size: 1.06em; }

.lede {
  max-width: 60ch;
  margin: 0 0 16px;
  font-size: 15px;
  line-height: 1.75;
  color: var(--ink-dim);
}

.section-head { margin-bottom: 56px; max-width: 900px; }

/* ------------------------------------------------------- unit anatomy --- */

#anatomy, #grader, #after { padding: 104px 0; }

.steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 18px;
  counter-reset: step;
}

@media (min-width: 700px) { .steps { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .steps { grid-template-columns: repeat(3, 1fr); } }

.step {
  border-radius: 26px;
  padding: 28px 26px 30px;
  transition: transform 0.3s var(--ease), background 0.3s var(--ease);
}

.step:hover { transform: translateY(-4px); background: rgba(255, 255, 255, 0.05); }

.step-n {
  display: block;
  font-family: 'Anton', 'Oswald', Impact, sans-serif;
  font-size: 13px;
  letter-spacing: 0.18em;
  color: var(--neon);
  margin-bottom: 14px;
}

.step h3 { font-size: 21px; margin-bottom: 10px; }
.step p { margin: 0; font-size: 14px; line-height: 1.7; color: var(--ink-dim); }
.step em { color: var(--lavender); font-style: italic; }

/* ------------------------------------------------------------ grader --- */

.grader-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 44px;
  align-items: center;
}

@media (min-width: 1024px) {
  .grader-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); gap: 64px; }
}

.grader-grid .btn { margin-top: 12px; }

.terminal { border-radius: 24px; overflow: hidden; }

.terminal-bar {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 14px 18px;
  border-bottom: 1px solid rgba(233, 213, 255, 0.1);
}

.terminal-bar i { width: 10px; height: 10px; border-radius: 50%; background: rgba(233, 213, 255, 0.22); }
.terminal-bar i:first-child { background: rgba(248, 113, 113, 0.6); }
.terminal-bar i:nth-child(2) { background: rgba(251, 191, 36, 0.6); }
.terminal-bar span {
  margin-left: auto;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

.terminal-body {
  margin: 0;
  padding: 22px 24px;
  font-size: 13.5px;
  line-height: 1.75;
  overflow-x: auto;
  color: var(--lavender);
}

.terminal-body .k { color: #c084fc; }
.terminal-body .f { color: #7dd3fc; }
.terminal-body .s { color: var(--mint); }
.terminal-body .n { color: var(--gold); }
.terminal-body .c { color: rgba(233, 213, 255, 0.45); }

.terminal-out { padding: 4px 18px 20px; border-top: 1px solid rgba(233, 213, 255, 0.1); }

.terminal-out .case {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 12px;
  margin-top: 10px;
  border-radius: 13px;
  font-size: 13px;
}

.terminal-out .case b { font-size: 15px; }
.terminal-out .case span { margin-left: auto; font-size: 11.5px; color: var(--ink-dim); }
.terminal-out .ok { background: rgba(52, 211, 153, 0.1); box-shadow: inset 0 0 0 1px rgba(52, 211, 153, 0.28); }
.terminal-out .ok b { color: var(--mint); }
.terminal-out .bad { background: rgba(248, 113, 113, 0.1); box-shadow: inset 0 0 0 1px rgba(248, 113, 113, 0.3); }
.terminal-out .bad b { color: #f87171; }

.diff-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 12px; }
.diff-row > div { padding: 11px 13px; border-radius: 13px; background: rgba(255, 255, 255, 0.03); }
.diff-row small {
  display: block;
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-dim);
  margin-bottom: 5px;
}
.diff-row code { font-size: 13px; color: var(--mint); }
.diff-row code.wrong { color: #f87171; }

/* -------------------------------------------------------- after Python -- */

.after-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}

@media (min-width: 640px) { .after-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .after-grid { grid-template-columns: repeat(5, 1fr); } }

/* One card per AI branch. Same construction as the level cards — a per-card
   --accent, a syntax mark, a solid ground instead of a dashed outline — so the
   two card families on the page read as one system. The dashed boxes made the
   whole section look like a placeholder someone had not finished. */
.after-card {
  --accent: var(--neon);
  display: flex;
  flex-direction: column;
  border-radius: 22px;
  padding: 22px 20px 24px;
  background:
    radial-gradient(120% 130% at 12% 0%,
      color-mix(in srgb, var(--accent) 16%, transparent), transparent 62%),
    rgba(255, 255, 255, 0.022);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 20%, transparent);
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease);
}

.after-card:hover {
  transform: translateY(-4px);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--accent) 42%, transparent),
    0 22px 50px -30px color-mix(in srgb, var(--accent) 70%, transparent);
}

.after-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.after-num {
  font-family: 'Anton', 'Oswald', Impact, sans-serif;
  font-size: 12px;
  letter-spacing: 0.16em;
  color: var(--accent);
}

.after-glyph {
  font-family: 'Anton', 'Oswald', Impact, sans-serif;
  font-size: 26px;
  line-height: 1;
  color: color-mix(in srgb, var(--accent) 42%, transparent);
}

.after-card h3 {
  margin: 16px 0 0;
  font-size: 17px;
  letter-spacing: 0.01em;
}

.after-card ul {
  margin: 14px 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 9px;
}

/* A rule in the branch's own colour instead of a bullet — bullets on a dark
   panel read as noise at this size. */
.after-card li {
  position: relative;
  padding-left: 14px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-dim);
}

.after-card li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 7px;
  height: 1.5px;
  border-radius: 1px;
  background: var(--accent);
  opacity: 0.75;
}

/* Closing rail now sits in the flow above the footer line. Centred so it reads
   as a deliberate end-cap rather than a stray element pinned to a corner. */
.join-rail { margin: 56px auto 0; width: max-content; }

#join .foot { margin-top: 40px; }

/* ================================================= Dishi as a 2D sprite === */

.dishi {
  position: relative;
  display: grid;
  place-items: center;
  pointer-events: none;
}

/* One transform per layer. The figure turns with the cursor, the body floats;
   putting both on one element makes them overwrite each other. */
.dishi-figure {
  position: relative;
  width: 100%;
  max-width: 560px;
  transform-origin: 50% 78%;
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform;
}

.dishi-body {
  position: relative;
  animation: dishi-float 6s ease-in-out infinite;
}

.dishi-art {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 34px 60px rgba(109, 40, 217, 0.45));
  transition: opacity 0.22s ease;
}

.dishi-art.swapping { opacity: 0; }

/* The irises are cut from the render and rest on their own pixels, so
   they need no styling beyond position and size — the image is the art. */
.dishi-iris {
  position: absolute;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background-size: 100% 100%;
  background-repeat: no-repeat;
  transition: transform 0.12s linear;
  pointer-events: none;
}

@keyframes dishi-float {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-2.2%) rotate(-0.6deg); }
}

#hero .dishi { align-self: center; }
#hero .dishi-figure { max-width: 620px; }

@media (max-width: 1023px) {
  #hero .dishi { display: none; }
}

.dishi-stage .dishi-figure { max-width: 420px; }
.dishi-stage .dishi-body { animation-duration: 7s; }

/* Soft brand glow behind the hero, replacing what the WebGL scene used to
   contribute to that corner. */
.hero-glow {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(58% 62% at 74% 46%, rgba(109, 40, 217, 0.42), transparent 68%),
    radial-gradient(42% 48% at 22% 22%, rgba(168, 85, 247, 0.16), transparent 70%);
}

@media (prefers-reduced-motion: reduce) {
  .dishi-body { animation: none; }
  .dishi-figure, .dishi-iris { transition: none; }
}

/* ------------------------------------------------------- language switcher */

/* The academy's switcher component, restyled for this page's chrome. It sits
   in the topbar's third grid column, and from 1024 up it needs to clear the
   social rail — that rail is absolutely positioned 64px from the edge and
   56px wide, so the switcher is pushed in past both. */
.topbar-lang { justify-self: end; display: flex; align-items: center; }

/* Clear the whole rail, not most of it. The old 76px cleared the rail's offset
   but not the icons themselves, so the top icon sat on top of the ES button. */
@media (min-width: 1024px) {
  .topbar-lang {
    grid-column: 3;
    margin-right: calc(var(--rail-inset) + var(--rail-size) + 20px);
  }
}

.lang-switch {
  display: inline-flex;
  gap: 2px;
  padding: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
  box-shadow: inset 0 0 0 1px rgba(233, 213, 255, 0.14);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.lang-switch button {
  padding: 7px 13px;
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  background: transparent;
  color: var(--ink-dim);
  font-family: 'Anton', 'Oswald', Impact, sans-serif;
  font-size: 12px;
  letter-spacing: 0.1em;
  transition: color 0.2s var(--ease), background 0.2s var(--ease);
}

.lang-switch button:hover { color: var(--ink); }

.lang-switch button.active {
  color: #12061f;
  background: var(--neon);
}

.lang-switch button:focus-visible {
  outline: 2px solid var(--neon);
  outline-offset: 2px;
}

/* ==================================================================== pricing
 *
 * Same card construction as the levels and the AI branches — a per-card
 * --accent, a solid ground with a wash from the top corner, one hairline. The
 * plans came across from the retired landing; only their presentation is new.
 */

#pricing { padding: 110px 0 120px; }

.price-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 18px;
  margin-top: 52px;
  align-items: stretch;
}

.price-card {
  --accent: var(--neon);
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 26px 22px 24px;
  border-radius: 24px;
  background:
    radial-gradient(120% 130% at 12% 0%,
      color-mix(in srgb, var(--accent) 18%, transparent), transparent 62%),
    rgba(255, 255, 255, 0.022);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent);
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease);
}

.price-card:hover {
  transform: translateY(-5px);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--accent) 44%, transparent),
    0 26px 60px -32px color-mix(in srgb, var(--accent) 70%, transparent);
}

/* The featured plan is marked by weight, not by growing taller — an odd card
   out of five would break the row's baseline on every screen size. */
.price-card.is-featured {
  box-shadow: inset 0 0 0 1.6px color-mix(in srgb, var(--accent) 52%, transparent);
}

.price-ribbon {
  position: absolute;
  top: -11px;
  left: 22px;
  padding: 4px 11px;
  border-radius: 999px;
  background: var(--accent);
  color: #12061f;
  font-family: 'Anton', 'Oswald', Impact, sans-serif;
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.price-tag {
  font-size: 9.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

.price-card h3 { font-size: 21px; }

.price-amount { display: flex; align-items: baseline; gap: 8px; }

.price-amount strong {
  font-family: 'Anton', 'Oswald', Impact, sans-serif;
  font-size: 38px;
  line-height: 1;
  color: var(--ink);
}

.price-amount span { font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-dim); }

.price-note { margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--ink-dim); }
.price-note s { opacity: 0.7; }
.price-note strong { color: var(--ink); }

.price-features {
  margin: 4px 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 9px;
  flex: 1;
}

.price-features li {
  position: relative;
  padding-left: 15px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-dim);
}

.price-features li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 7px;
  height: 1.5px;
  border-radius: 1px;
  background: var(--accent);
  opacity: 0.8;
}

.price-actions { display: flex; flex-wrap: wrap; gap: 9px; }
.price-actions .btn { flex: 1; min-width: 118px; justify-content: center; padding: 12px 14px; font-size: 12px; }
.price-card > .btn { justify-content: center; }

.price-alt {
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
  color: var(--ink-dim);
  font-family: inherit;
  font-size: 11.5px;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.price-alt:hover { color: var(--ink); }

.price-legal {
  margin: 40px 0 0;
  text-align: center;
  font-size: 11.5px;
  color: var(--ink-dim);
}

.price-legal a { text-decoration: underline; text-underline-offset: 3px; }
.price-legal a:hover { color: var(--neon); }

/* Schools seat picker and the live checkout status line. */
.school-seat-picker {
  display: grid;
  gap: 7px;
  margin-top: 4px;
  font-size: 12px;
  color: var(--ink-dim);
}

.school-seat-picker select {
  width: 100%;
  padding: 10px 12px;
  border: 0;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.06);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 26%, transparent);
  color: var(--ink);
  font-family: inherit;
  font-size: 13px;
}

.school-seat-picker select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.school-seat-picker small { font-size: 11px; color: var(--ink-dim); }

.price-note-live {
  margin: 26px auto 0;
  max-width: 62ch;
  text-align: center;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-dim);
  min-height: 1.2em;
}

/* A disabled plan button must still look deliberate, not broken. */
.price-actions .btn:disabled { opacity: 0.55; cursor: progress; transform: none; }

/* Skip link. Off-screen until focused, then a real, visible target — a
   keyboard user should not have to tab the whole header on every load. */
.skip-link {
  position: absolute;
  left: -9999px;
  z-index: 100;
}

.skip-link:focus {
  left: 20px;
  top: 20px;
  padding: 12px 18px;
  border-radius: 12px;
  background: var(--neon);
  color: #12061f;
  font-family: 'Anton', 'Oswald', Impact, sans-serif;
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* Copy confirmation.
   The email buttons put the address on the clipboard instead of opening a mail
   client, so something has to say it worked — a button that silently does
   nothing is indistinguishable from a broken one. */
.copy-note {
  position: fixed;
  left: 50%;
  bottom: 28px;
  z-index: 90;
  transform: translate(-50%, 14px);
  padding: 12px 20px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(21, 12, 46, 0.96);
  color: var(--ink);
  font-size: 14px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s var(--ease), transform 0.22s var(--ease);
}

.copy-note.is-on { opacity: 1; transform: translate(-50%, 0); }
.copy-note.is-bad { border-color: rgba(248, 113, 113, 0.5); color: #fecaca; }

/* The plan, under the figure that is real.
   The strip briefly showed "8 / 221" with a two-line label, which broke the row
   of single big numbers the hero is built around. The headline is what exists;
   the plan sits under it, quiet and still visible. */
.hero-stats div small {
  display: block;
  margin-top: 4px;
  font-size: 10px;
  letter-spacing: 0.04em;
  color: rgba(233, 213, 255, 0.42);
}

/* Scrollbars. The browser default is a light grey bar on a white track, which
   against a deep purple page reads as a strip of another site glued to the
   edge. Both syntaxes: scrollbar-color is the standard one Firefox reads, the
   ::-webkit- pseudo-elements are what Chrome and Safari read, and neither
   covers both.

   `*` rather than `:root`, so panes that scroll on their own are covered too.
   .level-code keeps its own hidden bar — a class beats the universal selector,
   so that rule still wins. */
* {
  scrollbar-width: thin;
  scrollbar-color: rgba(233, 213, 255, 0.22) transparent;
}

::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }

::-webkit-scrollbar-thumb {
  background: rgba(233, 213, 255, 0.22);
  border-radius: 999px;
  /* A transparent border with background-clip keeps the thumb slim to look at
     while leaving the full width as a target to hit. */
  border: 3px solid transparent;
  background-clip: content-box;
}

::-webkit-scrollbar-thumb:hover { background: var(--neon); background-clip: content-box; }
::-webkit-scrollbar-corner { background: transparent; }
