/* Arpeggio's site. It wears the app's palettes: the theme menu in the nav picks one, and Auto
   follows the Mac, Paper by day and Graphite by night. Quiet everywhere except the playground,
   which is the stage itself, edge to edge. The live colour is kept for what moves with the music.
   Headlines are set in Faculty Glyphic, the small line above each one in Doto Rounded. */

/* Both faces wait to be drawn until they've arrived rather than standing in another face: everything
   set in them animates in, and a name that changes face halfway through typing looks broken. The
   hero's intro waits for them too (js/site.js). */
@font-face {
  font-family: "Faculty Glyphic";
  src: url("assets/FacultyGlyphic-Regular.woff2") format("woff2");
  font-display: block;
}
@font-face {
  font-family: "Doto";
  src: url("assets/Doto-Rounded-Black.woff2") format("woff2");
  font-weight: 900;
  font-display: block;
}

/* MARK: Theme. The --d-* values are a palette's own, from model.js; js/site.js writes the picked
   one onto :root. These are Paper's, for the moment before it runs. Everything else derives. */

:root {
  --d-bg: #ECE8E1; --d-stage: #DDD8CF; --d-ink: #2A2623; --d-ink-dim: #7F7973; --d-ink-faint: #B4AEA6;
  --d-rule: #D6D1C9; --d-empty: #D2CCC2; --d-chip: #E2DDD5; --d-live: #F0531C; --d-key: #F5F2ED;
  --pulse-0: #B03A12; --pulse-1: #F0531C; --pulse-2: #F3A579; --pulse-3: #111010; --pulse-4: #6B645D;
  --shadow: rgba(42, 38, 35, 0.16);
  --bevel: rgba(255, 255, 255, 0.95);

  --bg: var(--d-bg);
  --ink: var(--d-ink);
  --dim: var(--d-ink-dim);
  --faint: var(--d-ink-faint);
  --rule: var(--d-rule);
  --rest: var(--d-empty);
  --raised: var(--d-key);
  --card: var(--d-chip);
  --live: var(--d-live);
  --panel: color-mix(in srgb, var(--d-bg) 86%, transparent);

  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;
  --song: "Faculty Glyphic", Georgia, serif;
  --dots: "Doto", var(--mono);
  --gutter: clamp(16px, 4vw, 48px);
  --max: 1240px;
  color-scheme: light;
}
/* Before the script has picked, a dark Mac gets Graphite. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --d-bg: #242424; --d-stage: #1B1B1B; --d-ink: #F2F2F2; --d-ink-dim: #A4A4A4; --d-ink-faint: #6A6A6A;
    --d-rule: #454545; --d-empty: #353535; --d-chip: #363636; --d-live: #F55A60; --d-key: #494949;
    --pulse-0: #F55A60; --pulse-1: #F0B1B5; --pulse-2: #6F9BC0; --pulse-3: #F2F2F2; --pulse-4: #8FA6C9;
    --shadow: rgba(0, 0, 0, 0.45);
    --bevel: rgba(255, 255, 255, 0.1);
    color-scheme: dark;
  }
}
:root[data-mode="dark"] {
  --shadow: rgba(0, 0, 0, 0.45);
  --bevel: rgba(255, 255, 255, 0.1);
  color-scheme: dark;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 400 13px/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
  /* Clip, not hidden: hidden would make the body a box that scrolls, and the bar would stick to it
     instead of the page whenever the page itself is held still (the phone's menu). */
  overflow-x: clip;
  transition: background-color 200ms, color 200ms;
}
a { color: inherit; }
h1, h2, h3, p { margin: 0; }
canvas { display: block; }
button { font: inherit; color: inherit; }
:focus-visible { outline: 1.5px solid var(--ink); outline-offset: 2px; }

/* MARK: Type */

/* Headlines: Faculty Glyphic, the face of the app's song titles. */
.display {
  font-family: var(--song);
  font-weight: 400;
  font-size: clamp(40px, 6vw, 88px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
/* The line above a headline: Doto Rounded, in sentence case. */
.eyebrow {
  font-family: var(--dots);
  font-weight: 900;
  /* Doto's dots need room: below about 22px they thin out to a grey haze. In ink, since the dots
     already read lighter than solid type. */
  font-size: 24px;
  line-height: 1.1;
  letter-spacing: 0.01em;
  color: var(--ink);
  margin-bottom: 18px;
  width: fit-content;
}
.lede { font-size: clamp(17px, 1.6vw, 21px); line-height: 1.38; letter-spacing: -0.01em; color: var(--dim); text-wrap: pretty; }
.body { font-size: 16px; line-height: 1.55; color: var(--dim); text-wrap: pretty; }
.fine { font-size: 11px; color: var(--dim); }
.dim { color: var(--dim); }

/* MARK: Keys. Raised toward the one light; pressed in when on. */

.key {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: 34px; padding: 0 16px;
  border: 0; border-radius: 8px;
  background: var(--raised); color: var(--ink);
  text-decoration: none; font-size: 13px; white-space: nowrap;
  cursor: pointer; user-select: none; -webkit-user-select: none;
  box-shadow: -1px -1px 0 var(--bevel), 0 1px 0 var(--shadow), 0 2px 5px var(--shadow), inset 1px 1px 0 var(--bevel);
  transition: transform 80ms ease, box-shadow 80ms ease, background-color 200ms;
}
.key:hover { transform: translateY(-0.5px); }
.key:active, .key[aria-pressed="true"], .key[aria-checked="true"], .fx-row[aria-pressed="true"] .key, .theme-item[aria-checked="true"] .swatch {
  transform: none;
  background: color-mix(in srgb, var(--raised) 93%, #000);
  box-shadow: inset 1px 1px 3px var(--shadow), inset -1px -1px 0 var(--bevel);
}
.key-ink { background: var(--ink); color: var(--bg); box-shadow: 0 1px 0 var(--shadow), 0 3px 10px var(--shadow); }
.key-ink:active { background: var(--ink); box-shadow: inset 1px 1px 4px rgba(0, 0, 0, 0.4); }
.key.small { height: 28px; padding: 0 10px; font-size: 12px; border-radius: 7px; }
.key.big { height: 46px; padding: 0 24px; font-size: 15px; border-radius: 11px; }
.key-glyph { width: 18px; height: 18px; color: currentColor; }

/* MARK: Nav */

/* The page is drawn edge to edge on an iPhone (viewport-fit=cover), so the bar makes room for what's
   there: the status bar above it, and the camera at either side in landscape. Its background runs up
   behind the status bar; its contents sit below it. --bar is its full height, which the menu sheet,
   the hero, the playground and every section's landing spot measure from. Elsewhere the room is 0. */
:root {
  --safe-top: env(safe-area-inset-top, 0px);
  --bar: calc(56px + var(--safe-top));
}
.nav {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; gap: 24px;
  height: var(--bar);
  padding: var(--safe-top) max(var(--gutter), env(safe-area-inset-right, 0px)) 0 max(var(--gutter), env(safe-area-inset-left, 0px));
  background: var(--panel);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  transition: background-color 160ms ease;
}
.brand { display: flex; align-items: center; gap: 9px; text-decoration: none; font-size: 15px; }
.brand-icon { width: 26px; height: 26px; color: var(--ink); }
.nav-links { display: flex; gap: 22px; margin-left: auto; }
.nav-links a { text-decoration: none; font-size: 13px; color: var(--dim); }
.nav-links a:hover { color: var(--ink); }
/* The download button, the same dark key as the page's own. */
.nav-get { height: 32px; padding: 0 14px; font-size: 13px; }
@media (max-width: 760px) { .nav-links { display: none; } .theme-menu { margin-left: auto; } }
@media (max-width: 420px) {
  .nav-get-more, #theme-name { display: none; }
  .theme-btn { padding: 0 8px; }
}

/* MARK: The phone's menu. Under 760px the bar keeps the name, the play key and Menu, whose dots step
   from three lines to a cross. The sheet under it holds the sections, large, in the headline face;
   the themes as keys that wear them; and Download. The page behind holds still while it's open. */
.menu-key { display: none; }
.menu-sheet {
  position: fixed; inset: var(--bar) 0 0; z-index: 29;
  display: flex; flex-direction: column; gap: 36px;
  padding: 16px max(var(--gutter), env(safe-area-inset-right, 0px)) calc(32px + env(safe-area-inset-bottom, 0px)) max(var(--gutter), env(safe-area-inset-left, 0px));
  overflow-y: auto; overscroll-behavior: contain;
  /* Solid: a menu that lets the page's words show through reads as broken. */
  background: var(--bg);
  /* It comes in the way the page's sections do (Reveals, below). The sheet switches on from the bar
     down a quarter at a time, like the scenes window, as the menu key folds into its X. The sections
     print in through the halftone one after another, like a headline's words; the songs rise in a
     line at a time, like the words under it; the theme keys switch on in turn; Download comes in like
     the hero's keys, and its small print fades in after. */
  animation: rows-on 280ms steps(4);
  --head: 80ms; --body: 340ms; --keys: 560ms; --get: 800ms;
}
/* Closing, the way the playground's words leave it: what's in the sheet fades and sinks, and the
   sheet switches off back up into the bar a quarter at a time (js/site.js setMenu). */
.menu-sheet.closing { animation: rows-off 240ms steps(4) 80ms both; pointer-events: none; }
.menu-sheet.closing > * { opacity: 0; translate: 0 8px; transition: opacity 220ms ease, translate 220ms ease; }
/* With the menu open the bar turns solid too, so the bar and the sheet are one surface over the page,
   and it stays solid until the sheet has gone back into it. */
html:is(.menu-open, .menu-closing) .nav { background: var(--bg); -webkit-backdrop-filter: none; backdrop-filter: none; }
.menu-links { display: flex; flex-direction: column; }
.menu-links a {
  padding: 14px 0 12px; border-bottom: 1px solid var(--rule);
  font: 34px/1.1 var(--song); letter-spacing: -0.01em; color: var(--ink); text-decoration: none;
}
/* Through the same halftone as the headlines, held hidden until its turn where the mask isn't drawn. */
.reveal-ready .menu-links a {
  -webkit-mask: radial-gradient(circle, #000 calc(var(--dot) - 0.6px), #0000 var(--dot)) 0 0 / 6px 6px repeat;
  mask: radial-gradient(circle, #000 calc(var(--dot) - 0.6px), #0000 var(--dot)) 0 0 / 6px 6px repeat;
  animation: print-in 360ms steps(6) calc(var(--head) + var(--i, 0) * 70ms) both,
             appear 1ms linear calc(var(--head) + var(--i, 0) * 70ms) both;
}
/* The songs: tap one to play it, the one that's on to pause it. */
.menu-music { display: flex; flex-direction: column; gap: 8px; }
.menu-songs { display: flex; flex-direction: column; gap: 2px; margin: 0 -8px; }
.menu-songs .song-item { height: 46px; padding: 0 8px; animation: line-in 460ms cubic-bezier(0.2, 0.7, 0.2, 1) calc(var(--body) + (var(--n, 0) + 1) * 80ms) backwards; }
.menu-themes { display: flex; flex-direction: column; gap: 12px; }
.menu-label { font-size: 12px; color: var(--dim); animation: fade-in 400ms ease var(--body) backwards; }
.menu-themes .menu-label { animation-delay: calc(var(--keys) - 40ms); }
.menu-swatches { display: flex; flex-wrap: wrap; gap: 8px; }
.menu-swatches .swatch {
  width: 42px; height: 42px; padding: 0; border-radius: 10px; font-size: 13px;
  animation: switch-on 1ms linear calc(var(--keys) + var(--i, 0) * 40ms) backwards;
}
/* The theme in use is pressed in, like every selected key on the page (Keys, above). */
.menu-get { display: flex; flex-direction: column; gap: 10px; margin-top: auto; }
.menu-get .key { width: 100%; animation: card-in 300ms cubic-bezier(0.2, 0.7, 0.2, 1) var(--get) backwards; }
.menu-get > .fine { animation: fade-in 500ms ease calc(var(--get) + 60ms) backwards; }
@media (prefers-reduced-motion: reduce) { .menu-sheet, .menu-sheet * { animation: none !important; } }
/* The page holds still behind the sheet. Only the root is stopped: stopping the body too would make it
   the box the bar sticks to, and opened part way down the page the bar would scroll away with it,
   taking the close key with it (as an iPhone showed). */
html.menu-open { overflow: hidden; }
@media (max-width: 760px) {
  .menu-key { display: inline-flex; }
  .nav > .theme-menu, .nav > .nav-get { display: none; }
}
@media (min-width: 761px) { .menu-sheet { display: none !important; } }

/* The song, from anywhere: once something has played, a play key and the song's name, while the
   playground is off screen. The name drops down all the songs, like the theme menu beside it. */
.nav-play {
  position: relative;
  display: flex; align-items: center; gap: 6px;
  opacity: 0; visibility: hidden;
  transition: opacity 160ms ease, visibility 0s linear 160ms;
}
.nav-play.shown { opacity: 1; visibility: visible; transition: opacity 160ms ease, visibility 0s; }
.nav-play .icon-key { width: 30px; height: 30px; }
/* On a phone the bar is the name on the left and two keys on the right, play and Menu: the same size,
   close together, as a pair. */
@media (max-width: 760px) {
  .nav { gap: 8px; }
  .nav-play .icon-key { width: 32px; height: 32px; }
}
.nav-play canvas { width: 16px; height: 16px; color: var(--ink); }
.nav-song-btn {
  display: flex; align-items: center; gap: 6px; max-width: 200px;
  height: 30px; padding: 0 8px;
  border: 0; border-radius: 8px; background: none; color: var(--ink); cursor: pointer;
}
.nav-song-btn:hover, .nav-song-btn[aria-expanded="true"] { background: color-mix(in srgb, var(--ink) 7%, transparent); }
.nav-song { min-width: 0; font: 14px/1.2 var(--song); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nav-song-btn .arrow { color: var(--faint); font-size: 15px; line-height: 1; transition: transform 140ms ease; }
.nav-song-btn[aria-expanded="true"] .arrow { transform: rotate(90deg); }
.nav-songs { right: auto; left: 0; width: 280px; }
@media (max-width: 1000px) { .nav-song-btn { display: none; } }
@media (max-width: 760px) { .nav-play { margin-left: auto; } .nav-play + .theme-menu { margin-left: 0; } }

/* The theme menu: a palette's name with its live colour, opening onto all five. */
.theme-menu { position: relative; }
.theme-btn {
  display: flex; align-items: center; gap: 8px;
  height: 32px; padding: 0 10px;
  border: 0; border-radius: 8px; background: none; cursor: pointer;
  font-size: 13px; color: var(--dim);
}
.theme-btn:hover, .theme-btn[aria-expanded="true"] { color: var(--ink); background: color-mix(in srgb, var(--ink) 7%, transparent); }
.theme-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--live); }
.theme-btn .arrow { color: var(--faint); font-size: 15px; line-height: 1; transition: transform 140ms ease; }
.theme-btn[aria-expanded="true"] .arrow { transform: rotate(90deg); }
.theme-list {
  position: absolute; z-index: 40;
  width: 264px; padding: 6px;
  display: flex; flex-direction: column; gap: 2px;
  border-radius: 12px;
  background: var(--bg);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--ink) 8%, transparent), 0 18px 40px -16px rgba(0, 0, 0, 0.35), 0 2px 6px -2px rgba(0, 0, 0, 0.12);
}
.theme-list { right: 0; top: calc(100% + 8px); }
.theme-item, .song-item {
  display: flex; align-items: center; gap: 10px;
  height: 40px; padding: 0 8px 0 6px;
  border: 0; border-radius: 8px; background: none; cursor: pointer;
  font-size: 13px; color: var(--ink); text-align: left;
}
.theme-item:hover, .theme-item:focus-visible, .song-item:hover, .song-item:focus-visible { background: color-mix(in srgb, var(--ink) 7%, transparent); outline: none; }
/* Each theme's key wears that theme: its paper and its ink. */
.theme-item .swatch {
  width: 28px; height: 28px; flex: none;
  display: grid; place-items: center;
  border-radius: 7px; font-size: 12px;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 12%, transparent);
}
.theme-item .dots { margin-left: auto; width: 60px; height: 10px; }
.theme-item .sub { margin-left: auto; font-size: 11px; color: var(--dim); }
:is(.theme-item, .song-item) .check { width: 6px; height: 6px; border-radius: 50%; background: var(--live); flex: none; opacity: 0; }
:is(.theme-item[aria-checked="true"], .song-item[aria-current="true"]) .check { opacity: 1; }

/* MARK: Hero. A dot grid edge to edge, with the words centred on it. On load (see js/site.js and
   Reveals) the name types out large in the middle, then moves up into its place over the headline,
   and the rest comes in under it. */

.hero {
  position: relative;
  min-height: calc(100vh - var(--bar));
  min-height: calc(100svh - var(--bar));
  display: flex; align-items: center; justify-content: center;
  padding: 48px var(--gutter) 64px;
  overflow: hidden;
  text-align: center;
}
.hero-grid { position: absolute; inset: 0; width: 100%; height: 100%; }
.hero-text {
  position: relative; z-index: 1;
  max-width: 760px;
  display: flex; flex-direction: column; align-items: center;
  /* The words sit over the grid but let the pointer through to it, except on their keys. */
  pointer-events: none;
}
.hero-text a, .hero-text button { pointer-events: auto; }
.hero .display { font-size: clamp(52px, 8vw, 116px); margin-bottom: 24px; }
.hero .lede { max-width: 560px; margin-bottom: 30px; }
.hero-keys { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-bottom: 16px; }
.hero-keys .icon-key { width: 34px; height: 34px; border-radius: 8px; }
/* Three keys in a row down to a 375 px phone; narrower than that, the play key goes under the other two. */
@media (max-width: 400px) { .hero-keys { gap: 8px; } .hero-keys .key:not(.icon-key) { padding: 0 13px; } }

/* MARK: The playground. The stage, edge to edge. The section's words sit in its top left corner,
   and two corners of the app's own UI below: the song bottom left, the effects card bottom right. */

.play {
  position: relative;
  width: 100%;
  /* The window, less the nav above it. */
  height: calc(100vh - var(--bar));
  height: calc(100svh - var(--bar));
  min-height: 560px;
  scroll-margin-top: var(--bar);
  overflow: hidden;
  background: var(--d-stage);
  color: var(--ink);
}
.stage { position: absolute; inset: 0; width: 100%; height: 100%; }

/* As the playground opens up (js/site.js clips it from a rounded window to the full width), its
   words and corners wait until it's open, then switch on. Without the script, or with reduced
   motion, they're simply there. */
@keyframes card-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes fade-out { from { opacity: 1; } to { opacity: 0; } }
.scroll-fx .play .play-head, .scroll-fx .play .corner { opacity: 0; }
.scroll-fx .play.opened .play-head, .scroll-fx .play.opened .corner { opacity: 1; }
.scroll-fx .play:not(.opened) :is(.play-head, .corner) { pointer-events: none; }
/* Once it's open, everything inside switches on in turn: the line types on and the headline prints
   in (see Reveals), then the words under it; the song card rises in, its key switches on and its
   title prints in; the effects card follows, filling in as it rises, its header and then its rows a
   line at a time, top to bottom, while js/site.js sweeps each row's picture on with its line and
   fills in the song's dots. Each time it opens. */
.scroll-fx .play.opened .corner { animation: card-in 300ms cubic-bezier(0.2, 0.7, 0.2, 1) var(--card-delay, 260ms) backwards; }
/* Leaving, in either direction, the words and cards go quicker than they came: a short fade as they
   sink a little, while the stage closes back into its window behind them (js/site.js). */
.scroll-fx .play:not(.opened) :is(.play-head, .corner) { translate: 0 8px; transition: opacity 220ms ease, translate 220ms ease; }
.scroll-fx .play.opened .fx-card { --card-delay: 340ms; }
.scroll-fx .play.opened #play-key { animation: switch-on 1ms linear 420ms backwards; }
.scroll-fx .play.opened .now-card .artist { animation: fade-in 300ms ease 560ms backwards; }
.scroll-fx .play.opened .fx-head { animation: fade-in 220ms ease 360ms backwards; }
.scroll-fx .play.opened .fx-row { animation: fade-in 220ms ease calc(390ms + var(--line, 0) * 30ms) backwards; }

/* The words: eyebrow, headline, body, like every section, in white, straight on the stage. */
.play-head {
  position: absolute; z-index: 2;
  top: calc(var(--gutter) + 8px); left: var(--gutter);
  max-width: 460px;
  color: #fff;
}
.play-head .eyebrow { color: #fff; }
.play-head .display { color: #fff; font-size: clamp(40px, 5vw, 72px); margin-bottom: 20px; }
/* A little more weight: the picture behind breaks thin strokes up first. */
.play-head .body { color: #fff; font-weight: 500; }
.play-hint { display: none; color: #fff; font-size: 15px; font-weight: 500; }

.veil, .no-gl {
  position: absolute; inset: 0; z-index: 4;
  display: flex; align-items: center; justify-content: center;
  padding: 24px; text-align: center;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  font-size: 22px;
}
.veil { outline: 2px dashed var(--dim); outline-offset: -16px; }
.no-gl { font-size: 15px; }

/* The two panels are frosted glass: a tint of the theme's paper over the stage, blurred hard and a
   little more saturated so the picture glows through as it moves, with a sheen across the top where
   the light catches it. Their edges catch the one light, bright along the top and left. Everything
   on them is made for the glass too (see On the glass, below). */
.corner {
  --sheen: rgba(255, 255, 255, 0.3);
  --tint: color-mix(in srgb, var(--bg) 68%, transparent);
}
:root[data-mode="dark"] .corner {
  --sheen: rgba(255, 255, 255, 0.07);
  --tint: color-mix(in srgb, var(--bg) 55%, transparent);
}
.corner {
  position: absolute; z-index: 2;
  bottom: var(--gutter);
  background: linear-gradient(to bottom, var(--sheen), transparent 55%), var(--tint);
  -webkit-backdrop-filter: blur(28px) saturate(1.7);
  backdrop-filter: blur(28px) saturate(1.7);
  color: var(--ink);
  border-radius: 14px;
  box-shadow:
    inset 1px 1px 0 color-mix(in srgb, #fff 60%, transparent),
    inset -1px -1px 0 color-mix(in srgb, #000 6%, transparent),
    0 0 0 1px color-mix(in srgb, var(--ink) 8%, transparent),
    0 18px 40px -18px rgba(0, 0, 0, 0.35),
    0 2px 6px -2px rgba(0, 0, 0, 0.12);
  transition: background-color 200ms, color 200ms;
}
:root[data-mode="dark"] .corner {
  box-shadow:
    inset 1px 1px 0 color-mix(in srgb, #fff 16%, transparent),
    inset -1px -1px 0 color-mix(in srgb, #000 30%, transparent),
    0 0 0 1px color-mix(in srgb, #000 40%, transparent),
    0 18px 40px -18px rgba(0, 0, 0, 0.6),
    0 2px 6px -2px rgba(0, 0, 0, 0.3);
}

/* The cards' wrapper only lays them out on a phone; elsewhere it makes no box of its own. */
.corners { display: contents; }

/* Bottom left: play, and the song. */
/* The song card is the player. Its name expands it upward, rolling open the way the effects card
   does, to show every song over the one that's on; picking one plays it and folds the list away. It
   keeps one width, so expanding only ever grows it upward. */
.now-card { left: var(--gutter); display: flex; flex-direction: column; width: 300px; padding: 8px 16px 8px 8px; }
.now-row { display: flex; align-items: center; gap: 12px; min-width: 0; }
.now-more { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 300ms cubic-bezier(0.2, 0.7, 0.2, 1); }
.now-card.expanded .now-more { grid-template-rows: 1fr; }
.now-songs { min-height: 0; overflow: hidden; display: flex; flex-direction: column; gap: 2px; }
/* A hairline between the songs and the one that's on, inside what rolls away. */
.now-songs::after { content: ""; height: 1px; margin: 6px 0 8px; background: color-mix(in srgb, var(--ink) 10%, transparent); flex: none; }
/* The songs fade in one after another as it opens, and go together as it closes. */
.now-songs .song-item { height: 38px; padding: 0 8px; opacity: 0; transition: opacity 120ms ease, background-color 120ms; }
.now-card.expanded .now-songs .song-item { opacity: 1; transition: opacity 180ms ease calc(60ms + var(--n, 0) * 30ms), background-color 120ms; }
.icon-key { width: 32px; height: 32px; padding: 0; flex: none; border-radius: 7px; }
#play-key canvas { width: 18px; height: 18px; color: var(--ink); }
.now-text { flex: 1; display: flex; flex-direction: column; gap: 4px; min-width: 0; }
/* Who made the song, under its title, linking to their page. */
.artist {
  font-size: 12px; line-height: 1.3; color: var(--dim); text-decoration: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: color 120ms;
}
.artist::after { content: " \2197\FE0E"; content: " \2197\FE0E" / ""; color: var(--faint); }
.artist:hover { color: var(--ink); }
.artist:hover::after { color: var(--dim); }
.song { font: 15px/1.2 var(--song); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* With more than one song, the name expands the player to show them all. */
.song-menu { min-width: 0; }
.song-btn {
  display: flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%;
  margin: -2px -6px; padding: 2px 6px;
  border: 0; border-radius: 6px; background: none; color: inherit; cursor: pointer; text-align: left;
}
.song-btn:hover, .song-btn[aria-expanded="true"] { background: color-mix(in srgb, var(--ink) 7%, transparent); }
.song-btn .song { min-width: 0; }
.song-btn .arrow { flex: none; color: var(--faint); font-size: 14px; line-height: 1; transition: transform 140ms ease; }
.song-btn[aria-expanded="true"] .arrow { transform: rotate(-90deg); }
.song-item .name { flex: 1; min-width: 0; font: 15px/1.2 var(--song); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.song-item .sub { font-size: 11px; color: var(--dim); white-space: nowrap; }
.now-line { display: flex; align-items: center; gap: 10px; }
.timeline { flex: 1; min-width: 0; height: 6px; color: var(--ink); }
.clock { height: 9px; color: var(--ink); }

/* Bottom right: the effects card. Open or closed it keeps its width: it rolls up out of its header
   bar and back down into it, its rows fading in a line at a time as it opens and together as it
   closes. On a phone it does the same, full width, over the song. */
.fx-card { right: var(--gutter); width: 324px; padding: 2px 8px; }
.fx-head {
  display: flex; align-items: center; gap: 6px;
  width: 100%; height: 40px; padding: 0 6px;
  border: 0; background: none; cursor: pointer;
  font-size: 13px; color: var(--ink); text-align: left;
}
.fx-head .arrow { color: var(--faint); font-size: 15px; line-height: 1; transition: transform 300ms cubic-bezier(0.2, 0.7, 0.2, 1); }
.fx-card.open .fx-head .arrow { transform: rotate(90deg); }
.fx-head .summary { margin-left: auto; padding-left: 16px; display: flex; align-items: center; gap: 6px; color: var(--dim); font-size: 12px; }
.listen { width: 6px; height: 6px; border-radius: 50%; background: var(--live); opacity: 0.35; }
/* Closed, the header's dot stands in for the rows' own. It keeps its place, so the count never moves. */
.fx-head .listen { transition: scale 200ms ease; }
.fx-card.open .fx-head .listen { scale: 0; }
.fx-body { display: grid; grid-template-rows: 1fr; }
.fx-card:not(.open) .fx-body { grid-template-rows: 0fr; }
.fx-body { transition: grid-template-rows 300ms cubic-bezier(0.2, 0.7, 0.2, 1); }
.fx-grid { min-height: 0; overflow: hidden; display: grid; grid-template-columns: 1fr 1fr; gap: 2px 6px; padding-bottom: 6px; }
.fx-row {
  display: flex; align-items: center; gap: 8px;
  height: 36px; padding: 0 6px 0 2px;
  border: 0; border-radius: 8px; background: none; cursor: pointer;
  font-size: 13px; text-align: left; white-space: nowrap;
  /* Off names are dim rather than the app's faint: here most of them are off, and they need reading. */
  color: var(--dim);
  transition: color 120ms, opacity 180ms ease calc(var(--line, 0) * 28ms);
}
.fx-card:not(.open) .fx-row { opacity: 0; transition: color 120ms, opacity 120ms ease; }
.fx-row[aria-pressed="true"] { color: var(--ink); }
/* The rows' box clips to roll up, so their focus ring sits inside them. */
.fx-row:focus-visible { outline-offset: -1.5px; }
.fx-row .key { width: 28px; height: 28px; padding: 0; border-radius: 6px; flex: none; pointer-events: none; }
.fx-row:hover .key { transform: translateY(-0.5px); }
.fx-row[aria-pressed="true"]:hover .key { transform: none; }
.fx-row .key canvas { width: 18px; height: 18px; }
.fx-row .listen { margin-left: auto; opacity: 0; }

/* MARK: On the glass. Keys on the panels are glass too: a thin film of the ink with a lit top edge
   and nothing under them, where the page's keys are solid and cast shadows. One that's on, and the
   play key, is a brighter pane. Words and dots are the ink at lower strengths rather than greys, so
   the picture moving behind tints them; a row lights faintly under the pointer. */
.corner {
  --g-dim: color-mix(in srgb, var(--ink) 62%, transparent);
  --g-faint: color-mix(in srgb, var(--ink) 34%, transparent);
  --g-film: color-mix(in srgb, var(--ink) 7%, transparent);
  --g-film-hover: color-mix(in srgb, var(--ink) 12%, transparent);
  --g-hover: color-mix(in srgb, var(--ink) 6%, transparent);
  --g-line: color-mix(in srgb, var(--ink) 9%, transparent);
  --g-lit: rgba(255, 255, 255, 0.74);
  --g-edge: rgba(255, 255, 255, 0.55);
}
:root[data-mode="dark"] .corner {
  --g-lit: rgba(255, 255, 255, 0.2);
  --g-edge: rgba(255, 255, 255, 0.18);
}
.corner .key {
  background: var(--g-film);
  box-shadow: inset 0 0.5px 0 var(--g-edge), inset 0 0 0 0.5px var(--g-line);
  transition: background-color 160ms ease, box-shadow 160ms ease;
}
.corner .key:hover { transform: none; background: var(--g-film-hover); }
.corner :is(#play-key, .key:active),
.corner .fx-row[aria-pressed="true"] .key {
  background: var(--g-lit);
  box-shadow: inset 0 0.5px 0 var(--g-edge), inset 0 0 0 0.5px var(--g-line), 0 1px 2px rgba(0, 0, 0, 0.1);
}
.corner #play-key:hover { background: color-mix(in srgb, var(--g-lit) 82%, var(--ink)); }
.corner .fx-row { color: var(--g-dim); transition: color 120ms, background-color 120ms, opacity 180ms ease calc(var(--line, 0) * 28ms); }
.corner .fx-row:hover { background: var(--g-hover); }
.corner .fx-row:hover .key { transform: none; }
.corner .fx-row[aria-pressed="true"] { color: var(--ink); }
.corner .fx-head { border-radius: 8px; transition: background-color 120ms; }
.corner .fx-head:hover { background: var(--g-hover); }
.corner .fx-head .summary { color: var(--g-dim); }
.corner :is(.fx-head, .song-btn) .arrow { color: var(--g-faint); }
.corner :is(.song-btn:hover, .song-btn[aria-expanded="true"]) { background: var(--g-hover); }
.corner .artist { color: var(--g-dim); }
.corner .artist::after { color: var(--g-faint); }
.corner .artist:hover { color: var(--ink); }
.now-songs .song-item:is(:hover, :focus-visible) { background: var(--g-hover); }
.now-songs .song-item .sub { color: var(--g-dim); }

@media (max-width: 760px) {
  .play-head { top: 24px; left: 16px; right: 16px; }
  .play-head .display { margin-bottom: 12px; }
  .play-head .body { display: none; }
  .play-hint { display: block; }
  /* The cards stack across the bottom, full width: the effects over the song. The stack sits on the
     bottom edge, so the effects card grows upward as it opens. */
  .corners {
    position: absolute; z-index: 2; left: 12px; right: 12px; bottom: 12px;
    display: flex; flex-direction: column-reverse; gap: 8px;
  }
  .corners > .corner { position: relative; inset: auto; }
  .now-card { width: auto; padding-right: 14px; }
  .fx-card { width: auto; }
  /* Open, the effects are one strip of keys across the card, each name under its picture, sliding
     sideways, so the stage stays in view above it. They fade in along the strip. */
  .fx-grid { display: flex; gap: 2px; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; scroll-snap-type: x proximity; scrollbar-width: none; padding-bottom: 4px; }
  .fx-grid::-webkit-scrollbar { display: none; }
  .corner .fx-row {
    position: relative; flex: none; flex-direction: column; justify-content: flex-start;
    width: 66px; height: auto; padding: 6px 2px; gap: 6px;
    font-size: 11px; line-height: 1.2; text-align: center; white-space: normal;
    scroll-snap-align: start;
    transition: color 120ms, background-color 120ms, opacity 180ms ease calc(var(--n, 0) * 16ms);
  }
  .corner .fx-row .key { width: 46px; height: 46px; border-radius: 11px; }
  .corner .fx-row .key canvas { width: 28px; height: 28px; }
  .corner .fx-row .listen { position: absolute; top: 8px; right: 8px; margin: 0; }
}

/* MARK: Features: a statement, centred, then its picture, large. */

.feature {
  max-width: var(--max); margin: 0 auto;
  display: flex; flex-direction: column; align-items: center; gap: 64px;
  padding: 160px var(--gutter);
  border-bottom: 1px solid var(--rule);
  text-align: center;
  scroll-margin-top: var(--bar);
}
.feature-text { max-width: 760px; display: flex; flex-direction: column; align-items: center; }
.feature .display { margin-bottom: 22px; }
.feature .body { max-width: 560px; }
.feature-art { width: 100%; max-width: 900px; display: flex; flex-direction: column; align-items: center; gap: 56px; }
@media (max-width: 760px) { .feature { padding: 104px var(--gutter); gap: 48px; } }

/* Listens first: the five pulses, each a ring of dots lit to its level. */
.pulses { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 24px; width: 100%; }
.pulses figure { margin: 0; display: flex; flex-direction: column; align-items: center; gap: 14px; }
.pulses canvas { width: 100%; max-width: 128px; aspect-ratio: 1; }
.pulses figcaption { display: flex; flex-direction: column; gap: 2px; font-size: 14px; }
.pulses figcaption small { font-size: 12px; color: var(--dim); }
@media (max-width: 760px) { .pulses { grid-template-columns: repeat(3, minmax(0, 1fr)); row-gap: 36px; } }

/* Eleven effects: one picture at a time, large, on its key, and the eleven keys to pick from. */
.glyph-show { gap: 36px; }
.glyph-plate {
  width: min(340px, 74vw); aspect-ratio: 1;
  display: grid; place-items: center;
  border-radius: 36px;
  background: var(--raised);
  box-shadow: -1px -1px 0 var(--bevel), 0 2px 0 var(--shadow), 0 28px 56px -28px var(--shadow), inset 1px 1px 0 var(--bevel);
  transition: background-color 200ms;
}
.glyph-plate canvas { width: 74%; height: 74%; color: var(--ink); }
.glyph-info { display: flex; flex-direction: column; align-items: center; min-height: 148px; }
.glyph-name { font-family: var(--song); font-weight: 400; font-size: clamp(32px, 3.6vw, 48px); line-height: 1.05; margin-bottom: calc(12px - 0.22em); }
.glyph-meta { font-size: 20px; margin-bottom: 12px; text-wrap: balance; }
.glyph-blurb { font-size: 15px; color: var(--dim); max-width: 420px; }
.glyph-picker { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; max-width: 660px; }
.glyph-key { width: 48px; height: 48px; padding: 0; border-radius: 10px; }
.glyph-key canvas { width: 24px; height: 24px; }
@media (max-width: 760px) {
  .glyph-meta { font-size: 18px; }
  .glyph-key { width: 42px; height: 42px; }
  .glyph-key canvas { width: 20px; height: 20px; }
}

/* Scenes: a window onto the stage, and the eight keys under it. Nothing else: the song brings its
   scenes in by itself while it plays, and the keys show it. */
.scene-art { gap: 24px; }
.scene-window {
  width: 100%; max-width: 860px; aspect-ratio: 16 / 10;
  border-radius: 14px; overflow: hidden;
  background: var(--d-stage);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--ink) 8%, transparent), 0 30px 60px -30px var(--shadow);
}
.scene-window canvas { width: 100%; height: 100%; }
.scene-strip { display: flex; gap: 6px; }
.scene-strip .key { width: 52px; height: 52px; padding: 0; }
.scene-strip canvas { position: relative; width: 18px; height: 26px; color: var(--ink); }
.scene-strip .key.empty canvas { color: var(--faint); }
/* A scene picked while the song plays waits for its beat, filling its key from the left as it comes. */
.scene-key { position: relative; overflow: hidden; }
.scene-key::before {
  content: ''; position: absolute; inset: 0;
  background: color-mix(in srgb, var(--ink) 14%, transparent);
  transform-origin: left; transform: scaleX(var(--fill, 0));
  pointer-events: none;
}
/* On a phone the keys take the window's width, eight across. */
@media (max-width: 640px) {
  .scene-art { gap: 16px; }
  .scene-strip { width: 100%; display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 5px; }
  .scene-strip .key { width: auto; height: auto; aspect-ratio: 1; border-radius: 7px; }
  .scene-strip canvas { width: 14px; height: 20px; }
}

/* MARK: Reveals. Each section switches on as it comes into view, like a display: js/site.js adds
   .reveal-ready to the page (not with reduced motion, and not without the script, so then everything
   is simply there) and .revealed to each section once, after timing its words (choreograph()):
   - The Doto line types on letter by letter. In front of it steps a cursor made of Doto's dots
     (0.1em apart, five wide and seven tall, like a letter) in the live colour; it blinks twice at
     the end and goes out.
   - The headline prints in word by word: each word's halftone screen grows in six steps to solid.
   - The words under it come in a line at a time; then the keys switch on, one after another.
   - The pictures come on after the words (see revealProgress()).
   The playground's words do the same each time it opens. */

@property --dot { syntax: "<length>"; inherits: false; initial-value: 0px; }
@keyframes type-on { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes caret-move { from { opacity: 1; transform: translateX(0); } to { opacity: 1; transform: translateX(calc(var(--letters, 1) * 0.6em)); } }
@keyframes caret-blink { 0%, 49.9% { opacity: 1; } 50%, 100% { opacity: 0; } }
@keyframes print-in { from { --dot: 0px; } to { --dot: 5.2px; } }
@keyframes line-in { from { opacity: 0; transform: translateY(0.35em); } to { opacity: 1; transform: none; } }
@keyframes switch-on { from { opacity: 0; } to { opacity: 1; } }
@keyframes rows-on { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes rows-off { from { clip-path: inset(0 0 0 0); } to { clip-path: inset(0 0 100% 0); } }
/* Holds something hidden until its turn. Words that print in through the halftone mask are hidden by
   this too, not by the mask alone: an iPhone was seen showing the hero's headline from the start while
   the name was still typing, so on a browser that doesn't draw the mask they now just appear on time. */
@keyframes appear { from { visibility: hidden; } to { visibility: visible; } }

/* The Doto line and its cursor. */
.eyebrow { position: relative; }
.eyebrow .type { display: inline-block; }
.eyebrow .caret {
  position: absolute; left: 0; top: 0.2em;
  width: 0.5em; height: 0.7em;
  /* Doto's dots, every 0.1em. Drawn at about two thirds of that across, which is how the letters'
     dots actually render at these sizes, so the cursor reads as one more letter. */
  background: radial-gradient(circle, var(--live) 0.031em, transparent 0.038em) 0 0 / 0.1em 0.1em;
  opacity: 0;
  pointer-events: none;
}
.reveal-ready .eyebrow .type { clip-path: inset(0 100% 0 0); }
.reveal-ready :is(.revealed, .play.opened) .eyebrow .type {
  clip-path: none;
  animation: type-on var(--type-dur, 400ms) steps(var(--letters, 12)) var(--type-delay, 0ms) backwards;
}
.reveal-ready :is(.revealed, .play.opened) .eyebrow .caret {
  animation: caret-move var(--type-dur, 400ms) steps(var(--letters, 12)) var(--type-delay, 0ms) both,
             caret-blink 260ms linear calc(var(--type-delay, 0ms) + var(--type-dur, 400ms)) 2 both;
}

/* The hero's opening: the name types out in the middle of the hero at twice its size, then moves up
   into its place over the headline and settles to its own size (js/site.js: introHero()). */
@keyframes settle { from { transform: translateY(var(--intro-dy, 0px)) scale(var(--intro-scale, 2)); } to { transform: none; } }
.reveal-ready .hero .eyebrow { transform: translateY(var(--intro-dy, 0px)) scale(var(--intro-scale, 2)); }
.reveal-ready .hero.revealed .eyebrow { animation: settle 720ms cubic-bezier(0.65, 0, 0.2, 1) var(--settle-delay, 1400ms) both; }

/* Headlines, word by word. A mask clips to its box and these faces' descenders hang below their
   tight line height, so each word's box has room underneath, given back by its margin. */
:is(.display, .wordmark) .w { display: inline-block; padding-bottom: 0.22em; margin-bottom: -0.22em; }
/* The dot's soft edge sits inside its radius, so at zero there's truly nothing, not a sub-pixel speck;
   it grows past the cell's corners (4.24px out) so the letters end solid. */
.reveal-ready :is(.display, .wordmark) .w, .reveal-ready :is(.glyph-name, .song) {
  -webkit-mask: radial-gradient(circle, #000 calc(var(--dot) - 0.6px), #0000 var(--dot)) 0 0 / 6px 6px repeat;
  mask: radial-gradient(circle, #000 calc(var(--dot) - 0.6px), #0000 var(--dot)) 0 0 / 6px 6px repeat;
}
.reveal-ready :is(.revealed, .play.opened) :is(.display, .wordmark) .w {
  animation: print-in 360ms steps(6) calc(var(--head-delay, 160ms) + var(--w, 0) * 70ms) both,
             appear 1ms linear calc(var(--head-delay, 160ms) + var(--w, 0) * 70ms) both;
}
.glyph-name { padding-bottom: 0.22em; }
.reveal-ready .revealed .glyph-name { animation: print-in 640ms steps(6) var(--head-delay, 160ms) both, appear 1ms linear var(--head-delay, 160ms) both; }
.reveal-ready .play.opened .song { animation: print-in 480ms steps(6) 460ms both, appear 1ms linear 460ms both; }

/* The words under the headline, a line at a time. */
:is(.lede, .body) .wl { display: inline-block; }
.reveal-ready :is(.lede, .body) .wl { opacity: 0; }
.reveal-ready :is(.revealed, .play.opened) :is(.lede, .body) .wl {
  opacity: 1;
  animation: line-in 460ms cubic-bezier(0.2, 0.7, 0.2, 1) calc(var(--body-delay, 400ms) + var(--l, 0) * 80ms) backwards;
}

/* Until a section comes in, its words are hidden whole. The spans that animate them only exist once the
   page script has run, and a phone paints the page before then (index.html hides from the first paint). */
.reveal-ready :is(.hero, .feature, .get):not(.revealed) :is(.eyebrow, .display, .wordmark, .lede, .body) { visibility: hidden; }

/* Then the rest: keys switch on in turn, small print and pictures fade in after the words. */
.reveal-ready :is(.hero, .feature, .get):not(.revealed) :is(.hero-keys, .fine, .spec, .glyph-plate, .glyph-blurb, #glyph-try, .get .key) { opacity: 0; }
.reveal-ready :is(.hero, .feature, .get).revealed :is(.fine, .spec, .glyph-blurb, #glyph-try) { transition: opacity 500ms ease var(--keys-delay, 600ms); }
.reveal-ready .feature.revealed .glyph-plate { transition: opacity 300ms ease var(--body-delay, 400ms); }
.hero-keys .key:nth-child(2) { --k: 1; }
.hero-keys .key:nth-child(3) { --k: 2; }
.reveal-ready :is(.hero, .get).revealed :is(.hero-keys .key, .get > .key) {
  animation: card-in 300ms cubic-bezier(0.2, 0.7, 0.2, 1) calc(var(--keys-delay, 600ms) + var(--k, 0) * 70ms) backwards;
}
.reveal-ready .feature:not(.revealed) :is(.glyph-key, .scene-key) { opacity: 0; }
/* Each pulse's name comes in under its ring, one after another, as the rings light. */
.reveal-ready .feature:not(.revealed) .pulses figcaption { opacity: 0; }
.reveal-ready .feature.revealed .pulses figcaption { transition: opacity 400ms ease calc(var(--body-delay, 400ms) + 300ms + var(--i, 0) * 70ms); }
.reveal-ready .feature.revealed :is(.glyph-key, .scene-key) { animation: switch-on 1ms linear calc(var(--body-delay, 400ms) + 220ms + var(--i, 0) * 40ms) backwards; }

/* The scenes window switches on top to bottom, a quarter at a time, like a screen warming up. */
.reveal-ready #scenes:not(.revealed) .scene-window { clip-path: inset(0 0 100% 0); }
.reveal-ready #scenes.revealed .scene-window { animation: rows-on 280ms steps(4) var(--body-delay, 420ms) backwards; }

/* MARK: Download */

.get {
  min-height: 100svh;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px;
  padding: 120px var(--gutter);
  text-align: center;
}
.get-icon { width: min(220px, 50vw); height: min(220px, 50vw); color: var(--ink); margin-bottom: 24px; }
/* The name is set in the system face, as in the nav and the app's header. Faculty Glyphic is for
   headlines and song titles, never the name. */
.wordmark { font-family: var(--sans); font-weight: 500; font-size: clamp(56px, 8vw, 112px); line-height: 1; letter-spacing: -0.045em; }
.get .lede { margin-bottom: 12px; }
/* The release under its key: version and size, once there's one to download. */
.download-meta { margin-top: -6px; }
.spec { display: grid; grid-template-columns: auto auto; gap: 0 32px; margin: 56px 0 0; text-align: left; }
.spec dt, .spec dd { margin: 0; padding: 9px 0; border-bottom: 1px solid var(--rule); }
.spec dt { font: 400 11px/1.9 var(--mono); color: var(--dim); }

.foot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 24px; padding: 28px var(--gutter) 40px; font-size: 12px; border-top: 1px solid var(--rule); }
.foot-links { display: flex; gap: 20px; margin-left: auto; }
.foot-links a { text-decoration: none; color: var(--dim); }
.foot-links a:hover { color: var(--ink); }
