@font-face {
  font-family: "LT Remark";
  src: url(/fonts/LTRemark-Regular.woff2) format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "LT Remark";
  src: url(/fonts/LTRemark-Italic.woff2) format("woff2");
  font-weight: 400; font-style: italic; font-display: swap;
}

/* The menu's names (Figma: Domaine Display Condensed Regular, Klim). This is Klim's test web font, from
   source/Test Domaine Display Condensed/; swap in the licensed file under the same name when it's bought. */
@font-face {
  font-family: "Domaine Display Condensed";
  src: url(/fonts/DomaineDisplayCondensed-Regular.woff2) format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}

@font-face {
  font-family: "Ogelo";
  src: url(/fonts/Ogelo.woff2) format("woff2");
  font-weight: 100 900; font-style: normal; font-display: block; /* one weight, never synthesised bold (the rail sets 600 for Comfortaa) */
}

/* Registered as colours so the home page can fade them with the painting reel. */
@property --accent { syntax: "<color>"; inherits: true; initial-value: #513b23; }
@property --pill { syntax: "<color>"; inherits: true; initial-value: #ddd2cc; }

/* Sounds of a Sphere (from the Living Paintings AR site). Tokens follow the Sounds of a Sphere website designs (Figma: SOAS-WEBSITE).
   Each page sets --from/--to (background bloom), --accent (display type) and --pill (pill fills) per painting. */
:root {
  --ground: #e5e5e5;
  --ink: #2c3439;
  --body: #433b36;
  --muted: #3f3934; /* darker than the Figma #4d4742: small text stays >= 4.5:1 wherever the moving blobs overlap */
  --accent-text: color-mix(in srgb, var(--accent) 58%, #000);
  --line: #ccd6dd;
  --dot: #e0a37b;
  --glow: #fffbf2;
  --chip: #f4f3f1;
  --from: #ffc984;
  --to: #df6a42;
  --accent: #513b23;
  --pill: #ddd2cc;
  --display: "LT Remark", Georgia, serif;
  --sans: "Plus Jakarta Sans", ui-sans-serif, system-ui, sans-serif;
  --gutter: 20px;
  --max: 1180px;
  --vw: 1vw; /* the window's width / 100 without a scrollbar: vinyl.js sets it in px */
  color-scheme: light;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
/* Lenis smooth scroll (app.js), with its recommended CSS (lenis 1.3.27, dist/lenis.css). Lenis eases anchor jumps itself,
   so the native smooth scrolling is turned off while it runs. */
html.lenis, html.lenis body { height: auto; }
html.lenis { scroll-behavior: auto; }
.lenis:not(.lenis-autoToggle).lenis-stopped { overflow: clip; }
.lenis [data-lenis-prevent], .lenis [data-lenis-prevent-wheel], .lenis [data-lenis-prevent-touch],
.lenis [data-lenis-prevent-vertical], .lenis [data-lenis-prevent-horizontal] { overscroll-behavior: contain; }
.lenis.lenis-smooth iframe { pointer-events: none; }
.lenis.lenis-autoToggle { transition-property: overflow; transition-duration: 1ms; transition-behavior: allow-discrete; }
body {
  margin: 0;
  min-height: 100vh;
  font: 400 16px/1.6 var(--sans);
  color: var(--body);
  background: var(--ground) url(/img/grain.png);
  background-blend-mode: screen;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img { max-width: 100%; display: block; }
picture { display: block; }
button { font: inherit; color: inherit; }
a { color: inherit; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

html[data-lang="en"] [data-l="fr"],
html[data-lang="fr"] [data-l="en"] { display: none !important; }

/* Background bloom: the soft blurred ellipse behind each hero, coloured per culture. */
.bg {
  position: absolute; inset: 0 0 auto; height: 140vh; overflow: hidden; pointer-events: none; z-index: -1;
  /* fade the bloom out inside the box so a wide screen never shows its clipped bottom edge */
  -webkit-mask-image: linear-gradient(#000 62%, transparent); mask-image: linear-gradient(#000 62%, transparent);
}
.bg__bloom {
  position: absolute;
  left: 50%; top: 4vh;
  width: max(150vw, 900px); height: max(95vw, 620px);
  translate: -42% 0;
  rotate: 8deg;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--from) 22%, var(--to) 78%);
  opacity: .5;
  filter: blur(min(100px, 14vw));
  animation: drift 28s ease-in-out infinite alternate;
}
@keyframes drift { to { translate: -36% 4vh; rotate: 4deg; } }
/* With JavaScript: two drifting blobs on a small canvas, stretched over the same area (app.js). Grain on top against banding. */
.bg__canvas { position: absolute; inset: 0; width: 100%; height: 100%; filter: blur(8px); } /* mild: smooths the stretched pixels */
.bg--live .bg__bloom { display: none; }
/* Grain (round 3): a static fine-noise tile (scripts/make-noise.mjs) over the background only: above the blobs and the
   ground, below every piece of content (z-index -1, after .bg). Multiplied in at a low opacity, it darkens the ground and
   the blobs by about the same amount, matching the Figma frames' grain (about 1.3 grey levels of texture). Fixed, with no
   animation, so there's nothing to redraw as the page scrolls. */
.grain {
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: url(/img/noise.png) 0 0 / 128px; mix-blend-mode: multiply; opacity: .0425;
}
@media (prefers-reduced-motion: reduce) { .bg__bloom { animation: none; } html { scroll-behavior: auto; } }

/* Small caps label with the copper dot, as on the Figma "MUSICIANS •" and "MENU •" labels. */
.label {
  display: inline-flex; align-items: center; gap: 8px;
  margin: 0;
  font: 700 10.5px/1.2 var(--sans);
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--accent-text);
}
.dot { width: 6px; height: 6px; border-radius: 50%; background: var(--dot); flex: none; }

/* Header (phones and tablets; desktop has the rail). The menu button floats at the top right (.menu-btn--fab). */
.top {
  position: relative; z-index: 2;
  display: flex; align-items: center; justify-content: space-between;
  max-width: var(--max); margin: 0 auto;
  padding: max(18px, env(safe-area-inset-top)) var(--gutter) 0;
}
.emblem { position: relative; display: block; width: 52px; height: 52px; }
.emblem::before {
  content: ""; position: absolute; inset: -10px; border-radius: 50%;
  background: radial-gradient(circle, var(--glow) 0 30%, transparent 70%);
  opacity: .45;
  filter: blur(5px);
}
.emblem img { position: relative; width: 100%; height: 100%; animation: spin 60s linear infinite; }
@keyframes spin { to { rotate: 360deg; } }
@media (prefers-reduced-motion: reduce) { .emblem img { animation: none; } }

/* "sounds of a sphere" wordmark (outlined from Ogelo): horizontal in the footer, vertical in the desktop rail. */
.wordmark {
  display: block; width: 168px; height: 22.4px;
  background: var(--accent);
  -webkit-mask: url(/img/wordmark.svg) no-repeat center / contain; mask: url(/img/wordmark.svg) no-repeat center / contain;
}
.wordmark--v { width: 28px; height: 210px; -webkit-mask-image: url(/img/wordmark-vertical.svg); mask-image: url(/img/wordmark-vertical.svg); }
.rail, .hero__summary .hero__lede, .scroll-hint { display: none; }

main { max-width: var(--max); margin: 0 auto; padding: 0 var(--gutter); }

/* Hero */
.hero { display: grid; grid-template-columns: minmax(0, 1fr); gap: 22px; padding: 28px 0 8px; }
.hero__text { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
.display {
  margin: 0;
  /* Sized so the longest word (--chars) always fits on one line. */
  --chars: 9;
  font: 400 clamp(2.4rem, min(15vw, 172vw / var(--chars)), 8.5rem)/.9 var(--display);
  letter-spacing: -.035em;
  color: var(--accent);
  text-shadow: 1px 1px 0 rgba(0, 0, 0, .04); /* the owner's Figma drop shadow: X 1, Y 1, no blur, #000 at 4% */
  text-wrap: balance;
  position: relative;
  /* No max-width: the box is never narrower than its longest word; the font sizes keep the words inside the page. */
  width: fit-content;
}
/* Progressive blur on the last word (see BLUR_STEPS in templates.mjs): every copy shows through its own mask (--m) and
   blurs by --b; the copies after the first add up with plus-lighter inside an isolated group, so their weights sum to one.
   Each copy gets padding cancelled by negative margins, so the masks (and the blur) reach past the text box: without it
   the blurred right end, descenders (g, p, J) and the macron in "Pūoro" were cut off at the box edge. */
.display__end { position: relative; display: inline-block; isolation: isolate; --ramp: min(2.4em, 62%); }
.display__l {
  display: inline-block;
  padding: .25em .25em .3em .12em; margin: -.25em -.25em -.3em -.12em;
  -webkit-mask-image: var(--m); mask-image: var(--m);
}
.display__l + .display__l { position: absolute; left: 0; top: 0; filter: blur(var(--b)); mix-blend-mode: plus-lighter; pointer-events: none; }
.hero__by { margin: 2px 0 0; font: 500 15px/1.4 var(--sans); color: var(--ink); }
.amp { font-family: var(--display); font-size: 1.15em; color: var(--accent-text); }
.hero__intro { margin: 6px 0 0; max-width: 46ch; color: var(--body); }

.hero__art { position: relative; margin: 0; }
.hero__art > picture > img {
  width: 100%; height: auto; border-radius: 4px;
  box-shadow: 1px 1px 18px rgba(0, 0, 0, .05), 0 24px 48px -28px rgba(0, 0, 0, .35);
}
/* Home: the four paintings cross-fading in one 16:9 frame (app.js), with the painting pages' radius and shadow. */
.hero--home { transition: --accent 1.6s ease-in-out; }
/* The header and rail (wordmark, language pill, social icons) follow the reel too, while it's on screen (app.js). */
.page--home :is(.top, .rail) { --accent-text: color-mix(in srgb, var(--accent) 58%, #000); transition: --accent 1.6s ease-in-out, --pill 1.6s ease-in-out; }
.hero__reel { display: grid; aspect-ratio: 16 / 9; border-radius: 4px; box-shadow: 1px 1px 18px rgba(0, 0, 0, .05), 0 24px 48px -28px rgba(0, 0, 0, .35); }
.hero__reel picture { grid-area: 1 / 1; min-width: 0; min-height: 0; opacity: 0; transition: opacity 1.6s ease-in-out; }
.hero__reel picture.is-on { opacity: 1; z-index: 1; }
/* The outgoing painting stays opaque under the incoming one, so the frame never turns see-through mid-fade. */
.hero__reel picture.is-prev { opacity: 1; transition: none; }
.hero__reel img { width: 100%; height: 100%; object-fit: cover; object-position: var(--pos, 50% 50%); border-radius: 4px; }
.hero__art.hero__reel > picture > img { height: 100%; box-shadow: none; aspect-ratio: auto; }
/* Home (Figma frame "3"): the painting reel fills the first screen (so, on the vinyl screen, the whole sleeve), with the
   black sticker in its top right corner. Phones: the reel full width at the top, the sticker as a card under it. */
.sleeve-reel { position: relative; display: grid; margin: 0 calc(-1 * var(--gutter)); aspect-ratio: 16 / 9; overflow: hidden; background: #0b0d0d; }
.sleeve-reel picture { grid-area: 1 / 1; min-width: 0; min-height: 0; opacity: 0; transition: opacity 1.6s ease-in-out; }
.sleeve-reel picture.is-on { opacity: 1; z-index: 1; }
.sleeve-reel picture.is-prev { opacity: 1; transition: none; }
.sleeve-reel img, .sleeve-reel .preview { width: 100%; height: 100%; object-fit: cover; object-position: var(--pos, 50% 50%); border-radius: 0; }
.sleeve-reel img { scale: 1.03; } /* the stills carry a thin dark frame from the print; the clips (cut clean) don't */
.sticker {
  font: 400 15px/1.25 var(--sans); color: #fff; margin-top: 16px; padding: 26px 24px 28px;
  border-radius: 9px; background: linear-gradient(225deg, #161111 18%, #000 96%);
}
.sticker__title { margin: 0; font: 400 2.25em/1 "Domaine Display Condensed", var(--display); white-space: nowrap; } /* two lines, as set */
.sticker__text { margin: .95em 0 0; }
.hero--sleeve { padding-bottom: 0; }

/* Preview loops (app.js): a short silent clip over the still, faded in once it's really playing. The still underneath is
   the fallback whenever a clip can't play. Same crop rules as the still, so the swap doesn't shift a pixel. */
picture.has-preview { position: relative; display: block; }
.preview {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: var(--pos, 50% 50%);
  border-radius: 4px; opacity: 0; transition: opacity .6s ease-out; pointer-events: none;
}
.preview.is-playing { opacity: 1; }

/* Country map and "Music from / India". Everything is in em of the country name, so the map keeps the
   Figma proportions to the text at every size. Phones: hangs off the painting's bottom-right corner. */
.origin {
  --mh: 0em; --mw: 0em;
  position: absolute; right: 0; top: 100%; width: 0; height: 0;
  font-size: 13px; color: #3f3934;
  text-shadow: 0 0 .5em rgba(255, 255, 255, .85), 0 0 .15em rgba(255, 255, 255, .9);
}
.origin--india { --mh: 11.7em; --mw: 10.8em; }
.origin--aotearoa { --mh: 12.9em; --mw: 8.8em; }
.origin--bali { --mh: 8em; --mw: 12.9em; } /* landscape island: about the same visual weight as the other maps */
.hero__art:has(.origin--india) { margin-bottom: calc(11.7 * 13px - 2.4 * 13px); }
.hero__art:has(.origin--aotearoa) { margin-bottom: calc(12.9 * 13px - 2.4 * 13px); }
.hero__art:has(.origin--bali) { margin-bottom: calc(8 * 13px - 2.4 * 13px); }
.hero__art:has(.origin:not(:has(.origin__map))) { margin-bottom: 4.2em; }
.origin__map { position: absolute; right: .6em; top: -2.4em; width: auto; height: var(--mh); max-width: none; }
.origin__text {
  position: absolute; right: calc(.6em + var(--mw) * .42); top: calc(-2.4em + var(--mh) * .4);
  display: grid; text-align: right; white-space: nowrap;
  font: italic 800 1em/1.25 var(--sans); letter-spacing: .03em; text-transform: uppercase;
}
.origin:not(:has(.origin__map)) .origin__text { right: 0; top: 1em; }
.origin__pre { font-size: .69em; font-weight: 500; }

.hero__actions { display: grid; justify-items: start; gap: 10px; padding-top: 6px; }
/* Phones: the button comes straight after the title, before the painting. Below the painting (and the map hanging off
   it) it sat at 520-675 px, under the fold of a 375x548 browser on three of the four paintings (round 2 review). */
@media (max-width: 719px) { .hero__actions { order: 1; padding-top: 0; } .hero__art { order: 2; } }

/* Primary action: the notched pill with a disc, after the Figma "LATEST LP" control. The pill and the disc are both inside
   the button, so the whole control is one hit target with one focus ring (round 2: the disc used to ignore clicks). */
.cta-wrap { position: relative; display: inline-block; max-width: 100%; }
/* Every dimension follows the button height --ch (60 px; fluid on desktop), so the control scales as one piece. */
:root { --ch: 60px; }
.cta {
  position: relative; isolation: isolate; display: inline-flex; align-items: center; gap: calc(.2 * var(--ch));
  min-height: var(--ch); padding: 0 calc(1.2667 * var(--ch)) 0 calc(.5667 * var(--ch)); max-width: 100%; /* the label wraps rather than overflowing a narrow phone */
  border: 0; border-radius: calc(var(--ch) / 2); background: none; cursor: pointer; text-align: left;
  font: 800 12.5px/1.25 var(--sans); letter-spacing: .05em; text-transform: uppercase; color: var(--ink); text-decoration: none;
}
/* The pill ends half a disc short of the button's right edge, under the middle of the disc; its shadow sits on a wrapper
   because the notch mask would clip a shadow drawn on the pill itself. */
.cta__pill { position: absolute; inset: 0 calc(var(--ch) / 2) 0 0; z-index: -1; filter: drop-shadow(0 calc(var(--ch) / 6) calc(var(--ch) * .3667) rgba(0, 0, 0, .1)); }
.cta__pill::before {
  content: ""; position: absolute; inset: 0;
  border-radius: calc(var(--ch) / 2) 0 0 calc(var(--ch) / 2);
  background: var(--chip);
  /* Notch concentric with the disc (centred on the pill's right edge), leaving an even 2 px ring at every size (round 2:
     halved from 4 px). The last 1 px of the radius is the anti-aliased edge. */
  -webkit-mask: radial-gradient(circle calc(var(--ch) / 2 + 2px) at 100% 50%, transparent calc(100% - 1px), #000 100%);
  mask: radial-gradient(circle calc(var(--ch) / 2 + 2px) at 100% 50%, transparent calc(100% - 1px), #000 100%);
}
.cta__label { min-width: 0; text-wrap: balance; }
.cta__disc {
  position: absolute; right: 0; top: 50%; translate: 0 -50%;
  display: grid; place-items: center;
  width: var(--ch); height: var(--ch); border-radius: 50%;
  background: repeating-radial-gradient(circle, #f2f2f2 0 1px, #e4e4e4 1.5px 3px);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .7), 0 6px 16px rgba(0, 0, 0, .12);
  transition: rotate .8s cubic-bezier(.2, .7, .2, 1);
}
.cta__disc svg { width: calc(.3667 * var(--ch)); height: calc(.3667 * var(--ch)); padding: calc(.1 * var(--ch)); border-radius: 50%; background: var(--accent); color: #fff; box-sizing: content-box; translate: 0 0; }
.cta:hover .cta__disc { rotate: 120deg; }
@media (max-width: 370px) { .cta { font-size: 11.5px; letter-spacing: .03em; padding-left: 22px; } }
.watch-link {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 4px;
  font: 600 14px/1.2 var(--sans); color: var(--ink); text-decoration: none;
  border-bottom: 1px solid var(--line); padding-bottom: 3px;
  position: relative;
}
.watch-link::before { content: ""; position: absolute; inset: -14px -8px -10px; }
.watch-link svg { width: 16px; height: 16px; }

/* Watch: YouTube, loaded only when asked for */
.watch { padding: 40px 0 8px; scroll-margin-top: 16px; max-width: 920px; }
/* No dark box, shadow or vignette (round 2): the still covers the 16:9 box, so there are no letterbox bars on the light
   paintings, and the label keeps its own dark backing for contrast. */
.yt { position: relative; aspect-ratio: 16 / 9; overflow: hidden; border-radius: 4px; background: transparent; }
.yt picture { position: absolute; inset: 0; display: block; }
.yt img { width: 100%; height: 100%; object-fit: cover; object-position: var(--pos, 50% 50%); }
.yt iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; background: #000; }
.yt__play { position: absolute; inset: 0; display: block; margin: 0; padding: 0; border: 0; background: none; cursor: pointer; color: #fff; }
/* The disc and its label, centred as one group on the box's centre (round 3); app.js moves the group with the pointer
   through `transform`, so it never fights the centring `translate`. */
.yt__group {
  position: absolute; left: 0; right: 0; top: 50%; translate: 0 -50%; padding: 0 16px;
  display: flex; flex-direction: column; align-items: center; gap: 12px; text-align: center;
  pointer-events: none;
}
.yt__disc { display: grid; place-items: center; width: 64px; height: 64px; border-radius: 50%; background: var(--accent); box-shadow: 0 4px 14px rgba(0, 0, 0, .18); transition: scale .2s; }
.yt__disc svg { width: 26px; height: 26px; translate: .5px 0; }
.yt__play:hover .yt__disc { scale: 1.06; }
.yt__label { padding: 7px 12px 6px; border-radius: 14px; background: rgba(20, 17, 15, .66); font: 700 12px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; text-shadow: 0 1px 3px rgba(0, 0, 0, .8), 0 0 14px rgba(0, 0, 0, .7); }
.watch__note { display: flex; align-items: center; gap: 8px; margin: 12px 0 0; font-size: 13px; color: var(--muted); }
.watch__note svg { width: 18px; height: 18px; }

/* Read: tabs */
.read { padding: 36px 0 8px; }
.tabs { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 26px; }
.tab {
  display: inline-flex; align-items: center; gap: 8px;
  height: 40px; padding: 0 18px;
  border: .5px solid var(--line); border-radius: 24px; background: transparent; cursor: pointer;
  font: 700 11px/1 var(--sans); letter-spacing: .04em; text-transform: uppercase; color: var(--muted);
  position: relative;
}
.tab::before { content: ""; position: absolute; inset: -4px -2px; }
.tab .dot { opacity: 0; }
.tab[aria-selected="true"] { background: var(--pill); color: var(--accent-text); box-shadow: inset 0 0 0 3px rgba(255, 255, 255, .35); }
.tab[aria-selected="true"] .dot { opacity: 1; }

.prose { max-width: 64ch; }
.prose p { margin: 0 0 1em; }
.honours { font-size: 14px; color: var(--muted); font-style: italic; }
.panel > .label { margin-top: 18px; }
.credits { display: grid; gap: 12px; margin: 14px 0 0; max-width: 64ch; }
.credits div { display: grid; grid-template-columns: minmax(120px, 38%) 1fr; gap: 16px; padding-top: 12px; border-top: .5px solid var(--line); }
.credits dt { font-size: 13px; color: var(--muted); }
.credits dd { margin: 0; font-weight: 600; color: var(--ink); }

.artist { padding: 4px 0 26px; }
.artist + .artist { border-top: .5px solid var(--line); padding-top: 26px; }
.artist__name { margin: 8px 0 12px; font: 400 clamp(2rem, 8vw, 2.8rem)/1 var(--display); letter-spacing: -.02em; color: var(--accent); }

.verses { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 28px; max-width: 64ch; }
.verse { display: grid; gap: 10px; padding-top: 22px; border-top: .5px solid var(--line); }
.verse p { margin: 0; }
.verse__sa { font: italic 500 15px/1.6 var(--sans); color: var(--accent); }
.verse__tr { color: var(--body); }

/* Other works */
.more { padding: 48px 0 24px; }
.more--home { padding-top: 24px; }
.cards { list-style: none; margin: 18px 0 0; padding: 0; display: grid; gap: 28px; }
.card a { display: grid; gap: 14px; text-decoration: none; }
.card img { width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 4px; box-shadow: 0 18px 36px -26px rgba(0, 0, 0, .45); transition: translate .3s; }
.card a:hover img { translate: 0 -3px; }
.card__text { display: grid; gap: 6px; }
.card__title { font: 400 2rem/1 var(--display); letter-spacing: -.02em; color: var(--accent); }
.card__by { font-size: 14px; color: var(--ink); }

/* Footer */
.foot {
  max-width: var(--max); margin: 32px auto 0;
  padding: 32px var(--gutter) max(32px, env(safe-area-inset-bottom));
  border-top: .5px solid var(--line);
  display: grid; gap: 14px; justify-items: start;
}
.foot__about { margin: 0; max-width: 52ch; font-size: 14px; }

@media (min-width: 720px) {
  :root { --gutter: 40px; }
  .cards { grid-template-columns: repeat(2, 1fr); }
  .more--home .cards { grid-template-columns: repeat(2, 1fr); }
}
/* Tablet (720-1023 px, round 2 item 12): title across the top; the button, its note and "Or watch it here" under it on
   the left, with the map and "Music from …" on the right (its foot just over the still's top edge); then the still. The
   figure is display: contents so its picture and caption can sit in different grid cells. */
@media (min-width: 720px) and (max-width: 1023px) {
  .hero {
    grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "text text" "actions origin" "art art";
    column-gap: 24px; row-gap: 0; align-items: end; padding-top: 40px;
  }
  .hero__text { grid-area: text; }
  .hero__actions { grid-area: actions; align-self: start; padding: 30px 0 36px; }
  .hero__art:not(.hero__reel) { display: contents; }
  .hero__art:not(.hero__reel) > picture, .hero__reel { grid-area: art; }
  .origin {
    grid-area: origin; position: relative; z-index: 1; top: auto; right: auto;
    width: calc(var(--mw) + .6em); height: var(--mh); margin-bottom: calc(-.14 * var(--mh)); /* overlaps the still's top edge */
    font-size: 15px;
  }
  .origin__map { top: 0; }
  .origin__text { top: calc(var(--mh) * .4); }
  .origin:not(:has(.origin__map)) { height: auto; margin-bottom: 1em; }
  .origin:not(:has(.origin__map)) .origin__text { position: static; }
}
@media (min-width: 960px) {
  .cards { grid-template-columns: repeat(3, 1fr); }
  .more--home .cards { grid-template-columns: repeat(2, 1fr); gap: 40px; }
  .read { display: grid; grid-template-columns: minmax(0, 1fr); max-width: 760px; }
}

/* ───────────── Desktop (>= 1024 px): fixed left rail and the hero laid out on the Figma frame ─────────────
   Hero positions come from the 1922x1028 Figma frame, in vw, so the hero keeps the Figma proportions at every width.
   Type and control sizes are fluid too (round 2): clamp(min, rem + vw, max). The preferred value passes through the
   Figma size at 1922 px (title 159.5 px = 8.3vw, summary 16.4 px = 0.85vw, ...) and is mostly vw, with a little rem so a
   reader's font-size setting still counts. Maximums are the 2560 px equivalents. Minimums are the 1280 px equivalents,
   except for small text, where that would drop below a legible size: labels, the summary, the button and the map label
   stop at today's sizes (10.5 / 15 / 12.5 / 13 px) instead. */
@media (min-width: 1024px) {
  :root {
    --rail: max(10vw, 130px); --col-l: 16.7vw; --col-r: 16.8vw;
    --t-title: clamp(6.6rem, 1rem + 7.47vw, 13.3rem);     /* 159.5 px at 1922; 106 at 1280, 207 at 2560 */
    --t-label: clamp(.656rem, .06rem + .48vw, .85rem);     /* labels and pills: 10.5 px up to ~2000, 13.3 at 2560 */
    --t-by: clamp(.9375rem, .12rem + .89vw, 1.55rem);      /* performers: 19 px at 1922 */
    --t-body: clamp(.9375rem, .1rem + .77vw, 1.35rem);     /* summary: 16.4 px at 1922 */
    --t-prose: clamp(1rem, .1rem + .77vw, 1.35rem);        /* reading text below the hero: never under 16 px */
    --t-cta: clamp(.78rem, .08rem + .585vw, 1.02rem);      /* button label: 12.5 px at 1922; the whole button scales with it */
    --t-note: clamp(.8125rem, .08rem + .61vw, 1.06rem);    /* "Best with headphones" */
    --t-origin: clamp(.8125rem, .1rem + .75vw, 1.3rem);    /* "Music from India" (the map is sized in em of it) */
    --t-mark: clamp(1.375rem, .16rem + 1.22vw, 2.15rem);   /* rail wordmark, Latin: 26.13 px at 1922 */
    --ch: calc(4.8 * var(--t-cta));                        /* button height: 60 px at 1922, 78 at 2560 */
  }
  body { font-size: var(--t-prose); }
  .label { font-size: var(--t-label); }
  .hero {
    /* Longest word (--chars) longer than 9 letters: the title shrinks so it still fits. */
    --fs: calc(var(--t-title) * min(1, 9 / var(--chars, 9)));
    --cta-top: 37.15vw; --lede-top: max(44.4vw, var(--cta-top) + 2.07 * var(--ch));
  }

  /* Rail: emblem at the top, wordmark in the middle, social icons at the bottom. Nothing else ever sits under it. */
  .top, .menu-btn--fab { display: none; }
  .rail { display: block; position: fixed; z-index: 30; inset: 0 auto 0 0; width: var(--rail); pointer-events: none; }
  .rail__in { position: absolute; inset: 0; }
  .rail__in > * { pointer-events: auto; }
  .rail__emblem { position: absolute; left: 50%; top: clamp(2.5rem, .3rem + 2.65vw, 4.6rem); translate: -50% 0; width: clamp(3.5rem, .4rem + 3.7vw, 6.5rem); height: auto; aspect-ratio: 1; }
  /* Figma (2186:2): the menu button at 45% of the height (app.css "Vinyl"), the wordmark centred at about 66%. */
  .rail__mark { position: absolute; left: 50%; top: 65.6%; translate: -50% -50%; display: grid; place-items: center; }
  .wordmark--v { width: calc(1.077 * var(--t-mark)); height: calc(8.08 * var(--t-mark)); background: currentColor; }
  /* Typed wordmark (app.js): centred on the outlined one, which keeps its place but is hidden. */
  .rail__mark--typed .wordmark--v { visibility: hidden; }
  .typed {
    position: absolute; left: 50%; top: 50%; translate: -50% -50%; rotate: -90deg;
    white-space: nowrap; font: 600 var(--t-mark)/1 "Ogelo", "Comfortaa", var(--sans); color: inherit;
  }
  /* Sizes from the owner's Figma, relative to the Latin 26.13 px; weights matched to Ogelo's stems (see app.js). */
  .typed:lang(el) { font-weight: 700; font-size: calc(.7654 * var(--t-mark)); -webkit-text-stroke: .016em currentColor; } /* 20 px; Comfortaa 700 is still a little light at this size */
  .typed:lang(hi) { font-weight: 600; font-size: calc(.842 * var(--t-mark)); line-height: 1.5; } /* 22 px */
  .typed--up {
    rotate: none; writing-mode: vertical-rl; text-orientation: upright; font-weight: 500;
    font-size: calc(.9567 * var(--t-mark)); /* 25 px */
    letter-spacing: .6em; padding-inline-start: .6em; /* even space before the first character and after the last */
  }
  .typed__off { visibility: hidden; }
  .rail__social { list-style: none; margin: 0; padding: 0; position: absolute; left: 50%; bottom: clamp(3.5rem, .3rem + 3.65vw, 6.2rem); translate: -50% 0; display: grid; gap: clamp(1rem, .06rem + .78vw, 1.3rem); }
  .rail__social a { --i: clamp(1.25rem, .08rem + .97vw, 1.65rem); position: relative; display: block; width: var(--i); height: var(--i); color: inherit; transition: color .3s ease; }
  @media (hover: hover) { .rail__social a:hover { color: var(--menu-ink); } } /* the menu's yellow, as on the button */
  .rail__social a::before { content: ""; position: absolute; inset: min(-12px, (44px - var(--i)) / -2); } /* 44px hit area at least */
  .rail__social svg { display: block; width: 100%; height: 100%; }

  main { max-width: none; padding: 0; }
  .watch, .read, .more { margin-left: var(--col-l); margin-right: var(--col-r); }
  .watch { max-width: none; padding-top: max(40px, 2.6vw); }
  .watch__note { font-size: var(--t-note); }
  .read { max-width: calc(47.5 * var(--t-prose)); }
  .tab { font-size: calc(1.048 * var(--t-label)); height: 3.64em; padding: 0 1.64em; border-radius: 2.2em; }
  .card__title { font-size: calc(.2 * var(--t-title)); }
  .card__by { font-size: calc(.88 * var(--t-prose)); }
  .foot { max-width: none; margin: max(48px, 3.1vw) var(--col-r) 0 var(--col-l); padding-left: 0; padding-right: 0; }

  /* Hero: every block sits in the same grid cell, placed by its margins, so the hero still grows if text runs long. */
  .hero, .hero--home {
    display: grid; grid-template: minmax(max(53.5vw, 92svh), auto) / minmax(0, 1fr); grid-template-areas: none;
    gap: 0; padding: 0; align-items: start; justify-items: start;
  }
  /* Below about 1366 px the map would reach the language pill: move the whole hero down a little. */
  .hero { padding-top: max(0px, (1366px - 100vw) * .16); }
  .hero > *, .hero__actions > *, .hero--home .hero__text > * { grid-area: 1 / 1; }
  .hero__actions { display: contents; }

  /* Title over the still's bottom-left corner on every painting, as "Manjit & / Jaita Singh" in the Figma (round 2). */
  .hero__text { margin: 23.9vw 0 0 var(--col-l); gap: 0; z-index: 2; }
  .display { font-size: var(--fs); line-height: .86; margin-top: calc(2.7vw - 1.2 * var(--t-label) + .02em); }
  .display__w { display: block; }
  .display__w + .display__w { padding-left: .45em; }
  /* Dark corner (per-work whiteTitle, and the home reel): the first line, which lies over the still, is white. */
  .hero--white .display__w:first-child { color: #fff; }
  .hero__by { margin-top: .5vw; font-size: var(--t-by); }
  .hero--2 .hero__by { padding-left: calc(.45 * var(--fs)); } /* two-line title: under the second line, which is indented */

  .hero__art { margin: 8.8vw 0 0 37.7vw !important; width: 45.5vw; z-index: 1; }
  .hero__art > picture > img { aspect-ratio: 16 / 9; object-fit: cover; object-position: var(--pos, 50% 50%); }
  /* Map over the thumbnail's top-right corner, the label to its right. */
  .origin { top: 0; font-size: var(--t-origin); }
  .origin__map { right: auto; left: -3.6em; top: -3.8em; }
  .origin--aotearoa .origin__map { left: -6.4em; top: -3.75em; }
  .origin--bali .origin__map { left: -7em; top: -4.4em; }
  .origin__text, .origin:not(:has(.origin__map)) .origin__text { right: auto; left: 1em; top: 2.05em; text-align: left; }
  .origin--aotearoa .origin__text { top: 3.4em; }

  .cta { font-size: var(--t-cta); }
  .cta-wrap { justify-self: end; margin: var(--cta-top) var(--col-r) 0 0; z-index: 2; }
  .hero__actions >   .hero__summary { margin: var(--lede-top) 0 0 56vw; width: 27.2vw; display: grid; gap: calc(1.1 * var(--t-body)); justify-items: start; }
  .hero__summary .hero__lede { display: block; margin: 0; text-wrap: pretty; font-size: var(--t-body); line-height: 1.55; color: var(--body); }
  .watch-link { font-size: calc(.93 * var(--t-body)); }

  /* Home: the cross-fading painting in the thumbnail's place, the intro where a painting's summary goes. */
  .hero--home .hero__text { display: contents; }
  .hero--home .hero__text > .label { margin: 23.9vw 0 0 var(--col-l); z-index: 2; }
  .hero--home .display { margin: calc(26.6vw + .02em) 0 0 var(--col-l); z-index: 2; }
  .hero--home .hero__intro { margin: var(--lede-top) 0 0 56vw; width: 27.2vw; text-wrap: pretty; max-width: none; font-size: var(--t-body); line-height: 1.55; }
  .more--home { padding-top: max(56px, 3.6vw); }

  /* Narrower screens: the title stops shrinking at its 1280 px size, so a two-line title reaches across to the button,
     which then waits for it (measured: Nirvana Shatakam up to 1160 px, the home page's longer button up to 1240 px). */
  @media (max-width: 1179px) { .hero--2 { --cta-top: max(37.15vw, 26.6vw + 1.7 * var(--fs) + 16px); } }
  @media (max-width: 1279px) { .hero--home { --cta-top: max(37.15vw, 26.6vw + 1.7 * var(--fs) + 16px); } } /* longer label */

  /* Scroll hint (Figma, bottom right): a 1 px track ending exactly at the bottom of the first screen, whatever the
     window's height (the hero starts at the top of the page; svh is the visible height with a tablet's toolbars showing),
     with a darker segment sliding down it. Decorative; it fades once the page has scrolled (app.js). */
  .hero { position: relative; }
  .scroll-hint {
    display: block; position: absolute; z-index: 2; pointer-events: none;
    grid-area: auto; /* measured from the top of the hero, not from its grid cell (which starts below its padding) */
    left: 93.4vw; top: calc(100svh - clamp(7.5rem, .5rem + 8.3vw, 14rem));
    width: 1px; height: clamp(7.5rem, .5rem + 8.3vw, 14rem); /* 168 px at 1922 */
    overflow: hidden; background: color-mix(in srgb, var(--accent) 30%, transparent);
    transition: opacity .5s;
  }
  .scroll-hint::after {
    content: ""; position: absolute; inset: 0 0 50%; background: var(--accent);
    animation: scroll-hint 2.8s cubic-bezier(.45, 0, .55, 1) infinite;
  }
  @keyframes scroll-hint { from { translate: 0 -100%; } to { translate: 0 200%; } }
  .scrolled .scroll-hint { opacity: 0; }
  .scrolled .scroll-hint::after { animation-play-state: paused; }
  @media (prefers-reduced-motion: reduce) { .scroll-hint::after { animation: none; } }

  /* Home: the reel is the whole first screen (a touch wider, so no sliver of ground shows at the sleeve's edge); the sticker
     is placed and sized so it lands where the Figma has it on the vinyl screen (269 x 276 px, 48 px in from the sleeve's
     right edge and 58 px down at 1922 x 1028), in em of its 16 px text. */
  .hero.hero--sleeve { display: block; position: relative; height: 100vh; padding: 0; }
  .hero--sleeve .sleeve-reel { position: absolute; inset: 0 -1vw; margin: 0; aspect-ratio: auto; }
  .sticker {
    position: absolute; z-index: 2; top: 7.6vh; right: 2.9vw; margin: 0;
    width: 16.8em; padding: 2.3em 2.7em 3.2em 1.95em; border-radius: .55em;
    font-size: clamp(14px, .89vw, 22px); /* 17 px at 1922: 16 px on the shrunk vinyl screen */
  }

  /* 404: just the title, then the paintings. */
  .hero--404 { grid-template-rows: auto; }
  .hero--404 .hero__text { display: grid; margin: 10vw 0 4vw var(--col-l); }
  .hero--404 .hero__text > * { grid-area: auto; }
  .hero--404 .hero__text > .label { margin: 0; }
  .hero--404 .display { margin: calc(2.7vw - 1.2 * var(--t-label) + .02em) 0 0; }
}

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

/* ───────────────────────── Menu button ─────────────────────────
   The Figma "MENU •" pill (92 x 36 at 1922): in the rail on desktop, floating at the top right on phones. Open, it turns
   into the yellow "CLOSE •" of the inner sleeve. Both labels share one grid cell, so the pill keeps its width. */
.menu-btn {
  position: relative; display: inline-grid; grid-template-columns: auto auto; align-items: center; column-gap: .86em;
  height: 3.43em; padding: 0 1.33em 0 1.9em; border: 0; border-radius: 999px; cursor: pointer;
  font: 700 10.5px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase;
  background-color: var(--pill); color: var(--accent-text);
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .28), 0 1px 2px rgba(0, 0, 0, .05);
  transition: background-color .35s, color .35s;
}
.menu-btn::before { content: ""; position: absolute; inset: -5px -2px; } /* 44px hit area at least */
/* Hover: the menu's yellow fades in over the pill (a layer of its own, so it never fights the pill's colour change when
   the page leaves the vinyl state), the label and dot turning the sleeve's blue. */
.menu-btn { isolation: isolate; }
.menu-btn::after { content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit; background: var(--menu-ink); opacity: 0; transition: opacity .3s ease; }
.menu-btn__l { transition: color .3s ease; }
@media (hover: hover) {
  .menu-btn:hover::after { opacity: 1; }
  .menu-btn:hover .menu-btn__l { color: var(--sleeve-ink); }
  .menu-btn:hover .dot { background: var(--sleeve-ink); }
}
.menu-btn__l { grid-area: 1 / 1; }
.menu-btn .dot { grid-area: 1 / 2; transition: background-color .35s; }
.menu-btn__l--close, .menu-btn[aria-expanded="true"] .menu-btn__l--menu { visibility: hidden; }
.menu-btn[aria-expanded="true"] .menu-btn__l--close { visibility: visible; }
.menu-btn[aria-expanded="true"] { background-color: var(--menu-ink) !important; color: var(--sleeve-ink) !important; }
.menu-btn[aria-expanded="true"] .dot { background: var(--sleeve-ink); }
.menu-btn:focus-visible { outline-color: var(--sleeve-ink); }
.menu-btn--fab {
  position: fixed; z-index: 40; right: var(--gutter); top: calc(max(18px, env(safe-area-inset-top)) + 8px);
  background-color: color-mix(in srgb, var(--pill) 88%, transparent);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .28), 0 4px 14px rgba(0, 0, 0, .08);
}

/* ───────────────────────── The menu: a printed inner sleeve ─────────────────────────
   Teal paper with gold foil along its top and bottom edges, yellow type, and Gus's outline (the Figma "Frame 290",
   2201:105). Phones and tablets: the sheet slides in from the right and fills the screen. Desktop: see "Vinyl" below.
   --open (0 closed, 1 open) is set on the elements marked data-open by app.js while the sleeve moves. */
@property --open { syntax: "<number>"; inherits: false; initial-value: 0; }
/* Yellow and blue from the Figma (#ffd04f, #f5c747 for the lead-ins, #2574a9). The blue for small text on the yellow and
   pale-blue pills is a shade darker than the Figma's, which only reaches 3.4:1 there; this one passes AA (4.5:1). */
:root { --menu-ink: #ffd04f; --menu-ink-2: #f5c747; --sleeve-ink: #1f5f8b; --teal: #3a7181; }
.sleeve {
  position: fixed; z-index: 20; inset: 0; visibility: hidden;
  color: var(--menu-ink); font-family: var(--sans);
  transform: translateX(calc((1 - var(--open)) * 100%));
  box-shadow: -18px 0 50px -10px rgba(0, 0, 0, .35);
}
html.menu-open .sleeve, html.menu-moving .sleeve { visibility: visible; }
.sleeve a { color: inherit; text-decoration: none; }
.sleeve :focus-visible { outline: 1.5px solid var(--menu-ink); outline-offset: 6px; border-radius: 2px; }
/* Paper: the owner's export of the inner sleeve's printed stock (source/COlour-bg 1.png: the damask photo with #e0a37b
   laid over it in exclusion), as a near-lossless JPEG (q1, 4:4:4; it keeps the faint birds and fish far better than WebP);
   a 1000 px copy on phones. Covers the sleeve from its top-left corner; fetched once the page has loaded. */
.sleeve__paper { position: absolute; inset: 0; overflow: hidden; background: var(--teal) 0 0 / cover no-repeat; }
html.menu-ready .sleeve__paper { background-image: url(/img/paper-phone.jpg); }
@media (min-width: 1024px) { html.menu-ready .sleeve__paper { background-image: url(/img/paper.jpg); } }
/* Gold foil along the top and bottom edges: the Figma's photographed strip ("Untitled-1", gold-edge.jpg), the bottom one
   flipped, with the Figma's faint white catch-light along the top one ("Rectangle 147"). */
.sleeve__paper::before, .sleeve__paper::after {
  content: ""; position: absolute; left: 0; right: 0; height: max(8px, 1.4vh);
  background: #d9b06c 0 0 / 100% 100%;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .16);
}
html.menu-ready .sleeve__paper::before { background-image: linear-gradient(rgba(255, 255, 255, .15), transparent 10px), url(/img/gold-edge.jpg); background-size: 100% 100%; }
html.menu-ready .sleeve__paper::after { background-image: url(/img/gold-edge.jpg); }
.sleeve__paper::before { top: 0; }
.sleeve__paper::after { bottom: 0; transform: scaleY(-1); }
.sleeve__in { position: absolute; inset: 0; }
.sleeve :where(ul) { list-style: none; margin: 0; padding: 0; }
/* "Tour the / GALLERY": a small lead-in over the condensed serif name. The highlighted item gets the running sine line. */
.mi { display: grid; justify-items: start; gap: .15em; width: max-content; cursor: pointer; }
.mi__pre { display: flex; justify-self: stretch; align-items: baseline; gap: 3px; margin-left: 1.25em; font-weight: 400; color: var(--menu-ink-2); }
.mi__name { font: 400 1em/.86 "Domaine Display Condensed", "LT Remark", Georgia, serif; text-transform: uppercase; letter-spacing: .005em; }
/* The sine runs from the lead-in to the right-hand end of the name under it. */
.sine { position: relative; display: block; flex: 1 1 0; min-width: 24px; height: 8px; margin-bottom: -4px; overflow: hidden; opacity: 0; transition: opacity .25s;
  -webkit-mask: linear-gradient(90deg, transparent, #000 6px, #000 calc(100% - 6px), transparent); mask: linear-gradient(90deg, transparent, #000 6px, #000 calc(100% - 6px), transparent); }
.sine svg { position: absolute; left: 0; top: 0; animation: sine .9s linear infinite paused; } /* out of the flow, so its length never widens the item */
@keyframes sine { to { transform: translateX(-12px); } }
.mi:is(:hover, :focus-visible) .sine { opacity: 1; }
.mi:is(:hover, :focus-visible) .sine svg { animation-play-state: running; }
.sleeve__small { display: grid; gap: .9em; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; }
.sleeve__social { display: flex; gap: 1.2em; }
.sleeve__social a { position: relative; display: block; width: 1.4em; height: 1.4em; }
.sleeve__social a::before { content: ""; position: absolute; inset: -12px; }
.sleeve__social svg { display: block; width: 100%; height: 100%; }
.sleeve__social li:last-child a { width: 1.95em; height: 1.95em; margin: -.285em 0; } /* YouTube: 25 px wide, as in the Figma */
.sleeve__art { position: absolute; pointer-events: none; }
/* Teja (source/teja outline.png, baked onto black like Gus) takes Gus's place while ARTISTS is highlighted (app.js):
   the same height, standing on the gold edge, right-aligned in the same space. */
.teja { position: absolute; right: 0; bottom: 0; height: 100%; width: auto; mix-blend-mode: screen; opacity: 0; transition: opacity .35s ease; }
.sleeve[data-art="teja"] .teja { opacity: 1; }
.sleeve[data-art="teja"] .gus { opacity: 0; }
/* Gus: the Figma's "Gus outline masked": white line art on black from the original drawing (lossless, native resolution),
   cut by the Figma's mask (a diagonal up the left edge), screened over the paper at 63%, as in the Figma. */
.gus { display: block; width: 100%; height: auto; mix-blend-mode: screen; opacity: .63; transition: opacity .35s ease; }
.collage { display: none; }

/* Phones and tablets: full screen, the list from the top, Gus faint in the bottom right corner. */
@media (max-width: 1023px) {
  .sleeve__in { display: flex; flex-direction: column; padding: calc(max(18px, env(safe-area-inset-top)) + 92px) 28px calc(max(18px, env(safe-area-inset-bottom)) + 28px); overflow-y: auto; font-size: 13px; }
  .sleeve__list { position: relative; z-index: 1; display: grid; gap: clamp(18px, 3.4vh, 34px); }
  .mi__name { font-size: clamp(3rem, 15vw, 5rem); }
  .sleeve__small { position: relative; z-index: 1; margin-top: auto; padding-top: 36px; }
  .sleeve__social { position: relative; z-index: 1; margin-top: 26px; font-size: 14px; }
  .sleeve__art { right: -14%; bottom: max(8px, 1.4vh); height: 66%; aspect-ratio: 766 / 1493; }
  .gus { opacity: .38; } /* on the image, not the box: an opacity group would stop the screen blend and show Gus's black */
}

/* ───────────────────────── Vinyl (desktop) ─────────────────────────
   The home page's first screen is a record sleeve photographed on a plain ground (Figma "Frame 288", 2186:104): the page
   is shrunk into the sleeve (.sheet, scaled about the middle of the screen), the ground around it (.stage__mat) is cut
   away where the sleeve is, and a soft studio shadow (.stage__shadow) falls to the lower right. The obi, a strip of
   card, wraps round the sleeve's left side and carries the rail. Scrolling past 20% of the screen (app.js) sets
   html.is-flat: --flat eases from 0 to 1, the sleeve opens out to the whole window and the obi fades away, its emblem,
   button, wordmark and icons gliding to their places in the plain rail.

   Widths are in --vw, a hundredth of the window's width without its scrollbar (vinyl.js): CSS's vw includes a visible
   scrollbar, which put the hole in the ground ~15 px wider than the sleeve on screens that show one.

   Smoothness: every moving piece is its own GPU layer that only moves, scales or fades (transform, opacity), so nothing
   is repainted while it moves. The ground round the sleeve is one large fixed shape with the sleeve-shaped hole in it,
   and the menu is clipped by a strip (.sleeve-clip) that travels with the page's right edge.

   Scale: a record jacket is 314 mm square and about 3 mm thick with the record inside (1.2-2 mm of vinyl plus the board);
   the obi is paper, about 0.3 mm. With the sleeve about 930 px tall that's roughly 3 px a millimetre, so the jacket's
   top face shows as a ~4 px band where the obi wraps over it, and the obi's own edge and contact shadow are ~1 px.

   The menu (the inner sleeve) slides out from under the sleeve's right edge while the sleeve moves left to make room
   (Figma "Frame 290", 2201:105); the obi stays put. Once flat it does the same: the page moves left and the inner sleeve,
   the same size as on the vinyl screen, comes out from under its right edge, 4 px inside the window on three sides, as
   a sheet slightly smaller than the page it comes out of. */
@property --flat { syntax: "<number>"; inherits: false; initial-value: 1; }
@media (min-width: 1024px) {
  :root {
    --sl: calc(2.97 * var(--vw)); --sr: calc(3.44 * var(--vw)); --st: 4.67vh; --sb: 4.18vh; /* the sleeve inside the window (57, 66, 48, 43 px at 1922 x 1028) */
    --s0: .936;                                            /* the page's scale inside the sleeve: its width over the window's */
    --pull: calc(46.2 * var(--vw));                                        /* how far the sleeve moves left when the menu comes out */
    --obi-l: calc(4.58 * var(--vw)); --obi-w: calc(7.96 * var(--vw));                      /* the obi: 88 px in, 153 px wide */
    --sleeve-r: 3.5px; --obi-r: 2px; /* barely rounded corners: board and card are never truly sharp; the thinner obi a touch less */
    --slide: cubic-bezier(.65, 0, .35, 1);
  }
  .sheet, .stage > i, .obi, .rail__in { --flat: 1; transition: --flat 1.05s var(--slide); }
  html.still :is(.sheet, .stage > i, .obi, .rail__in) { transition: none; }

  .sheet {
    transform-origin: 50% 50vh;
    transform: translateX(calc(var(--open) * -1 * ((1 - var(--flat)) * var(--pull) + var(--flat) * (calc(46.71 * var(--vw)) + 4px)))) scale(calc(var(--s0) + (1 - var(--s0)) * var(--flat)));
  }
  /* Always its own layer on desktop; vinyl.js drops the hint for a frame after the page changes size, so it's redrawn
     sharp at its new scale. */
  .sheet { will-change: transform; }
  html.settle .sheet { will-change: auto; }

  /* The ground, the shadow and the sleeve's edge all sit at the sleeve's place in the window and move as one: left with
     the page when the menu comes out (--tx), and scaled about the sleeve's middle just past the window's edges once flat
     (the hole is 93.59 --vw x 91.15vh: 1.08 x 1.11 clears the window). */
  .stage { position: fixed; inset: 0; z-index: 10; pointer-events: none; }
  .stage > i {
    position: absolute; display: block; will-change: transform, opacity;
    /* flat: by the menu's width, plus the 0.304 --vw the 1.08 scale pushes the hole's right edge past the window's */
    --tx: calc(-1 * var(--open) * ((1 - var(--flat)) * var(--pull) + var(--flat) * (calc(47.014 * var(--vw)) + 4px)));
    transform: translateX(var(--tx)) scale(calc(1 + .08 * var(--flat)), calc(1 + .11 * var(--flat)));
  }
  /* The plain ground, lit softly from the upper left: one shape reaching well past the window on the right (it moves left
     with the page), with the sleeve cut out of it. Its own coordinates start calc(3 * var(--vw)) left of and 3vh above the window.
     The hole (--hx, --hy, --hw, --hh) has rounded corners: two crossing rectangles and a disc in each corner, added
     together and subtracted from the whole. */
  .stage__mat {
    --hx: calc(calc(3 * var(--vw)) + var(--sl)); --hy: calc(3vh + var(--st));
    --hw: calc(calc(100 * var(--vw)) - var(--sl) - var(--sr)); --hh: calc(100vh - var(--st) - var(--sb));
    --r: var(--sleeve-r); --d: calc(2 * var(--r));
    --sq: linear-gradient(#000 0 0); --disc: radial-gradient(closest-side, #000 calc(100% - 1px), transparent);
    left: calc(-3 * var(--vw)); right: calc(-50 * var(--vw)); top: -3vh; bottom: -3vh;
    transform-origin: calc(calc(3 * var(--vw)) + var(--sl) + (calc(100 * var(--vw)) - var(--sl) - var(--sr)) / 2) calc(3vh + var(--st) + (100vh - var(--st) - var(--sb)) / 2);
    background: radial-gradient(calc(90 * var(--vw)) 70vh at calc(25 * var(--vw)) 3vh, rgba(255, 255, 255, .38), transparent 70%), linear-gradient(#e4e4e3 3vh, #d7d7d6 58vh, #cdcdcc 103vh);
    -webkit-mask:
      var(--sq) 0 0 / 100% 100%,
      var(--sq) var(--hx) calc(var(--hy) + var(--r)) / var(--hw) calc(var(--hh) - var(--d)),
      var(--sq) calc(var(--hx) + var(--r)) var(--hy) / calc(var(--hw) - var(--d)) var(--hh),
      var(--disc) var(--hx) var(--hy) / var(--d) var(--d),
      var(--disc) calc(var(--hx) + var(--hw) - var(--d)) var(--hy) / var(--d) var(--d),
      var(--disc) var(--hx) calc(var(--hy) + var(--hh) - var(--d)) / var(--d) var(--d),
      var(--disc) calc(var(--hx) + var(--hw) - var(--d)) calc(var(--hy) + var(--hh) - var(--d)) / var(--d) var(--d);
    -webkit-mask-repeat: no-repeat;
    -webkit-mask-composite: source-out, source-over, source-over, source-over, source-over, source-over, source-over;
    mask:
      var(--sq) 0 0 / 100% 100% subtract,
      var(--sq) var(--hx) calc(var(--hy) + var(--r)) / var(--hw) calc(var(--hh) - var(--d)) add,
      var(--sq) calc(var(--hx) + var(--r)) var(--hy) / calc(var(--hw) - var(--d)) var(--hh) add,
      var(--disc) var(--hx) var(--hy) / var(--d) var(--d) add,
      var(--disc) calc(var(--hx) + var(--hw) - var(--d)) var(--hy) / var(--d) var(--d) add,
      var(--disc) var(--hx) calc(var(--hy) + var(--hh) - var(--d)) / var(--d) var(--d) add,
      var(--disc) calc(var(--hx) + var(--hw) - var(--d)) calc(var(--hy) + var(--hh) - var(--d)) / var(--d) var(--d);
    mask-repeat: no-repeat;
  }
  .stage__mat::after { content: ""; position: absolute; inset: 0; background: url(/img/noise.png) 0 0 / 128px; mix-blend-mode: multiply; opacity: .0425; }
  /* Diffused studio light: a hairline contact shadow, the jacket's right side catching a little shade, then wide soft
     falloff down and to the right. Flat, it shows only while the page is moved aside for the menu. */
  .stage__shadow, .stage__edge { left: var(--sl); right: var(--sr); top: var(--st); bottom: var(--sb); border-radius: var(--sleeve-r); }
  .stage__shadow {
    opacity: calc(1 - var(--flat) * (1 - var(--open)));
    /* Soft and warm (a brown-grey, not black), lightest right at the edge and widening away from it, as under a big
       diffused light: the sleeve sits on the surface rather than floating over it. */
    box-shadow:
      0 1px 2px rgba(60, 48, 36, .06),
      1px 3px 8px rgba(60, 48, 36, .06),
      4px 12px 28px -4px rgba(60, 48, 36, .08),
      12px 34px 80px -14px rgba(60, 48, 36, .1),
      24px 64px 150px -32px rgba(60, 48, 36, .1);
  }
  /* The sleeve's own edge: light along the top and left, where it faces the light. */
  .stage__edge {
    opacity: calc(1 - var(--flat));
    box-shadow: inset 1px 1px 0 rgba(255, 255, 255, .85), inset 5px 0 10px -6px rgba(255, 255, 255, .7), inset -1px -1px 0 rgba(0, 0, 0, .05), inset 0 0 70px rgba(0, 0, 0, .025);
  }

  /* The obi: card wrapped round the sleeve. A lit band where it folds over the jacket's top face, its bottom edge just
     below the sleeve's, and a short, tight shadow, as paper lying on board throws. */
  .obi {
    display: block; position: absolute; left: var(--obi-l); width: var(--obi-w);
    top: calc(var(--st) - .45vh); bottom: calc(var(--sb) - .45vh); border-radius: var(--obi-r); /* overhangs the sleeve by the same at both ends */
    /* Fades away in the first 60% of the change, scaled with the sleeve (the ground's scale, about the sleeve's middle), so
       it stays wrapped round the sleeve's edge as the sleeve grows; that carries it left with the rail's contents too. */
    opacity: calc(1 - 1.6 * var(--flat)); will-change: transform, opacity;
    transform-origin: calc(var(--sl) + (calc(100 * var(--vw)) - var(--sl) - var(--sr)) / 2 - var(--obi-l)) calc((100vh - var(--st) - var(--sb)) / 2 + .45vh);
    transform: scale(calc(1 + .08 * var(--flat)), calc(1 + .11 * var(--flat)));
    background: linear-gradient(90deg, #dfe0e1, #e7e8e8 22%, #e6e7e7 80%, #e2e3e3);
    /* The card's turn on the right is barely shaded; its shadow on the sleeve is soft and light, as thin paper throws. */
    box-shadow:
      inset 1px 0 0 rgba(255, 255, 255, .8), inset -1px 0 0 rgba(0, 0, 0, .02),
      1px 0 0 rgba(0, 0, 0, .035),
      1px 1px 3px rgba(60, 48, 36, .06),
      4px 5px 12px -3px rgba(60, 48, 36, .07),
      10px 14px 32px -12px rgba(60, 48, 36, .08);
  }
  /* The fold over the jacket's top face: light catching the curve, fading softly into the front, then a breath of shade
     where the card turns down. No hard edge, so it reads as one piece of card bending, not a separate band. */
  .obi::before {
    content: ""; position: absolute; left: 0; right: 0; top: 0; height: calc(.45vh + 16px);
    background: linear-gradient(rgba(255, 255, 255, .9), rgba(255, 255, 255, .6) 2px, rgba(255, 255, 255, .28) 6px, rgba(255, 255, 255, 0) 45%, rgba(0, 0, 0, .025) 70%, rgba(0, 0, 0, 0));
    border-radius: var(--obi-r) var(--obi-r) 0 0;
  }
  .obi::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: .45vh;
    background: linear-gradient(transparent, rgba(0, 0, 0, .045));
    border-bottom: 1px solid rgba(0, 0, 0, .19); border-radius: 0 0 var(--obi-r) var(--obi-r);
  }
  /* The rail rides on the obi (centred on it, a little lower) and glides to its own place as the obi fades. Its colours
     follow the painting on show in both states, like the play button (app.js tints .rail with the reel). */
  .rail__in {
    --vin: calc(1 - var(--flat)); will-change: transform;
    transform: translateX(calc(var(--vin) * (var(--obi-l) + var(--obi-w) / 2 - var(--rail) / 2)));
    color: var(--accent);
  }
  .rail__emblem { translate: -50% calc(var(--vin) * 3.6vh); }
  .rail__social { translate: -50% calc(var(--vin) * -.6vh); }
  .rail__in > * { will-change: transform, translate; } /* each on its own layer: they slide without repainting their neighbours */
  .menu-btn--fab { display: none; }
  .menu-btn--rail {
    position: absolute; left: 50%; top: 45.3%; translate: -50% -50%; font-size: calc(.933 * var(--t-label)); /* 9.8 px at 1922 */
  }

  /* The inner sleeve, the same in both states: the page moves left by --ps and the sleeve, 46.71 --vw wide, comes out from
     under the page's right edge (--pe at rest). The strip .sleeve-clip starts at that edge and moves with it, hiding
     whatever of the sleeve is still under the page; the sleeve starts tucked a quarter of the screen to the left. Flat
     (and on every page but the home page): 4 px inside the window, the page's edge meeting it exactly (on the vinyl screen
     the sleeve's edge overlaps it by 10 px, as in the Figma). */
  .sleeve-clip {
    --pl: calc(calc(100 * var(--vw)) - 4px - calc(46.71 * var(--vw))); --pt: 4px; --pb: 4px; --pe: calc(100 * var(--vw)); --ps: calc(calc(46.71 * var(--vw)) + 4px); /* flat: the page's edge meets the menu exactly */
    --pgb: 100%; /* the page's bottom edge, within the strip */
    position: fixed; z-index: 20; top: 0; bottom: 0; left: var(--pe); width: calc(60 * var(--vw)); pointer-events: none;
    transform: translateX(calc(-1 * var(--open) * var(--ps))); will-change: transform;
    /* Hides what's still under the page, but lets the sleeve's shadow run on a little to the left below the page's bottom
       edge, so the two shadows meet without a cut. Static, so it costs nothing while the strip moves. */
    clip-path: polygon(0 0, 100% 0, 100% 100%, calc(-5 * var(--vw)) 100%, calc(-5 * var(--vw)) var(--pgb), 0 var(--pgb));
  }
  .sleeve {
    --u: calc((100vh - var(--st) - var(--sb)) / 100); /* a hundredth of the vinyl sleeve's height: the print's unit in both states */
    position: absolute; inset: var(--pt) auto var(--pb) calc(var(--pl) - var(--pe)); width: calc(46.71 * var(--vw));
    border-radius: 0 5px 5px 0; overflow: hidden; pointer-events: auto; will-change: transform;
    transform: translateX(calc((var(--open) - 1) * calc(26 * var(--vw)) + var(--open) * var(--ps)));
    box-shadow: 2px 3px 6px -2px rgba(0, 0, 0, .14), 4px 8px 14px -4px rgba(0, 0, 0, .14), 18px 38px 64px -14px rgba(0, 0, 0, .22); /* soft only: a hard 1 px line read as a separate edge */
  }
  /* The page's right edge throws a soft shadow onto the inner sleeve (the Figma's 20 px "Rectangle 146", a touch deeper),
     kept at that edge as the sleeve slides under it. */
  .sleeve__shade {
    display: block; position: absolute; top: 0; bottom: 0; left: 0; width: calc(1.6 * var(--vw)); pointer-events: none; will-change: transform;
    transform: translateX(calc(var(--pe) - var(--pl) - (var(--open) - 1) * calc(26 * var(--vw)) - var(--open) * var(--ps)));
    background: linear-gradient(90deg, rgba(0, 0, 0, .16), rgba(0, 0, 0, .05) 45%, transparent);
  }
  /* Its print: designed on the vinyl sleeve (898 x 932 at 1922 x 1028), sized in --u so it's the same size in both states,
     centred top to bottom (--top0 is where the design's top edge falls); Gus stands on the bottom gold edge. */
  .sleeve__in { --top0: calc(50% - 50 * var(--u)); left: max(0px, (100% - 96.4 * var(--u)) / 2); right: auto; width: min(100%, 96.4 * var(--u)); font-size: max(11px, 1.395 * var(--u)); } /* 13 px */
  .sleeve__list { position: absolute; left: 8.9%; top: calc(var(--top0) + 20.8 * var(--u)); display: grid; grid-auto-rows: calc(12.18 * var(--u)); }
  .mi { gap: 0; }
  .mi__name { font-size: calc(6.44 * var(--u)); } /* 60 px */
  .sleeve__small { position: absolute; left: 9.8%; top: calc(var(--top0) + 74.1 * var(--u)); gap: calc(2.3 * var(--u)); }
  .sleeve__social { position: absolute; left: 10.1%; top: calc(var(--top0) + 87.6 * var(--u)); gap: 1.6em; }
  .sleeve__art { left: 43.4%; bottom: calc(max(8px, 1.4vh) + 1px); height: calc(87.45 * var(--u)); aspect-ratio: 766 / 1493; } /* 418 x 815 at 1922 */
  .gus { height: 100%; width: auto; }
  /* Gallery: the four paintings as tilted prints over the sleeve (app.js shows them while GALLERY is highlighted). Each one
     comes to the top under the pointer, grows a little, turns back towards level and plays its preview loop. */
  .collage { display: block; position: absolute; left: -2.44%; top: 7.66%; width: 108.1%; height: 69.9%; opacity: 0; visibility: hidden; transition: opacity .3s ease, visibility 0s .3s; }
  .show-gallery .collage { opacity: 1; visibility: visible; transition: opacity .3s ease, visibility 0s; pointer-events: auto; }
  .show-gallery .gus { opacity: 0; }
  .collage__card {
    position: absolute; left: var(--x); top: var(--y); width: var(--w); aspect-ratio: var(--ar); z-index: var(--z);
    translate: -50% -50%; rotate: var(--r); scale: .97;
    transition: scale .25s ease-out, rotate .25s ease-out;
  }
  .show-gallery .collage__card { scale: 1; }
  .collage__card:hover { z-index: 9; scale: 1.05; rotate: var(--r2); }
  .collage__card a { display: block; height: 100%; border-radius: 2px; overflow: hidden; box-shadow: 0 1px 2px rgba(0, 0, 0, .25), 0 14px 30px -10px rgba(0, 0, 0, .5); transition: box-shadow .25s; }
  .collage__card:hover a { box-shadow: 0 2px 4px rgba(0, 0, 0, .22), 0 22px 44px -12px rgba(0, 0, 0, .55); }
  .collage__card picture, .collage__card img { width: 100%; height: 100%; object-fit: cover; }
  .collage__card .preview { border-radius: 0; }
}
/* The vinyl state itself: home page, desktop, a window tall enough for the obi's contents, before the first scroll. */
@media (min-width: 1024px) and (min-height: 560px) {
  html.js.vinyl:not(.is-flat) :is(.sheet, .stage > i, .obi, .rail__in) { --flat: 0; }
  /* On the vinyl screen: level with the sleeve, out from under its right edge (the sleeve moves left by --pull). */
  /* The inner sleeve 2 px inside the sleeve top and bottom, so it looks as if it fits inside. */
  html.js.vinyl:not(.is-flat) .sleeve-clip { --pgb: calc(100% - var(--sb)); --pl: calc(49.85 * var(--vw)); --pt: calc(var(--st) + 2px); --pb: calc(var(--sb) + 2px); --pe: calc(calc(100 * var(--vw)) - var(--sr)); --ps: var(--pull); }
}
@media (max-width: 1023px) { .sleeve__shade { display: none; } .sleeve-clip { display: contents; } }

@media (prefers-reduced-motion: reduce) {
  .sheet, .stage > i, .obi, .rail__in { transition: none !important; }
  .sine svg { animation: none; }
  .collage__card, .collage__card a { transition: none; }
}
