/*
 * Groove Lab — the marketing site.
 *
 * The app's look, not dailyscales' paper-and-serif one: near-black ground,
 * raised cards, one accent pair (the blue a normal beat is drawn in, the
 * amber an accented one is) and the system face, so an Apple device renders
 * the page in the same letters the app is drawn in.
 *
 * Every colour on the page comes from a custom property, and every one of
 * those is redefined per theme below. Picking a theme in the widget sets
 * data-theme on <html>, and the whole page follows the pads.
 */

/* ------------------------------------------------------------- the themes */

/*
 * The app ships eight palettes (ios/Metronome/Utilities/Theme.swift). The
 * hexes here are that file's own values, converted straight from its
 * Color(red:green:blue:) triples, so the site cannot drift from the app.
 *
 * TODO(widget): the widget agent owns the canonical palette table, because
 * the pads have to match the app exactly and the widget is the thing being
 * matched. At integration, ITS hexes win: replace the values in these eight
 * blocks with the widget's, keep the property names, and delete this note.
 * The names are the contract; the numbers are not.
 *
 * Slugs are the widget's data-theme values: electric-blue (the default),
 * midnight, ember, mint, sunset, rose-gold, arctic, mono.
 *
 *   --bg            the page ground          (Theme.surfaceBase)
 *   --surface-1..4  cards, chips, edges      (Theme.surface1...surface4)
 *   --accent        a normal beat            (Theme.primary)
 *   --accent-strong the pressed/hover state  (Theme.primaryStrong)
 *   --accent-muted  a beat nobody looks at   (Theme.primaryMuted)
 *   --warm          an accented beat         (Theme.secondary)
 *   --warm-muted    the same, one step down  (Theme.secondaryMuted)
 *
 * The text tones are not per theme in the app either (Theme.textPrimary and
 * friends are constants), so they sit outside these blocks.
 */

:root,
:root[data-theme="electric-blue"] {
  --bg: #0F1318;
  --surface-1: #151A21;
  --surface-2: #1C222A;
  --surface-3: #252C36;
  --surface-4: #374151;
  --accent: #3B82F6;
  --accent-strong: #3171E2;
  --accent-muted: #1E4EA5;
  --warm: #F59E0B;
  --warm-muted: #D97706;
}

:root[data-theme="midnight"] {
  --bg: #0F0C19;
  --surface-1: #151122;
  --surface-2: #1C182C;
  --surface-3: #252038;
  --surface-4: #373151;
  --accent: #8A5CF1;
  --accent-strong: #774CDD;
  --accent-muted: #52339F;
  --warm: #58D0C8;
  --warm-muted: #41A8A2;
}

:root[data-theme="ember"] {
  --bg: #17110E;
  --surface-1: #1D1713;
  --surface-2: #251E19;
  --surface-3: #2F2721;
  --surface-4: #443A31;
  --accent: #EC7639;
  --accent-strong: #D8632D;
  --accent-muted: #9B461E;
  --warm: #F6BD41;
  --warm-muted: #D89E2D;
}

:root[data-theme="mint"] {
  --bg: #0B1410;
  --surface-1: #0F1A15;
  --surface-2: #14221B;
  --surface-3: #1B2C24;
  --surface-4: #283F34;
  --accent: #34D399;
  --accent-strong: #27BE88;
  --accent-muted: #19845F;
  --warm: #FACC15;
  --warm-muted: #D8AE0A;
}

:root[data-theme="sunset"] {
  --bg: #180E10;
  --surface-1: #1F1315;
  --surface-2: #28191B;
  --surface-3: #332123;
  --surface-4: #493133;
  --accent: #FB923C;
  --accent-strong: #EA7B2C;
  --accent-muted: #A5561E;
  --warm: #F472B6;
  --warm-muted: #CE5692;
}

:root[data-theme="rose-gold"] {
  --bg: #150F12;
  --surface-1: #1B1418;
  --surface-2: #231B1F;
  --surface-3: #2D2428;
  --surface-4: #41363B;
  --accent: #EA87A0;
  --accent-strong: #D57089;
  --accent-muted: #964E60;
  --warm: #D4A574;
  --warm-muted: #B6895A;
}

:root[data-theme="arctic"] {
  --bg: #0D1119;
  --surface-1: #121721;
  --surface-2: #181E2A;
  --surface-3: #202836;
  --surface-4: #2F3A4C;
  --accent: #7DD3FC;
  --accent-strong: #64BEEC;
  --accent-muted: #4284A5;
  --warm: #5EEAD4;
  --warm-muted: #43BEAB;
}

:root[data-theme="mono"] {
  --bg: #131313;
  --surface-1: #191919;
  --surface-2: #202020;
  --surface-3: #282828;
  --surface-4: #383838;
  --accent: #A6A6A6;
  --accent-strong: #8C8C8C;
  --accent-muted: #616161;
  --warm: #737373;
  --warm-muted: #595959;
}

:root {
  /* Theme.textPrimary / textSecondary / textTertiary. Constant across the
     eight palettes in the app, and constant here. */
  --text: #EFF2F7;
  --text-dim: #94A3B8;
  --text-faint: #64748B;

  /* Two derived accents, because the raw one cannot do both jobs on all
     eight palettes.

     --on-accent: what is written on top of a filled accent. Dark, always:
     white text clears 4.5:1 on exactly one of the eight (midnight, 4.3:1 —
     and that one fails), while near-black clears it on all eight, from
     4.52:1 on midnight to 11.7:1 on arctic.

     --accent-text: the accent as small text on the page's own ground. Raw
     accent is 4.3:1 on midnight and 4.75:1 on electric blue, both under or
     barely over the line an eyebrow at 0.72rem has to meet; lifted 15%
     toward white the worst case is 5.47:1. Every rule that uses it declares
     the raw accent first, so a browser without color-mix still gets a
     colour rather than an inherited one. */
  --on-accent: #0B0D12;
  --accent-text: color-mix(in srgb, var(--accent) 85%, white);

  /* Edges are light laid over the card rather than a colour of their own, so
     one value works on all eight grounds. */
  --stroke: rgba(255, 255, 255, 0.08);
  --stroke-strong: rgba(255, 255, 255, 0.16);

  --radius: 20px;
  --radius-sm: 12px;

  /* The one measure the whole page lines up on. Narrower sections override
     it on their own wrapper (.wrap.narrow) rather than inventing a width. */
  --measure: 62rem;

  /* The app's face. San Francisco on Apple hardware, which is the point:
     nothing is downloaded and the page is set in the app's own letters. */
  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
          "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  color-scheme: dark;
}

* { box-sizing: border-box; }

html {
  background: var(--bg);
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  /* The app's transport bar sets its BPM in tabular figures and so does the
     page: nothing here is a proportional-figures argument, and every number
     on it is a count of something. */
  font-variant-numeric: tabular-nums;
}

/* The app's background is a radial lift from the top (Theme.backgroundGradient).
   Fixed, so it belongs to the window rather than to the scroll. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: -1;
  background:
    radial-gradient(120% 70% at 50% -10%,
                    color-mix(in srgb, var(--accent) 12%, transparent),
                    transparent 60%);
}

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

a {
  color: var(--accent);
  color: var(--accent-text);
}
a:hover { text-decoration-thickness: 2px; }

/* One focus treatment for the whole site, on the accent so it is visible on
   every one of the eight grounds. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10;
  background: var(--surface-3);
  color: var(--text);
  padding: 0.7rem 1.1rem;
  border-radius: 0 0 var(--radius-sm) 0;
  text-decoration: none;
}
.skip:focus {
  left: 0;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------- section rhythm */

/*
 * Three treatments, used deliberately, and nothing else wraps a section.
 *
 *   the card   exactly one element on the site wears it: the widget. It is
 *              the machine on the page, so it is the thing with a frame.
 *   the band   a full-bleed tonal shift, one step off the page ground, no
 *              radius and no border. Two sections have it (the sounds and
 *              the practice room), and they alternate with naked ones.
 *   naked      eyebrow, heading and prose straight on the page.
 *
 * The page used to be nine identical rounded rectangles, which read as a
 * stack of the same object rather than as a document. What holds it together
 * now is not a wrapper: it is one measure (.wrap), a bigger interval between
 * sections than the boxes were giving, and one opener mark on every eyebrow.
 */

/* The measure. Everything on the page lines up on it — naked sections, the
   content inside a band, the card, and the footer — so the left edge of the
   page is a single line down the whole document. */
.wrap {
  width: min(100% - 2rem, var(--measure));
  margin-inline: auto;
}
/* Short sections take a narrower one rather than a smaller box. */
.wrap.narrow { --measure: 40rem; }
.wrap.centred { text-align: center; }
.wrap.centred .prose { margin-inline: auto; }
.wrap.centred .eyebrow { justify-content: center; }

/* The boxes were doing the separating. Space does it now, so there is more
   of it than there was. */
main > section { padding-block: clamp(2.75rem, 6.5vw, 5rem); }
main > section:first-child { padding-top: clamp(1.25rem, 3vw, 2.25rem); }

/* The band: a change of ground, edge to edge. Hairlines top and bottom as
   insets rather than borders, so the band's height is its padding and
   nothing shifts when one is added or taken away. */
.band {
  background: var(--surface-1);
  box-shadow: inset 0 1px 0 var(--stroke), inset 0 -1px 0 var(--stroke);
}

/* The opener mark: a short accent bar on the eyebrow row, on every section
   opener including the ones inside a band. It is the one repeated ornament
   on the page, and it is what says "a section starts here" now that no edge
   is saying it. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  color: var(--accent-text);
  /* Tracking adds the same space after the last letter, which drags the
     line right by half of it. */
  margin: 0 -0.16em 0.75rem 0;
}
.eyebrow::before {
  content: "";
  flex: none;
  width: 1.75rem;
  height: 2px;
  background: currentColor;
}

h1, h2, h3 { margin: 0; line-height: 1.08; letter-spacing: -0.02em; }

h2 {
  font-size: clamp(1.65rem, 3.4vw, 2.5rem);
  font-weight: 800;
  margin-bottom: 0.9rem;
  text-wrap: balance;
}

.prose {
  margin: 0 0 1.1rem;
  max-width: 40em;
  color: var(--text-dim);
  font-size: 1.02rem;
  text-wrap: pretty;
}
.prose:last-child { margin-bottom: 0; }

/* The centred aside: the opener mark moves from beside the eyebrow to above
   it, which is the same bar in the only place it can sit on a centred block
   without pulling the line off centre. */
.wrap.centred .eyebrow::before {
  width: 3.5rem;
  margin: 0 auto 1.25rem;
}
.wrap.centred .eyebrow {
  flex-direction: column;
  gap: 0;
  margin-right: 0;
  margin-left: -0.16em;
}

/* The label column dailyscales sets in its gutter, here on the one section
   that is about a person rather than a feature. It was invisible while the
   section wore a card: the gutter and the card's padding were the same
   space twice. */
@media (min-width: 900px) {
  .wrap.gutter {
    display: grid;
    grid-template-columns: 11rem minmax(0, 1fr);
    column-gap: 3rem;
  }
  /* align-self, or the eyebrow stretches over all three rows and its own
     centred flex line lands beside the middle of the heading. */
  .wrap.gutter .eyebrow {
    grid-column: 1;
    grid-row: 1 / span 3;
    align-self: start;
    margin-top: 0.55rem;
  }
  .wrap.gutter h2, .wrap.gutter .prose, .wrap.gutter .maker-row { grid-column: 2; }
}

/* The maker, in person: the portrait beside the story, the credit under it.
   Same photo the Daily Scales site uses. */
.maker-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(1.25rem, 3vw, 2rem);
  align-items: start;
  margin-top: 0.35rem;
}
.portrait {
  width: clamp(96px, 14vw, 150px);
  height: auto;
  border-radius: var(--radius-sm);
  outline: 1px solid rgba(255, 255, 255, 0.1);
  outline-offset: -1px;
}
.maker-credit {
  margin-top: 1.1rem;
  display: grid;
  gap: 0.15rem;
}
.maker-credit .name { font-weight: 700; }
.maker-credit .lines {
  display: grid;
  gap: 0.1rem;
  color: var(--text-faint);
  font-size: 0.88rem;
}
@media (max-width: 560px) {
  .maker-row { grid-template-columns: 1fr; }
  .portrait { width: 112px; }
}

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

/* Measured, not chosen. The fold that matters is 1280x720 (a laptop) and
   1194x834 (an iPad in landscape): on both, the hero has to end high enough
   that the widget's eyebrow is on the first screen, because a page whose one
   playable thing is below the fold is a page nobody plays with. Every number
   from here to .hero-credit is part of paying for that. */
.hero {
  padding: clamp(1.5rem, 4vw, 3rem) 0 clamp(1.5rem, 4vw, 2.75rem);
}

/* No card. The title page floats on the page's own ground, and what ends it
   is the space under it plus the section opener below. */
.hero-inner { text-align: center; }

/* The app icon at the size an icon is read at, with the platform's own corner
   rounding rather than the artwork's: the file is square on purpose. */
.hero-icon {
  width: clamp(56px, 8vw, 76px);
  height: auto;
  border-radius: 22%;
  /* Pure white, never a palette neutral: a tinted edge picks up the surface
     under it and reads as dirt on the artwork. Outline, so it costs no
     layout. */
  outline: 1px solid rgba(255, 255, 255, 0.1);
  outline-offset: -1px;
  margin-bottom: 0.75rem;
}

/* The wordmark, not the claim. It is set small and tracked so the headline
   under it is the biggest thing on the screen — which is right, because the
   name means nothing to a stranger and the claim means everything. */
.appname {
  font-size: clamp(1.15rem, 2vw, 1.35rem);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 0.5rem;
}

.headline {
  font-size: clamp(2.1rem, 5.2vw, 3.3rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.02;
  margin: 0 0 0.85rem;
  text-wrap: balance;
}

.hero-support {
  margin: 0 auto 1.35rem;
  max-width: 34em;
  color: var(--text-dim);
  font-size: 1.02rem;
  text-wrap: pretty;
}

.pricenote {
  margin: 0.9rem 0 0;
  font-size: 0.9rem;
  color: var(--text-dim);
}

.hero-credit {
  margin: 0.55rem 0 0;
  font-size: 0.88rem;
  color: var(--text-faint);
}

/* ------------------------------------------------------------- the badge */

/* Apple's badge artwork has its own licence and its own asset pack, and a
   hand-drawn lookalike is worse than plain type. This is a plain button to
   the live listing; swap in the real artwork whenever the asset is added. */
.appstore {
  display: inline-block;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  padding: 0.7rem 1.5rem;
  border-radius: 14px;
  transition: background-color 140ms ease, scale 140ms cubic-bezier(0.2, 0, 0, 1);
}
.appstore:hover { background: var(--accent-strong); color: var(--on-accent); }
.appstore:active { scale: 0.96; }
.appstore .pre {
  display: block;
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  /* Quieter than the second line, but not so quiet it stops clearing 4.5:1
     on the lightest of the eight accents. */
  opacity: 0.9;
  margin-right: -0.14em;
}

/* The ask repeated once in the middle of the page, under the thing the
   reader has just played with. */
.cta {
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--stroke);
  text-align: center;
}

/* ----------------------------------------------------------- the widget */

/* The one card on the site. Raised a step above the page, a lit top edge,
   and a shadow under it: it is a machine sitting on the page, and it is
   allowed to look like one because nothing else does. */
.card {
  background: var(--surface-2);
  border: 1px solid var(--stroke-strong);
  /* Concentric with the widget it holds: the machine inside is 20px, so the
     card is 20px plus its own padding, whatever the padding clamps to. */
  border-radius: calc(20px + var(--stage-pad));
  box-shadow: 0 24px 60px -32px rgba(0, 0, 0, 0.95),
              inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

.widget-stage {
  --stage-pad: clamp(1rem, 2.5vw, 1.5rem);
  margin-top: 1.75rem;
  padding: var(--stage-pad);
  min-height: 12rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* The one guarantee the page makes about a component it does not own: if
     the widget lays out wider than the phone it is being read on, it scrolls
     inside this box rather than tipping the whole page sideways. Every
     control in it is a real button, so the region is reachable by keyboard
     without a tabindex of its own. */
  overflow-x: auto;
}
#groove-widget { width: 100%; }

.widget-fallback {
  margin: 0;
  color: var(--text-dim);
  text-align: center;
  max-width: 28em;
}

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

/* Text beside a phone. `flip` puts the phone on the left, so two consecutive
   sections do not rhyme. */
@media (min-width: 780px) {
  .wrap.split {
    display: grid;
    /* An explicit track for the phone: `auto` here lets the percentage-width
       image inflate the track to its intrinsic 1320px and crush the text
       column to min-content. */
    grid-template-columns: minmax(0, 1fr) 15rem;
    column-gap: clamp(2rem, 5vw, 4rem);
    align-items: center;
  }
  .wrap.split .shot { width: 100%; }
  /* order moves the text to the second cell, so the tracks must swap too or
     the phone lands in the wide column. */
  .wrap.split.flip { grid-template-columns: 15rem minmax(0, 1fr); }
  .wrap.split.flip .split-text { order: 2; }
}
.split-text > :last-child { margin-bottom: 0; }

/* ------------------------------------------------- screenshots and video */

/* The captures are portrait iPhone frames with their rounded corners baked
   into the pixels, so nothing here draws a bezel. Until they land, the same
   boxes stand empty at the same size (see slot() in scripts/build.py). */
.shot {
  margin: 1.75rem auto 0;
  width: min(100%, 15rem);
}
@media (min-width: 780px) {
  .shot { margin-top: 0; }
}
.shot img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 18px;
  outline: 1px solid rgba(255, 255, 255, 0.1);
  outline-offset: -1px;
}

.device-videos {
  margin-top: 2rem;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1rem, 3vw, 2rem);
  justify-items: center;
}
.device-video {
  margin: 0;
  width: min(100%, 17rem);
}
.device-video video {
  display: block;
  width: 100%;
  height: auto;
  /* The takes have the device corners baked into the pixels as a fixed
     colour, and that colour cannot follow the theme. The CSS radius is set
     just past where the baked arc lands at each rendered width (130px on the
     960px-wide encode), so the clip is always CSS's and the baked corner
     colour never shows. */
  border-radius: 38px;
  outline: 1px solid rgba(255, 255, 255, 0.1);
  outline-offset: -1px;
}
.device-video figcaption {
  margin-top: 0.8rem;
  text-align: center;
  font-size: 0.92rem;
  color: var(--text-dim);
  text-wrap: balance;
}
@media (max-width: 720px) {
  .device-videos { grid-template-columns: 1fr; }
  .device-video { width: min(100%, 15rem); }
  .device-video video { border-radius: 34px; }
}

/* A slot is scaffolding, never content: dashed, labelled with the file the
   build is waiting for, and hidden from assistive tech. If one of these is
   still on the page at launch, it is meant to be obvious. */
.slot {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 0.35rem;
  width: 100%;
  padding: 1rem;
  text-align: center;
  border: 2px dashed var(--surface-4);
  border-radius: 18px;
  background: repeating-linear-gradient(
    135deg,
    var(--surface-2) 0 12px,
    var(--surface-1) 12px 24px);
  color: var(--text-faint);
}
.slot-name {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.8rem;
  color: var(--text-dim);
  word-break: break-all;
}
.slot-detail { font-size: 0.82rem; max-width: 18em; }
.slot-size { font-size: 0.72rem; letter-spacing: 0.06em; }

/* ------------------------------------------------------------ small rows */

/* The eight profile names. Chips, because that is how the app draws a set of
   choices, and a small radius inside a section is not the page-level wrapper
   this page was getting monotonous from. */
.profiles {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 1.35rem 0 0;
  padding: 0;
}
.profiles li { display: contents; }
.profile-chip {
  background: var(--surface-3);
  border: 1px solid var(--stroke);
  border-radius: 999px;
  padding: 0 0.95rem;
  min-height: 40px;
  font: inherit;
  font-size: 0.88rem;
  color: var(--text-dim);
  cursor: pointer;
  transition-property: background-color, color, border-color, scale;
  transition-duration: 140ms;
  transition-timing-function: ease-out;
}
.profile-chip:hover { color: var(--text); border-color: var(--stroke-strong); }
.profile-chip:active { scale: 0.96; }
.profile-chip.playing {
  color: var(--on-accent);
  background: var(--accent);
  border-color: transparent;
}

/* The practice room's four, as a 4-up grid rather than a row of pills: the
   structure is the column and the rule over it, not a box round each one.
   Pills said "press me" about four things that cannot be pressed. */
.tiles {
  list-style: none;
  margin: clamp(1.75rem, 4vw, 2.75rem) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1.5rem clamp(1.25rem, 3vw, 2.25rem);
}
.tiles li {
  border-top: 1px solid var(--stroke-strong);
  padding-top: 0.85rem;
  font-size: 0.98rem;
  font-weight: 600;
  color: var(--text);
  text-wrap: balance;
}
@media (max-width: 720px) {
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* The list is in the app's own order, clicks first and kits last, and it is
   not colour-coded: the paragraph above it already says which are which, and
   the two tones that would say it again do not both clear 4.5:1 on all eight
   palettes (the mono theme's second accent is 3.7:1 on this chip). */

/* The transport bar's readout: huge tabular numerals with the note value
   beside them, which is how the app prints a tempo. */
.bpm-line {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin: 1.5rem 0 0;
  color: var(--text-faint);
}
.bpm-line .note {
  font-size: 1.5rem;
  color: var(--accent);
  color: var(--accent-text);
}
.bpm-line .bpm {
  font-size: clamp(2rem, 4vw, 2.75rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  color: var(--text);
}
.bpm-line .arrow { font-size: 1.2rem; }

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

footer {
  margin-top: auto;
  padding: 0 0 clamp(2rem, 4vw, 3rem);
}
/* A rule and the same measure. No card: the footer is the end of the
   document, not another object on it. */
footer .wrap {
  text-align: center;
  border-top: 1px solid var(--stroke);
  padding-top: clamp(2rem, 5vw, 3rem);
}
footer nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1.5rem;
  margin-top: 1.5rem;
}
footer nav a { color: var(--text-dim); text-decoration: none; }
footer nav a:hover { color: var(--text); text-decoration: underline; }
footer .imprint {
  margin: 1.25rem 0 0;
  color: var(--text-faint);
  font-size: 0.85rem;
}
footer .also {
  margin: 0.35rem 0 0;
  color: var(--text-faint);
  font-size: 0.85rem;
}

/* ------------------------------------------------------ the inside pages */

/* The inside pages take the same treatment as a naked section: the measure,
   an opener and nothing round it. */
.pagehead {
  --measure: 40rem;
  width: min(100% - 2rem, var(--measure));
  margin: clamp(1.5rem, 4vw, 3rem) auto 0;
}
.page-body { padding-block: clamp(1.5rem, 3vw, 2.25rem) 0; }
.pagehead h1 {
  font-size: clamp(1.9rem, 4.5vw, 2.6rem);
  font-weight: 800;
}
.pagehead .back {
  display: inline-block;
  margin-bottom: 1rem;
  font-size: 0.9rem;
  text-decoration: none;
}
.page-body h2 {
  font-size: 1.2rem;
  font-weight: 700;
  margin-top: 2rem;
  margin-bottom: 0.5rem;
}
.page-body h2:first-child { margin-top: 0; }
.page-body p { color: var(--text-dim); max-width: 40em; text-wrap: pretty; }
.page-body ul { color: var(--text-dim); max-width: 40em; padding-left: 1.2rem; }
.page-body li { margin-bottom: 0.4rem; }
.page-body .updated { color: var(--text-faint); font-size: 0.85rem; }

/* ------------------------------------------------------- the scroll reveal */

/*
 * A section arrives in parts, a beat apart, in the order it should be read.
 * Everything hangs off `revealing`, which site.js puts on <html> one
 * statement before the observer that takes it off again: nothing is hidden
 * unless something is guaranteed to show it. With JavaScript off, or broken,
 * or never loaded, the page is simply whole.
 */
.revealing .reveal > * {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.5s cubic-bezier(0.2, 0, 0, 1),
              transform 0.5s cubic-bezier(0.2, 0, 0, 1);
}
.reveal.in > * { opacity: 1; transform: none; }
.reveal.in > :nth-child(2) { transition-delay: 70ms; }
.reveal.in > :nth-child(3) { transition-delay: 140ms; }
.reveal.in > :nth-child(n + 4) { transition-delay: 200ms; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .revealing .reveal > * {
    opacity: 1;
    transform: none;
    transition: none;
    transition-delay: 0ms;
  }
  .appstore { transition: none; }
}

/* -------------------------------------------------------- the narrow page */

/* Last in the file: a media query carries no extra specificity, so an
   override written beside the rule it overrides loses to every base rule
   declared after it. */
@media (max-width: 560px) {
  body { font-size: 16px; }
  .wrap { width: min(100% - 2.5rem, var(--measure)); }
  .prose, .hero-support { font-size: 1rem; }
  .shot { width: min(100%, 13.5rem); }
  .device-video { width: min(100%, 13.5rem); }
  /* The stage is the only card on the page and the phone is the width the
     widget wants; it keeps its frame but not the inset it cannot afford. */
  .widget-stage { --stage-pad: 0.75rem; }
  footer nav { gap: 1.1rem; }
}
