/*
 * protobject.com
 *
 * The look is the one the site has always had, and it is not decoration: the
 * reader is eight to fourteen. A lesson is a stack of full-width colour bands —
 * pink to learn, blue to build, yellow to think about it — each opening with a
 * white icon box and a shouted heading, with the actual content on white cards
 * floating inside. A child scrolling always knows which part of the lesson they
 * are in without reading a word, and that is what the colour is for.
 *
 * What is new is dark mode. The bands keep their hue — take pink away from the
 * learn sections and the navigation goes with it — but drop far enough in
 * lightness to read as night, and the white cards become dark surfaces.
 *
 * The neutral tokens are the app's, verbatim, so crossing from a lesson into the
 * editor has no seam.
 */

:root {
  color-scheme: light dark;

  /* ---- the app's tokens ------------------------------------------------- */
  --brand: #224466;
  --brand-strong: #0b427a;
  --brand-soft: #429ee4;
  --brand-tint: #e7f1fa;
  --device: #00a9b5;
  --device-strong: #007a83;

  --go: #2f8f3a;
  --go-strong: #396e29;
  --danger: #993232;
  --danger-tint: #fbe9e9;

  --bg: #ffffff;
  --bg-sunken: #eef2f6;
  --surface: #ffffff;
  --surface-raised: #ffffff;
  --surface-muted: #f4f7fa;
  --text: #16232e;
  --text-muted: #4d5f6d;
  --text-inverse: #ffffff;
  --border: #c6d2dd;
  --focus: #0a6cff;

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;
  --space-8: 4rem;

  --radius-sm: 0.5rem;
  --radius-md: 0.875rem;
  --radius-lg: 1rem;
  --radius-xl: 1.5rem;
  --radius-pill: 999px;

  --font-sans: 'Segoe UI', system-ui, -apple-system, Roboto, 'Helvetica Neue', Arial, sans-serif;
  --text-xs: 0.8125rem;
  --text-sm: 0.9375rem;
  --text-base: 1.0625rem;
  --text-lg: 1.1875rem;
  --text-xl: 1.5rem;
  --text-2xl: 2.25rem;
  --text-3xl: 3rem;
  --leading-tight: 1.15;
  --leading-normal: 1.6;
  --weight-normal: 400;
  --weight-medium: 600;
  --weight-bold: 800;

  --shadow-1: 0 1px 2px rgba(16, 32, 48, 0.14);
  --shadow-2: 0 6px 18px rgba(16, 32, 48, 0.16);
  --shadow-3: 0 14px 40px rgba(16, 32, 48, 0.24);

  --tap-min: 2.75rem;
  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
  --duration: 220ms;

  --shell: 78rem;
  --shell-narrow: 54rem;

  /* ---- the bands -------------------------------------------------------- */
  /* Each phase is a vertical gradient; `ink` is the colour of anything that has
     to sit directly on it rather than on a card. */
  --hero-from: #2563eb;
  --hero-to: #1e40af;
  --hero-card: #facc15;
  --hero-card-ink: #1b1600;

  --intent-from: #a855f7;
  --intent-to: #7e22ce;

  --teacher-from: #ea580c;
  --teacher-to: #9a3412;

  --learn-from: #db2777;
  --learn-to: #be185d;
  --learn-deep: #831843;

  --create-from: #2563eb;
  --create-to: #1d4ed8;
  --create-deep: #172554;

  --reflect-from: #ca8a04;
  --reflect-to: #a16207;
  --reflect-deep: #713f12;

  --final-from: #0284c7;
  --final-to: #0369a1;
  --final-deep: #0c4a6e;

  /* Home bands, which are quieter because the activity cards carry the colour. */
  --home-from: #2563eb;
  --home-to: #2dd4bf;
  --home-tagline: #f3f4f6;
  --home-activities: #eef2f6;
  --home-activities-ink: #14532d;
  --home-features: #e5e7eb;
  --home-js-from: #9333ea;
  --home-js-to: #6b21a8;

  /* The white hairline between bands: it is what makes them read as separate
     panels rather than as a gradient that lost its way. */
  --band-edge: #ffffff;

  /* On a band, a card. */
  --panel: #ffffff;
  --panel-ink: #16232e;
  --panel-muted: #4d5f6d;
}

:root[data-theme='dark'] {
  --brand: #7fb4e4;
  --brand-strong: #a8cef2;
  --brand-soft: #3d7fb8;
  --brand-tint: #17293a;
  --device: #33c5cf;
  --device-strong: #7ee0e7;

  --go: #5cc06a;
  --go-strong: #7fd08a;
  --danger: #f08a8a;
  --danger-tint: #3a1e1e;

  --bg: #0d141b;
  --bg-sunken: #080d12;
  --surface: #16212c;
  --surface-raised: #1d2a36;
  --surface-muted: #131d26;
  --text: #eef4f9;
  --text-muted: #a9bccb;
  --text-inverse: #0d141b;
  --border: #324556;
  --focus: #7bb2ff;

  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.6);
  --shadow-2: 0 6px 18px rgba(0, 0, 0, 0.6);
  --shadow-3: 0 14px 40px rgba(0, 0, 0, 0.7);

  /* Same hues, night-time. Kept saturated enough to stay recognisable — the
     colour is how the page is navigated — but dark enough that a whole screen
     of it is not a lamp. */
  --hero-from: #1a3a7a;
  --hero-to: #10203f;
  --hero-card: #a37a06;
  --hero-card-ink: #fff8e1;

  --intent-from: #5b1f8f;
  --intent-to: #35104f;

  --teacher-from: #8a3708;
  --teacher-to: #4a1d05;

  --learn-from: #8a1c4c;
  --learn-to: #560f2f;
  --learn-deep: #3d0a21;

  --create-from: #1a3a7a;
  --create-to: #12294f;
  --create-deep: #0b1834;

  --reflect-from: #7a5203;
  --reflect-to: #4d3302;
  --reflect-deep: #362301;

  --final-from: #085a80;
  --final-to: #053a52;
  --final-deep: #032738;

  --home-from: #16345f;
  --home-to: #0d5b56;
  --home-tagline: #131d26;
  --home-activities: #131d26;
  --home-activities-ink: #a7e8bd;
  --home-features: #16212c;
  --home-js-from: #4c1d84;
  --home-js-to: #2d1150;

  --band-edge: #0d141b;

  --panel: #1d2a36;
  --panel-ink: #eef4f9;
  --panel-muted: #a9bccb;
}

/* ---- base ---------------------------------------------------------------- */

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

html {
  scroll-behavior: smooth;
  scroll-padding-top: 5rem;
}

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

/*
 * The footer belongs at the bottom of the window, not at the bottom of the
 * content. A short page — the videos page is two cards — otherwise leaves it
 * stranded halfway down with blank page under it.
 *
 * `dvh` so a phone's collapsing address bar does not leave a gap; `vh` first for
 * anything that does not know `dvh`.
 */
body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  -webkit-text-size-adjust: 100%;
}

main {
  flex: 1;
}

h1,
h2,
h3,
h4 {
  line-height: var(--leading-tight);
  margin: 0 0 var(--space-3);
  text-wrap: balance;
}

h1 {
  font-size: clamp(2rem, 1.2rem + 2.6vw, var(--text-3xl));
  font-weight: var(--weight-bold);
}

h2 {
  font-size: clamp(1.5rem, 1.1rem + 1.6vw, var(--text-2xl));
  font-weight: var(--weight-bold);
}

h3 {
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
}

p {
  margin: 0 0 var(--space-4);
}

a {
  color: var(--brand-strong);
}

img,
video,
iframe {
  max-width: 100%;
}

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border-radius: 0 0 var(--radius-md) 0;
}

.skip-link:focus {
  left: 0;
}

.shell {
  width: 100%;
  max-width: var(--shell);
  margin: 0 auto;
  padding-inline: var(--space-5);
}

.shell.narrow {
  max-width: var(--shell-narrow);
}

/*
 * Centring text is not centring the block that holds it. `.prose` is capped at a
 * readable measure, so without an auto margin it sits hard left while its lines
 * centre inside it — which reads as a mistake, because it is one.
 */
.center {
  text-align: center;
}

.center > *,
.center .prose {
  margin-inline: auto;
}

.center .prose {
  max-width: 46rem;
}

/* ---- header -------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 30;
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
  transition: transform var(--duration) var(--ease);
}

/*
 * Out of the way while you read down, back the instant you reach for it. The
 * transform is on the sticky element itself, so the page below never reflows —
 * a bar that pushed the content around every time it hid would be worse than
 * one that never moved.
 */
.site-header.is-hidden {
  transform: translateY(-100%);
}

@media (prefers-reduced-motion: reduce) {
  .site-header {
    transition: none;
  }
}

.site-header-inner {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-height: 4rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  margin-right: auto;
}

.brand-logo {
  display: block;
  height: 2rem;
  width: auto;
}

:root[data-theme='dark'] .brand-logo {
  /* The logo is one flat dark blue; on a dark bar it disappears. */
  filter: brightness(0) invert(1) opacity(0.92);
}

.site-nav {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

.nav-link {
  color: var(--text);
  text-decoration: none;
  font-weight: var(--weight-medium);
  padding: var(--space-2) 0;
  border-bottom: 3px solid transparent;
}

.nav-link:hover,
.nav-link.is-current {
  border-bottom-color: var(--brand-soft);
}

.site-nav-tools {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/*
 * Only below the breakpoint. It has to out-specify `.icon-button`, which sets
 * `display: inline-flex` and is declared after it — hence the compound selector
 * rather than a bare class.
 */
.icon-button.nav-toggle {
  display: none;
}

.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-width: var(--tap-min);
  height: var(--tap-min);
  padding: 0 var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
}

.icon-button:hover {
  color: var(--text);
  background: var(--surface-muted);
}

.icon {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
}

.lang-code {
  font-size: var(--text-xs);
  text-transform: uppercase;
  font-weight: var(--weight-bold);
}

:root[data-theme='dark'] .icon-light,
:root:not([data-theme='dark']) .icon-dark {
  display: none;
}

.lang-switch {
  position: relative;
}

.lang-menu {
  position: absolute;
  right: 0;
  top: calc(100% + var(--space-2));
  margin: 0;
  padding: var(--space-2);
  list-style: none;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-2);
  min-width: 9rem;
}

.lang-menu a {
  display: block;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--text);
  text-decoration: none;
}

.lang-menu a:hover {
  background: var(--surface-muted);
}

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

/*
 * One shape — a white-ringed pill — in several colours. The ring is what lets
 * the same button sit on a saturated band and on a white card without a second
 * design.
 */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-5);
  border: 2px solid var(--band-edge);
  border-radius: var(--radius-pill);
  font-weight: var(--weight-bold);
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--shadow-1);
  transition:
    transform var(--duration) var(--ease),
    box-shadow var(--duration) var(--ease);
}

.button:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-2);
}

.button .icon {
  width: 1.15rem;
  height: 1.15rem;
}

.button-lg {
  min-height: 3.25rem;
  font-size: var(--text-lg);
  padding-inline: var(--space-6);
}

.button-go {
  background: linear-gradient(90deg, #22a03a, #15803d);
  color: #fff;
}

.button-brand {
  background: linear-gradient(90deg, #3b82f6, #1d4ed8);
  color: #fff;
}

.button-learn {
  background: linear-gradient(90deg, #ec4899, #be185d);
  color: #fff;
}

.button-create {
  background: linear-gradient(90deg, #6366f1, #4338ca);
  color: #fff;
}

.button-reflect {
  background: linear-gradient(90deg, #eab308, #a16207);
  color: #fff;
}

.button-present {
  background: linear-gradient(90deg, #0d9488, #115e59);
  color: #fff;
}

.button-plan {
  background: linear-gradient(90deg, #0891b2, #155e75);
  color: #fff;
}

/* The third thing a class needs, in the colour of the material it makes. */
.button-model {
  background: linear-gradient(90deg, #b45309, #7c2d12);
  color: #fff;
}

.button-quiet {
  background: var(--panel);
  border-color: var(--band-edge);
  color: var(--panel-ink);
}

/* On a coloured band a quiet button reads as glass, not as a hole. */
.band-colour .button-quiet {
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  backdrop-filter: blur(4px);
}

/* ---- bands --------------------------------------------------------------- */

.band {
  padding-block: var(--space-7);
}

/*
 * A full-width colour band. `--from`/`--to` are set per phase; everything not on
 * a card inside it is white, so the band can be any of the six colours without a
 * second rule.
 */
.band-colour {
  background: linear-gradient(180deg, var(--from), var(--to));
  border-top: 4px solid var(--band-edge);
  color: #fff;
}

/*
 * The white rule separates one band from the next. Above the first band there is
 * no previous one — only the header — so it reads as a stray line under the
 * navigation.
 */
main > :first-child.band-colour {
  border-top: 0;
}

/* Prose links only. Navigation on a band is not a sentence. */
.band-colour .prose a {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.phase-learn {
  --from: var(--learn-from);
  --to: var(--learn-to);
  --deep: var(--learn-deep);
}

.phase-create,
.phase-code {
  --from: var(--create-from);
  --to: var(--create-to);
  --deep: var(--create-deep);
}

.phase-reflect {
  --from: var(--reflect-from);
  --to: var(--reflect-to);
  --deep: var(--reflect-deep);
}

.phase-final {
  --from: var(--final-from);
  --to: var(--final-to);
  --deep: var(--final-deep);
}

.band-intent {
  --from: var(--intent-from);
  --to: var(--intent-to);
}

.band-teacher {
  --from: var(--teacher-from);
  --to: var(--teacher-to);
  padding-block: var(--space-6);
}

/*
 * The heading of a band: a white box with the phase's icon, then the title,
 * shouted. It is the strongest signal on the page that a new part of the lesson
 * has started.
 */
.section-title,
.band-title {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
  font-size: clamp(1.4rem, 1.05rem + 1.3vw, 2rem);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.01em;
}

.band-colour .section-title,
.band-colour .band-title {
  color: #fff;
}

.section-title:not(.has-icon),
.band-title:not(.has-icon) {
  justify-content: center;
  text-align: center;
}

/*
 * The icon box stays light in both appearances. It is the one white thing on a
 * saturated band, and it is what carries the phase colour as an *ink* — invert
 * it at night and the colour has nothing to sit on.
 */
.section-title .icon,
.band-title .icon,
.activity-title .icon {
  width: 1.9rem;
  height: 1.9rem;
  padding: 0.6rem;
  box-sizing: content-box;
  border-radius: var(--radius-lg);
  background: #ffffff;
  color: var(--from, var(--brand-strong));
  box-shadow: var(--shadow-1);
  stroke-width: 2.2;
  flex: none;
}

/*
 * The content of a band, on a card. White in daylight, a raised dark surface at
 * night — either way it is the thing you read, lifted off the colour.
 *
 * No shadow: a white sheet on a saturated band already separates itself, and a
 * drop shadow under it only shows up as a smudge of darker colour.
 */
.panel {
  padding: var(--space-7);
  background: var(--panel);
  color: var(--panel-ink);
  border-radius: var(--radius-xl);
}

.panel > :last-child {
  margin-bottom: 0;
}

/*
 * A `create` section is a sequence of movements — the introduction, the steps,
 * the go-ahead — with one gap between them. A grid gap rather than margins, so
 * nothing compounds and nothing collapses.
 */
.create-body {
  display: grid;
  gap: var(--space-6);
}

/* The banner is no longer one of the grid's movements; see `.ready`. */

/*
 * Spacing comes from the grid gap alone. The reset has to out-specify the
 * elements' own margins — `.steps` and `.ready` are declared later in this file
 * and would otherwise win, and grid items do not collapse margins, so their
 * margins would add to the gap instead of disappearing into it.
 */
.create-body > .create-intro,
.create-body > .steps,
.create-body > .ready,
.create-body > .figure {
  margin: 0;
}

/*
 * Nothing here about the gap under the lead: it is the lead's own bottom margin,
 * and an adjacent sibling's top margin would only collapse into it. Rules that
 * look deliberate but never win are worse than no rules.
 */

.create-intro > .prose > p:last-child,
.create-intro > .prose-lead > p:last-child {
  margin-bottom: 0;
}

/*
 * "Build the cardboard model", before the steps.
 *
 * A line, not a button: the loud control for the same page is up in the
 * teacher's band, and this one is part of the reading. The note about the rubber
 * bands trails it in the muted colour — it is the answer to a question the
 * reader has not asked yet, so it must not compete with the link.
 */
.create-model {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
  margin: 0 0 var(--space-4);
}

.create-model > .icon {
  flex: none;
  align-self: center;
  color: #b45309;
}

.create-model-note {
  color: var(--text-muted);
  font-size: 0.92em;
}

/*
 * The line that sets up what follows. It is a heading doing a sentence's job, so
 * it needs the room a heading gets underneath it — pressed against the first
 * step it reads as the first step's own title.
 */
/*
 * A note about the tool, not a line of the lesson: it sits under the heading, on
 * the band, in a quieter voice than anything inside the white card below it.
 */
.section-hint {
  max-width: 46rem;
  margin: calc(var(--space-5) * -1) 0 var(--space-5);
  font-size: var(--text-sm);
  opacity: 0.88;
}

.band-colour .section-hint {
  color: #fff;
}

/* The program fills the card: it is what the section exists to show. */
.panel-flush {
  padding: var(--space-4);
}

.panel-flush .figure {
  box-shadow: none;
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.panel-flush .media-program {
  width: 100%;
}

.section-lead {
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  line-height: 1.35;
  margin-bottom: var(--space-6);
}

/* ---- prose --------------------------------------------------------------- */

.prose {
  max-width: 44rem;
}

/*
 * An editorial opening.
 *
 * Once per section, on the first block of text, and only when the generator has
 * decided there is enough of it to wrap around a capital — see
 * `opensWithEnoughText`. The lead paragraph is a step larger, which is what
 * balances the capital: on its own, a big letter over small type reads as a
 * stray glyph rather than an opening.
 */
.prose-lead > p:first-child {
  font-size: var(--text-lg);
  line-height: 1.6;
}

.prose-lead > p:first-child::first-letter {
  float: left;
  margin: 0.02em 0.11em 0 0;
  font-size: 3.1em;
  line-height: 0.86;
  font-weight: var(--weight-bold);
  color: var(--drop-cap, var(--brand-strong));
}

/* The capital wears the colour of the band it is standing on. */
.phase-learn {
  --drop-cap: var(--learn-to);
}

.phase-create,
.phase-code {
  --drop-cap: var(--create-to);
}

.phase-reflect {
  --drop-cap: var(--reflect-to);
}

:root[data-theme='dark'] .phase-learn {
  --drop-cap: #f472b6;
}

:root[data-theme='dark'] .phase-create,
:root[data-theme='dark'] .phase-code {
  --drop-cap: #7fb4e4;
}

:root[data-theme='dark'] .phase-reflect {
  --drop-cap: #e8b563;
}

/*
 * On a narrow column a line holds few enough words that even a long paragraph
 * cannot wrap three lines around a floated capital without leaving a ragged
 * hole. It simply grows instead.
 */
@media (max-width: 36rem) {
  .prose-lead > p:first-child::first-letter {
    float: none;
    font-size: 1.5em;
    line-height: inherit;
  }
}

.prose > :last-child {
  margin-bottom: 0;
}

/*
 * Content that arrives as a list of texts renders as sibling prose blocks, one
 * per entry — the reflect questions are exactly that — and the rule above
 * strips each block's own bottom margin. Without this, separate thoughts sat
 * pressed together as if they were one.
 */
.panel > .prose + .prose,
.panel > .prose-lead + .prose,
.with-aside-main > .prose + .prose,
.with-aside-main > .prose-lead + .prose,
.prose-wide > .prose + .prose,
.prose-wide > .prose-lead + .prose {
  margin-top: var(--space-4);
}

.prose h3,
.prose h4 {
  margin-top: var(--space-5);
}

.prose ul,
.prose ol {
  padding-left: 1.4em;
}

.prose li {
  margin-bottom: var(--space-2);
}

.prose img {
  border-radius: var(--radius-lg);
}

.prose code {
  padding: 0.1em 0.35em;
  border-radius: var(--radius-sm);
  background: var(--surface-muted);
  color: var(--text);
  font-size: 0.95em;
}

/* ---- section layouts ----------------------------------------------------- */

.split,
.columns,
.with-aside {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: 1fr;
}

/*
 * One column of a paired set: the picture, then the paragraph about it. They
 * need a real gap — the figure has no padding of its own any more, so without
 * one the first line of text sits on the bottom edge of the illustration.
 */
.column {
  display: grid;
  align-content: start;
  /* Still tighter than the gap between the columns, so each picture stays
     visibly bound to its own paragraph. */
  gap: var(--space-5);
}

.column .prose {
  max-width: none;
}

.split {
  align-items: center;
}

/*
 * Stacked on a phone, the picture goes first: show, then explain. The
 * illustration is the thing that makes a child stop scrolling, and it is
 * decorative (`alt=""`), so leading with it visually costs a screen reader
 * nothing while the paragraph still follows it in the document.
 */
.split > .figure {
  order: -1;
}

.split > .prose,
.split > .prose-lead {
  max-width: none;
}

.split .prose {
  max-width: none;
}

.prose-wide {
  max-width: 52rem;
}

/* Room to breathe between a section's opening text and whatever follows it. */
.prose-wide + .figure-row,
.figure-row + .prose-wide {
  margin-top: var(--space-5);
}

@media (min-width: 48rem) {
  .split {
    grid-template-columns: 1.05fr 1fr;
    gap: var(--space-6);
  }

  /* Side by side there is no "first": the text leads on the left. */
  .split > .figure {
    order: 0;
  }
  .columns {
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
    gap: var(--space-6);
  }
  .with-aside {
    grid-template-columns: minmax(0, 2fr) minmax(16rem, 1fr);
    /* Top of the column, beside everything, not floating in the middle of it. */
    align-items: start;
    gap: var(--space-6);
  }

  /*
   * Except in Reflect: a handful of questions beside a tall challenge card sat
   * pinned to the top with a lake of empty band under them. Centred against the
   * card they read the way the split sections do.
   */
  .phase-reflect .with-aside {
    align-items: center;
  }
}

/*
 * An illustration.
 *
 * No frame. The artwork already carries its own background colour and its own
 * rounded corners, so a bordered box around it inside a rounded card made three
 * nested rectangles for one picture. The rounding and a soft shadow are enough
 * to say "this is an exhibit" — everything beyond that was chrome about chrome.
 */
.figure {
  margin: 0;
  padding: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-1);
  line-height: 0;
}

.figure .media {
  display: block;
  border-radius: 0;
}

/*
 * A screenshot of the app, on a pale band.
 *
 * Left bare it dissolves into the page — it is itself mostly white chrome — so
 * it gets a real edge and a shadow, which is what makes it read as a window
 * onto something rather than as part of the article.
 */
.figure-showcase {
  max-width: 62rem;
  margin-top: var(--space-6);
  padding: var(--space-2);
  border: 3px solid var(--band-edge);
  box-shadow: var(--shadow-3);
  background: var(--panel);
}

/* Several illustrations belonging to one paragraph, across the top. */
.figure-row {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  margin-bottom: var(--space-5);
}

/*
 * The aside: a Pro Tip, a challenge, something to notice.
 *
 * Loud on purpose. These are the remarks a teacher makes out loud, and as a
 * white box on a white card they read as an afterthought.
 */
/*
 * The icon sits on top, not in a column of its own.
 *
 * As a left-hand column it pushed everything under it inwards, so the picture
 * started a couple of centimetres from the card's edge and the indent read as a
 * mistake. Stacking removes the column instead of compensating for it, and the
 * picture can reach the sides the way it does in every other card.
 */
.note-card {
  display: grid;
  gap: var(--space-4);
  justify-items: start;
  padding: var(--space-5);
  background: linear-gradient(180deg, #fff7db, #ffefc0);
  color: #4a3505;
  border: 2px solid #f0c74a;
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-2);
}

:root[data-theme='dark'] .note-card {
  background: linear-gradient(180deg, #33280d, #241c07);
  color: #ffe9a8;
  border-color: #6b5305;
}

.note-icon {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  flex: none;
  border-radius: 50%;
  background: #f0c74a;
  color: #4a3505;
}

.note-icon .icon {
  width: 1.4rem;
  height: 1.4rem;
}

.note-card .prose {
  max-width: none;
}

/* Inside a card the aside is already contained; it needs no second shadow. */
.panel .note-card {
  box-shadow: none;
}

/* Text and picture inside an aside are two things, not one paragraph. */
.note-body {
  display: grid;
  gap: var(--space-4);
  width: 100%;
}

.note-card .prose > :last-child,
.note-body > :last-child {
  margin-bottom: 0;
}

.note-card a {
  color: inherit;
}

.media {
  display: block;
  width: 100%;
  border-radius: var(--radius-lg);
}

/*
 * A floor under an embedded program, where there is room for one.
 *
 * The frame is sized to the program it holds, which is right — but it means the
 * article twitches: a short program gets a short frame, opening a comment grows
 * it, closing it shrinks it back. Below 550 the saving is not worth the
 * movement, and a program that small looks lost in a wide column anyway.
 *
 * Only on a wide screen. On a phone 550 pixels is most of the display, and the
 * frame there is already as tall as the program by design.
 */
@media (min-width: 46rem) {
  .media-program {
    min-height: 550px;
  }
}

.media-video {
  background: #000;
  box-shadow: var(--shadow-2);
}

.media-program,
.media-embed {
  border: 0;
  aspect-ratio: 16 / 9;
  background: transparent;
}

.media-program[style*='height'],
.media-embed[style*='height'] {
  aspect-ratio: auto;
}

.media-only {
  max-width: 50rem;
  margin-inline: auto;
}

/* ---- steps --------------------------------------------------------------- */

.steps {
  counter-reset: step;
  list-style: none;
  margin: 0 0 var(--space-5);
  padding: 0;
}

.steps > li {
  counter-increment: step;
  position: relative;
  margin-bottom: var(--space-5);
  padding-left: 3.5rem;
  min-height: 2.4rem;
  line-height: 1.55;
}

.steps > li:last-child {
  margin-bottom: 0;
}

.steps > li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  /* Centred on the first line of the step, whatever the line height. */
  top: 0.05em;
  display: grid;
  place-items: center;
  width: 2.4rem;
  height: 2.4rem;
  border-radius: 50%;
  background: linear-gradient(180deg, var(--from), var(--to));
  color: #fff;
  font-weight: var(--weight-bold);
  box-shadow: var(--shadow-1);
}

/*
 * The go-ahead, under the card rather than in it: it closes the section instead
 * of belonging to it — the last thing you read before the colour changes. The
 * deepest shade of the band it sits on, so it reads as part of the band and not
 * as another card.
 */
.ready {
  margin: var(--space-5) 0 0;
  padding: var(--space-5) var(--space-6);
  border-radius: var(--radius-xl);
  background: var(--deep, var(--to));
  color: #fff;
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  text-align: center;
}

/* ---- the inline control chips -------------------------------------------- */

/*
 * These stand in for controls in the app, inside a sentence: "press ✙ Add
 * device". They look like the real control on purpose — it saves a child
 * mapping a name onto a screen — so their colours are the app's, and the white
 * ring keeps them legible on a card or on a band alike.
 */
.chip {
  display: inline-block;
  white-space: nowrap;
  margin: 0 0.1em;
  padding: 0 0.5em;
  line-height: 1.4;
  border-radius: 3px;
  outline: 2px solid var(--panel);
}

.chip-add {
  background: #478383;
  border: 2px solid #478383;
  border-radius: 5px;
  color: #fff;
  padding: 0 0.35em;
}

/* The RUN button, in the green it wears on the editor's toolbar. */
.chip-run {
  background: #2f8f3a;
  color: #fff;
}

.chip-component {
  background: #e9ebec;
  border: 2px solid #e9ebec;
  border-radius: 1px;
  color: #111;
  font-weight: var(--weight-bold);
}

.chip-open {
  background: #17a2b8;
  border: 1px solid #17a2b8;
  color: #fff;
}

.chip-category {
  background: #dddddd;
  border-left: 8px solid #5a5a5a;
  border-radius: 0;
  color: #111;
  padding: 0.05em 0.9em;
}

.chip-qr {
  background: #e9ebec;
  border: 1px solid #e9ebec;
  color: #111;
}

.chip-question {
  display: inline-grid;
  place-items: center;
  width: 1.7em;
  height: 1.7em;
  padding: 0;
  border-radius: 50%;
  background: #2563eb;
  color: #fff;
  font-weight: var(--weight-bold);
}

/* ---- activity hero ------------------------------------------------------- */

/*
 * The one place the pattern inverts: a yellow card on a blue band. It is the
 * front page of the lesson, and it is meant to stop you.
 */
.activity-hero {
  --from: var(--hero-from);
  --to: var(--hero-to);
  padding-block: var(--space-7);
}

.activity-card {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: 1fr;
  padding: var(--space-7);
  border-radius: var(--radius-xl);
  background: var(--hero-card);
  color: var(--hero-card-ink);
  box-shadow: var(--shadow-3);
}

@media (min-width: 52rem) {
  .activity-card {
    grid-template-columns: 1.05fr 1fr;
    /* Top edges level. Centred, the illustration started somewhere below the
       title and lined up with nothing, which is what made it look adrift. */
    align-items: start;
  }
}

/*
 * `--title-max` is set per activity by the generator, from the length of the
 * words: a ceiling, not a size, so the clamp still scales it down on a narrow
 * screen. Without it every title took the size of the shortest one and the long
 * ones ran to three lines.
 */
.activity-card h1 {
  font-size: clamp(1.75rem, 1.1rem + 2.2vw, var(--title-max, 3rem));
  text-transform: uppercase;
  margin-bottom: var(--space-4);
}

.activity-card .lede {
  font-size: var(--text-lg);
  line-height: 1.62;
}

.activity-title {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}

.activity-title .icon {
  width: 2rem;
  height: 2rem;
  padding: 0.7rem;
  box-sizing: content-box;
  border-radius: var(--radius-lg);
  background: var(--panel);
  color: #ca8a04;
  box-shadow: var(--shadow-1);
  flex: none;
}

.activity-hero-art img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-2);
}

/*
 * What kind of activity this is, and what it is about.
 *
 * No white box: a white panel inside the yellow card repeated the idiom of the
 * whole page for a line of keywords, and gave them the weight of content. The
 * hierarchy is carried by the shapes instead — the level is a solid badge
 * because it classifies the activity, the tags are quiet chips because they only
 * describe it.
 */
.activity-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-5) 0 0;
}

/* Under the illustration, opposite the keywords on the other side. */
.activity-level {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  margin: var(--space-4) 0 0;
}

.activity-level-label {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #6b5a08;
}

:root[data-theme='dark'] .activity-level-label {
  color: #fff3c4;
}

.activity-hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-3);
  margin: var(--space-5) 0 0;
}

/* ---- "what will we do?" -------------------------------------------------- */

/*
 * "What will we do?" — the film of the finished thing.
 *
 * The heading row is exactly as wide as the film below it: the words on its left
 * edge, the icon on its right. That is what ties the three to each other instead
 * of leaving a centred title floating over a picture.
 */
.intent-film,
.intent-head {
  max-width: 58rem;
  margin-inline: auto;
}

.intent-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}

.intent-head h2 {
  margin: 0;
  font-size: clamp(1.4rem, 1.05rem + 1.3vw, 2rem);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.01em;
  color: #fff;
}

.intent-head-icon {
  display: grid;
  place-items: center;
  width: 3.1rem;
  height: 3.1rem;
  flex: none;
  border-radius: var(--radius-lg);
  background: #fff;
  color: var(--intent-to);
  box-shadow: var(--shadow-1);
}

.intent-head-icon .icon {
  width: 1.6rem;
  height: 1.6rem;
}

.intent-film .media {
  border: 4px solid var(--band-edge);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-2);
}

/* The three ways into the lesson, centred under the film. */
.phase-jump {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  justify-content: center;
  margin-top: var(--space-6);
}

/* ---- teacher panel ------------------------------------------------------- */

/*
 * The lesson plan sits on the orange band itself, not on a card.
 *
 * It is a long document addressed to one reader in a page written for another,
 * and a white card would give it the weight of the lesson rather than of an
 * appendix to it. Open, it simply continues down the band.
 */
.teacher {
  color: #fff;
}

.teacher {
  display: grid;
  grid-template-columns: 1fr;
  align-items: start;
}

@media (min-width: 46rem) {
  .teacher {
    grid-template-columns: 1fr auto;
  }
  .teacher-fold {
    grid-column: 1;
  }
  .teacher-downloads {
    grid-column: 2;
    grid-row: 1;
    /* Level with the summary, not with the middle of an opened plan. */
    align-self: start;
  }
}

.teacher-fold > summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  min-height: var(--tap-min);
  cursor: pointer;
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  list-style: none;
}

.teacher-fold > summary::-webkit-details-marker {
  display: none;
}

.teacher-marker {
  width: 1.5rem;
  height: 1.5rem;
  flex: none;
  color: #fff;
  stroke-width: 3;
  transition: transform var(--duration) var(--ease);
}

.teacher-fold[open] .teacher-marker {
  transform: rotate(90deg);
}

.teacher-body {
  padding: var(--space-4) 0 var(--space-5);
  max-width: none;
  color: #fff;
}

.teacher-body h3,
.teacher-body h4 {
  color: #fff;
}

.teacher-downloads {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: 0;
  padding: var(--space-2) 0;
}

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

/*
 * Three per row on a desktop, and every illustration whole. The artwork is
 * 1280x720 and it is the lesson's shop window — cropping it to a taller box hid
 * a third of every picture.
 */
.card-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
  margin: 0 0 var(--space-6);
  padding: 0;
  list-style: none;
}

@media (min-width: 64rem) {
  .card-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.card {
  display: flex;
}

.card[hidden] {
  display: none;
}

.card-link {
  display: flex;
  flex-direction: column;
  width: 100%;
  border-radius: var(--radius-xl);
  overflow: hidden;
  background: var(--panel);
  color: var(--panel-ink);
  border: 3px solid var(--band-edge);
  box-shadow: var(--shadow-2);
  text-decoration: none;
  transition:
    transform var(--duration) var(--ease),
    box-shadow var(--duration) var(--ease);
}

.card-link:hover,
.card-link:focus-visible {
  transform: translateY(-4px);
  box-shadow: var(--shadow-3);
}

/*
 * `height: auto` is load-bearing. The `height` attribute on the tag is a
 * presentational hint for the CSS `height` property, so with a width from CSS
 * and a height from the attribute both dimensions are definite and
 * `aspect-ratio` is ignored — which is what was cropping every illustration
 * into a portrait.
 */
.card-art {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: var(--surface-muted);
}

/*
 * Below the picture, never on top of it. These illustrations are the lesson, and
 * a caption laid over one hides whatever it was showing.
 */
/*
 * The description takes up the slack, so a two-line title in one card does not
 * leave the card next to it ending an inch higher.
 */
.card-text {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5) var(--space-5);
}

.card-description {
  flex: 1;
}

.card-title {
  margin: 0;
  font-size: var(--text-lg);
}

.card-description {
  margin: 0;
  color: var(--panel-muted);
  font-size: var(--text-sm);
}

.badge {
  display: inline-block;
  padding: 0.15em 0.8em;
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #fff;
}

.badge-level-1 {
  background: #2563eb;
}

.badge-level-2 {
  background: #ea580c;
}

.badge-level-3 {
  background: #be123c;
}

.tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0;
}

.tag {
  padding: 0.2em 0.85em;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.55);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: #46370a;
}

/* ---- home ---------------------------------------------------------------- */

/*
 * The concentric arcs behind the hero and the activity bands. They are the one
 * piece of pure decoration the site has, and they are what stops a flat
 * gradient from looking like a placeholder.
 */
.band-pattern {
  position: relative;
  isolation: isolate;
}

.band-pattern::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: url('/src/bg.svg');
  background-repeat: no-repeat;
  background-position: -60vw 85%;
  background-size: 190%;
  opacity: 0.16;
  pointer-events: none;
}

.activity-hero.band-pattern::before {
  background-position: -60vw 20%;
}

/* Headline and buttons on the left, the showreel on the right. */
.home-hero-inner {
  display: grid;
  gap: var(--space-6);
  align-items: center;
  grid-template-columns: 1fr;
}

@media (min-width: 52rem) {
  .home-hero-inner {
    grid-template-columns: 1fr 1.15fr;
    gap: var(--space-7);
  }
}

.home-hero {
  padding-block: var(--space-7) var(--space-8);
  background: linear-gradient(120deg, var(--home-from), var(--home-to));
  color: #fff;
}

.home-hero h1 {
  font-size: clamp(2.1rem, 1.1rem + 3.6vw, 3.5rem);
  margin-bottom: var(--space-5);
}

.home-hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-top: var(--space-6);
}

/* A thin white ring, enough to lift the film off the gradient. */
.home-hero-art {
  border: 4px solid var(--band-edge);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-3);
  overflow: hidden;
  line-height: 0;
}

.home-hero-art .media {
  border-radius: 0;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.band-tagline {
  background: var(--home-tagline);
}

.band-tagline h2 {
  text-transform: none;
}

/*
 * Open a browser · scan the code · the phone is the device.
 *
 * A row of three on any screen wide enough to read them side by side, and a
 * column below that — the drawings carry the sequence, so stacking them loses
 * nothing. The counter is drawn rather than listed: `ol` is the right element
 * for steps in order, but the default numbers would sit in the margin.
 */
.tagline-steps {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-6);
  margin: var(--space-7) 0 0;
  padding: 0;
  list-style: none;
  counter-reset: tagline-step;
}

.tagline-step {
  flex: 1 1 12rem;
  max-width: 16rem;
  counter-increment: tagline-step;
}

.tagline-step-art {
  width: 100%;
  max-width: 8.5rem;
  height: auto;
}

.tagline-step p {
  margin: var(--space-3) 0 0;
  color: var(--text);
  font-weight: var(--weight-medium);
}

.tagline-step p::before {
  content: counter(tagline-step);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  margin-right: var(--space-2);
  border-radius: 50%;
  background: var(--brand);
  color: var(--text-inverse);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  vertical-align: 0.1em;
}

.band-activities {
  background: var(--home-activities);
  border-top: 4px solid var(--band-edge);
}

.band-activities .band-title,
.band-activities .band-lede {
  color: var(--home-activities-ink);
}

.band-features {
  background: var(--home-features);
  border-top: 4px solid var(--band-edge);
}

.band-js {
  background: linear-gradient(180deg, var(--home-js-from), var(--home-js-to));
  border-top: 4px solid var(--band-edge);
  color: #fff;
}

.band-lede {
  font-size: var(--text-lg);
  max-width: 48rem;
}

/*
 * A band that makes an offer: what it is on one side, the way in on the other.
 *
 * Centred and stacked below the breakpoint, because at phone width two columns
 * are one narrow column and a button hanging off it.
 */
.band-split {
  display: grid;
  gap: var(--space-5);
  align-items: center;
  justify-items: center;
  text-align: center;
}

.band-split .prose {
  max-width: 44rem;
  margin-inline: auto;
}

.band-split-action {
  margin: 0;
}

@media (min-width: 52rem) {
  .band-split {
    grid-template-columns: 1fr auto;
    gap: var(--space-7);
    justify-items: start;
    text-align: left;
  }

  .band-split .prose {
    margin-inline: 0;
  }
}

/*
 * A call to action needs air above it. Prose has its trailing margin stripped so
 * a card does not end in a gap, which leaves the button that follows sitting on
 * the last line of the sentence.
 */
.band-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-6);
  margin-bottom: 0;
}

.band-actions.center {
  justify-content: center;
}

.band-title:not(.has-icon) + .band-lede {
  margin-inline: auto;
  text-align: center;
}

.lede {
  font-size: var(--text-lg);
}

.feature-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}

.feature {
  padding: var(--space-5);
  background: var(--panel);
  color: var(--panel-ink);
  border: 2px solid var(--band-edge);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-1);
}

.feature-icon {
  display: inline-grid;
  place-items: center;
  width: 3.25rem;
  height: 3.25rem;
  margin-bottom: var(--space-3);
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, #3b82f6, #1d4ed8);
  color: #fff;
}

.feature h3 {
  margin-bottom: var(--space-2);
  font-size: var(--text-base);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.feature p {
  margin: 0;
  color: var(--panel-muted);
  font-size: var(--text-sm);
}

/* ---- the index page ------------------------------------------------------ */

/*
 * One colour per section of the site.
 *
 * It is how the place is navigated: coming from the green half and landing on a
 * purple banner tells you where you are before you have read a word. The banner
 * carries it; the pages below stay neutral so the content is legible.
 */
.hue-blue {
  background: linear-gradient(180deg, #2563eb, #1e3a8a);
}

.hue-green {
  background: linear-gradient(180deg, #16a34a, #14532d);
}

.hue-purple {
  background: linear-gradient(180deg, #9333ea, #581c87);
}

.hue-red {
  background: linear-gradient(180deg, #dc2626, #7f1d1d);
}

:root[data-theme='dark'] .hue-blue {
  background: linear-gradient(180deg, #1a3a7a, #10203f);
}

:root[data-theme='dark'] .hue-green {
  background: linear-gradient(180deg, #10603a, #062417);
}

:root[data-theme='dark'] .hue-purple {
  background: linear-gradient(180deg, #5b1f8f, #2c0d47);
}

:root[data-theme='dark'] .hue-red {
  background: linear-gradient(180deg, #8f1f1f, #400d0d);
}

.band-index {
  background: var(--home-activities);
  min-height: 100vh;
  padding-top: var(--space-6);
}

.index-layout {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: 1fr;
}

@media (min-width: 64rem) {
  .index-layout {
    grid-template-columns: 21rem 1fr;
    align-items: start;
  }
}

/*
 * The filter follows you.
 *
 * A plain `position: sticky` pins the top of a panel that is taller than the
 * screen, so its last group can never be reached. This one is sticky at an
 * offset the script moves as you scroll: down and it slides up until its own
 * bottom is showing, up and it comes back immediately. The offset is measured
 * from wherever the header currently is, and always leaves a gap, so the panel
 * never ends up flush against an edge. See `site.js`.
 */
.sticky-follow {
  position: sticky;
  top: var(--follow-top, 5.5rem);
  margin-block: var(--space-1);
}

/*
 * Not on a phone.
 *
 * Following is for a panel standing beside a grid, where staying put is the
 * whole point. In one column it is a panel *above* the grid, and pinning it
 * means two things move at once — the list slides past a filter that does not —
 * with a stutter every time the header comes and goes and the offset is
 * recomputed under it. Folded it is one bar at the top of the list; open it is
 * a form. Neither wants pinning: the page scrolls, and that is all.
 */
@media (max-width: 63.999rem) {
  .sticky-follow {
    position: static;
    top: auto;
    margin-block: 0;
  }
}

.filters {
  padding: var(--space-5);
  background: var(--panel);
  color: var(--panel-ink);
  border: 2px solid var(--band-edge);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-2);
}

.filters-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  margin: 0 0 var(--space-4);
  padding: 0 0 var(--space-4);
  border: 0;
  border-bottom: 1px solid var(--border);
  background: none;
  color: inherit;
  font: inherit;
  font-weight: var(--weight-bold);
  text-align: left;
  text-transform: uppercase;
}

/*
 * Folded on a phone.
 *
 * Nine groups of checkboxes is a whole screen of filter before the first
 * activity — you arrive at a page called "activities" and cannot see one. So
 * below the two-column breakpoint the panel starts closed and the head becomes
 * what it looks like: a button, with the count of what is showing on it.
 */
.filters-chevron {
  display: none;
}

@media (max-width: 63.999rem) {
  .filters-head {
    cursor: pointer;
  }

  .filters-chevron {
    display: block;
    margin-left: var(--space-2);
    transform: rotate(90deg);
    transition: transform var(--transition, 0.2s) ease;
  }

  .filters-head[aria-expanded='true'] .filters-chevron {
    transform: rotate(-90deg);
  }

  /* Closed, the panel is one bar: no rule under the heading, no gap after it. */
  .filters-head[aria-expanded='false'] {
    margin-bottom: 0;
    padding-bottom: 0;
    border-bottom: 0;
  }

  .filters {
    padding: var(--space-4);
  }
}

/*
 * How many activities are showing, on the same line as the word FILTER — which
 * is where you are already looking when you tick a box. As a heavy number on its
 * own line it read as a statistic the page wanted you to admire.
 */
.filters-count {
  margin-left: auto;
  min-width: 2.25rem;
  padding: 0.1em 0.6em;
  border-radius: var(--radius-pill);
  background: var(--surface-muted);
  color: var(--panel-muted);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-align: center;
}

/*
 * Hierarchy inside the filter: a group is its heading plus the things under it.
 * The space between groups has to be clearly larger than the space between the
 * options within one, or the whole panel reads as a single undifferentiated
 * list of checkboxes.
 */
.filter-group {
  border: 0;
  margin: 0 0 var(--space-6);
  padding: 0;
}

.filter-group:last-of-type {
  margin-bottom: 0;
}

.filter-group legend {
  padding: 0;
  margin-bottom: var(--space-2);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--panel-muted);
}

.filter-option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  cursor: pointer;
}

/*
 * A mouse does not need a 44px row. That size is a *touch* target, and on a
 * desktop it spreads six options over the height of a paragraph and loses the
 * grouping. Tightened only where the pointer is precise.
 */
@media (pointer: fine) {
  .filter-option {
    min-height: 0;
    padding-block: 0.2rem;
    line-height: 1.4;
  }

  .filter-option input {
    margin: 0;
  }
}

/* ---- learn / course / projects ------------------------------------------- */

/*
 * The banner at the top of a page.
 *
 * Generous above and below on purpose: a big shouted title in a thin horizontal
 * strip reads as a stripe with words in it. The air is what turns it into a
 * title page.
 */
.article-hero {
  padding-block: var(--space-8);
  color: #fff;
  text-align: center;
}

.article-hero h1 {
  margin-bottom: var(--space-4);
}

.article-hero .lede {
  margin: 0;
  opacity: 0.92;
}

.article-hero h1 {
  text-transform: uppercase;
}

.band-alt {
  background: var(--surface-muted);
}

.device-grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr));
  margin: var(--space-5) 0 0;
  padding: 0;
  list-style: none;
}

.device {
  padding: var(--space-4);
  background: var(--panel);
  color: var(--panel-ink);
  border: 2px solid var(--band-edge);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-1);
  text-align: center;
}

.device img {
  width: 4rem;
  height: 4rem;
  object-fit: contain;
}

.device h3 {
  margin: var(--space-2) 0 var(--space-1);
  font-size: var(--text-base);
}

.device p {
  margin: 0;
  color: var(--panel-muted);
  font-size: var(--text-sm);
}

.chapters {
  margin: 0;
  padding: 0;
  list-style: none;
}

.chapter {
  margin-bottom: var(--space-6);
  padding: var(--space-5) var(--space-6);
  background: var(--panel);
  color: var(--panel-ink);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-2);
}

.chapter h2 {
  font-size: var(--text-xl);
  text-transform: none;
}

.resource-list {
  margin: var(--space-4) 0 0;
  padding: 0;
  list-style: none;
}

.resource a {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  color: inherit;
  text-decoration: none;
}

.resource a:hover {
  background: var(--surface-muted);
}

.resource strong {
  display: block;
  color: var(--brand-strong);
}

.resource-note {
  color: var(--panel-muted);
  font-size: var(--text-sm);
}

.article-section + .article-section {
  margin-top: var(--space-6);
}

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

.site-footer {
  margin-top: auto;
  padding-block: var(--space-6);
  border-top: 4px solid var(--band-edge);
  background: var(--brand);
  color: #fff;
}

:root[data-theme='dark'] .site-footer {
  background: var(--bg-sunken);
}

.site-footer a {
  color: #fff;
}

.site-footer-inner {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  justify-content: space-between;
  align-items: center;
}

.site-footer-copy {
  margin: 0;
}

.site-footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
}

/* ---- language picker ----------------------------------------------------- */

.picker-body {
  background: linear-gradient(120deg, var(--home-from), var(--home-to));
}

.picker {
  min-height: 100vh;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: var(--space-6);
  padding: var(--space-6);
}

.picker-logo {
  filter: brightness(0) invert(1);
}

.picker-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ---- framed inside the editor -------------------------------------------- */

.is-framed .site-header,
.is-framed .site-footer,
.is-framed #openInProtobject,
.is-framed .band-teacher {
  display: none;
}

.is-framed .band {
  padding-block: var(--space-5);
}

/* ---- small screens ------------------------------------------------------- */

@media (max-width: 52rem) {
  .icon-button.nav-toggle {
    display: inline-flex;
  }

  .site-nav {
    display: none;
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    background: var(--bg);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow-2);
  }

  .site-nav.is-open {
    display: flex;
  }

  .site-header-inner {
    position: relative;
  }

  .site-nav-tools {
    justify-content: space-between;
    padding-top: var(--space-3);
    border-top: 1px solid var(--border);
  }

  /* Stacked, the three steps are read one after another rather than compared
     side by side, so the drawings can be smaller and the band stays short. */
  .tagline-steps {
    gap: var(--space-5);
    margin-top: var(--space-6);
  }

  .tagline-step-art {
    max-width: 6rem;
  }

  /*
   * The language menu opens to the right of its button here, not to the left.
   *
   * On a wide header the button is the last thing on the right, so a menu that
   * grows leftwards stays on screen. In the phone menu the same button is the
   * first thing on the left — `space-between` puts it there — and the same rule
   * sent the menu off the edge of the screen, where "English" and "Español"
   * were cut in half.
   */
  .lang-menu {
    left: 0;
    right: auto;
  }

  /* Even on all four sides: uneven padding reads as a mistake at this width,
     where the card is nearly the whole screen. */
  .panel,
  .activity-card {
    padding: var(--space-4);
  }
}

@media print {
  .site-header,
  .site-footer,
  .phase-jump,
  .skip-link {
    display: none;
  }
}

/* The hero's own primary action: a ring on the gradient, as it has always been. */
.button-glass {
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  backdrop-filter: blur(6px);
}

.button-glass:hover {
  background: rgba(255, 255, 255, 0.24);
}

/* ---- contact ------------------------------------------------------------- */

.band-contact {
  background: var(--surface-muted);
}

.contact-card {
  margin-top: var(--space-5);
  padding: var(--space-6);
  background: var(--panel);
  color: var(--panel-ink);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-2);
  text-align: left;
}

.contact-form label {
  display: block;
  margin-bottom: var(--space-4);
}

.contact-form label span {
  display: block;
  margin-bottom: var(--space-1);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
}

.contact-form input,
.contact-form textarea {
  display: block;
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text);
  font: inherit;
}

.contact-form textarea {
  resize: vertical;
}

.contact-thanks {
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  background: var(--brand-tint);
  color: var(--text);
  text-align: center;
}

/* ---- the learn page ------------------------------------------------------ */

.learn-body {
  background: var(--bg-sunken);
}

.learn-layout {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: 1fr;
}

@media (min-width: 64rem) {
  .learn-layout {
    grid-template-columns: 15rem 1fr;
    align-items: start;
  }
}

.learn-section + .learn-section {
  margin-top: var(--space-5);
}

.learn-section > h2 {
  margin-bottom: var(--space-4);
}

/*
 * "In this page".
 *
 * Five long parts, and without an index the only way to reach the one you want
 * is to scroll past the other four. Quiet by design — it is a map, not a
 * feature — and it follows you down (see `.sticky-follow` in site.js).
 */
.toc {
  display: none;
}

@media (min-width: 64rem) {
  .toc {
    display: block;
    padding: var(--space-4) 0;
  }
}

.toc-head {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-4);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.toc-list {
  margin: 0;
  padding: 0;
  list-style: none;
  border-left: 2px solid var(--border);
}

.toc-list a {
  display: block;
  padding: var(--space-2) var(--space-4);
  margin-left: -2px;
  border-left: 2px solid transparent;
  color: var(--text-muted);
  text-decoration: none;
  font-size: var(--text-sm);
}

.toc-list a:hover {
  color: var(--text);
}

.toc-list a.is-current {
  border-left-color: var(--brand-soft);
  color: var(--brand-strong);
  font-weight: var(--weight-medium);
}

/* ---- the teacher course card --------------------------------------------- */

/*
 * Green, on the blue banner of the learn page. The colour is the point: this is
 * addressed to a teacher, on a page otherwise written for whoever is learning.
 */
.promo {
  /*
   * Stacked on a phone: badge, then the text, then the button.
   *
   * It used to keep the icon in a column of its own here too, and that broke
   * it. A grid track will not shrink below the longest word in it — the
   * heading's is "Computational" — so the text ran off the side of the card
   * and the icon sat alone in a tall empty green stripe.
   */
  display: grid;
  grid-template-columns: 1fr;
  justify-items: start;
  gap: var(--space-4);
  margin-top: var(--space-6);
  padding: var(--space-5);
  border-radius: var(--radius-xl);
  background: linear-gradient(180deg, #16a34a, #15803d);
  color: #fff;
  text-align: left;
  box-shadow: var(--shadow-2);
}

@media (min-width: 46rem) {
  .promo {
    grid-template-columns: auto 1fr auto;
    align-items: center;
  }
}

/*
 * Lets the text column actually be as narrow as it is given.
 *
 * `min-width: auto` is the default on a grid item, which means "never narrower
 * than my longest word" — the reason the card overflowed rather than wrapping.
 */
.promo-text {
  min-width: 0;
}

.promo-icon {
  display: grid;
  place-items: center;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: var(--radius-lg);
  background: #fff;
  color: #15803d;
}

.promo-icon .icon {
  width: 1.75rem;
  height: 1.75rem;
}

.promo h2 {
  margin: 0 0 var(--space-1);
  font-size: var(--text-xl);
  /* A long word beats a narrow phone otherwise. */
  overflow-wrap: break-word;
}

.promo p {
  margin: 0;
  font-size: var(--text-sm);
  opacity: 0.95;
}

.promo .button {
  white-space: nowrap;
  background: #fff;
  color: #15803d;
  border-color: #fff;
}

/* ---- a video the text points at ------------------------------------------ */

/*
 * A link with its thumbnail, not an embed: an embed loads YouTube's player, and
 * its cookies, onto a page a child opens at school.
 */
.video-card {
  position: relative;
  display: block;
  max-width: 40rem;
  margin: var(--space-5) 0;
  border-radius: var(--radius-xl);
  overflow: hidden;
  box-shadow: var(--shadow-2);
  line-height: 0;
}

.video-card img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.video-play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(0, 0, 0, 0.25);
  transition: background var(--duration) var(--ease);
}

.video-play .icon {
  width: 2rem;
  height: 2rem;
  padding: 1.1rem 1rem 1.1rem 1.3rem;
  box-sizing: content-box;
  border-radius: 50%;
  background: #dd4b39;
  color: #fff;
  fill: #fff;
}

.video-card:hover .video-play {
  background: rgba(0, 0, 0, 0.1);
}
