:root {
  color-scheme: dark;
  --bg: #000;
  --ink: #fff;
  --dim: rgb(255 255 255 / 0.62);
  --glass: rgb(20 20 20 / 0.55);
  --hit: #ff8400;
  --radius: 18px;
  --gap: 10px; /* the app's WALL_GAP */
}

* { box-sizing: border-box; }
html { background: #0a0a0a; }
html, body { margin: 0; color: var(--ink); }
/* THE HUE GROUND — the app's IntroHueGround, which is itself a port of this very CSS: the artwork,
   cover-fit, turning once round the hue wheel. The app's numbers for this page: a 4 s turn drawn at
   15 fps (so 60 steps), nothing laid over it. Portrait windows get the app's own portrait artwork;
   landscape ones the site's landscape cut of the same piece. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1;
  background: url("/studios-ground.webp") center / cover;
  animation: hue-ground 4s steps(60) infinite;
  will-change: filter;
}
@media (orientation: landscape) { body::before { background-image: url("/image.webp"); } }
@keyframes hue-ground { to { filter: hue-rotate(360deg); } }
/* The app stops the ground while something covers the page. */
body.viewer-open::before { animation-play-state: paused; }
body { font-family: "Asta Sans", system-ui, -apple-system, sans-serif; -webkit-font-smoothing: antialiased; }
body.viewer-open { overflow: hidden; }
button { font: inherit; color: inherit; border: 0; background: none; padding: 0; cursor: pointer; }
a { color: inherit; }

/* ── page frame: the site header floats; the wall scrolls in its own box ── */
/* THE DOCUMENT SCROLLS, not a box inside it (Joseph, 2026-09-21: "dragging down the website on mobile
   should refresh the page"). Pull-to-refresh is the browser's, and it only exists on the document's own
   scroller: the wall used to scroll inside a 100dvh box with `overscroll-behavior: contain`, which is
   precisely the switch that turns it off. The site header is fixed, as on the legal pages. */
/* NOTHING WIDENS THE PAGE (boss, 2026-10-01: "sometimes the right side of the page increases in size").
   The audio glyph sits on its card's top-right corner and overhangs it by ~29 pt; on a right-column card
   that reached 3–17 px past a phone's edge, and the page grew with it — Chrome widened the layout
   viewport (390 → 406) and Safari let the page pan sideways (414 → 425). `clip`, not `hidden`: no
   scroll container, so the document still scrolls (and pulls to refresh); fixed layers are not clipped. */
#root { overflow-x: clip; }

/* ── wall — fixed cards on the app's lattice; the columns and widths come from wallLattice() ── */
/* Clear of the site header above — the mark's height, its own top padding, and air under it — and of
   the glass pills below. The three numbers are the header's: see site-chrome.css. */
.wall { display: grid; gap: 10px; margin: 0 auto; padding: calc(var(--header-top) + var(--header-mark) + var(--header-gap)) 0 84px; justify-content: center; }
.wall-note { grid-column: 1 / -1; text-align: center; color: #8a8a8a; font-size: 14px; padding-top: 40px; }

.card { display: block; text-align: left; }
.card:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.card-box { position: relative; display: block; width: 100%; }
/* Square corners: the app clips the picture and rounds nothing. */
.card-picture { position: absolute; inset: 0; overflow: hidden; background: #111; contain: layout paint; }
.card-wave { position: absolute; z-index: 3; pointer-events: none; }
.card-title {
  display: block; margin-top: 8px; color: #9a9a9a; font-size: 12px; line-height: 15px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── the card's furniture — sizes are inline, from the app's constants (CardFace.tsx) ── */
.feel { position: absolute; inset: 0; overflow: hidden; }
/* A pattern is a square the size of the box's DIAGONAL, turned by the host's angle (FeelPaintRect):
   it covers any box at any angle with nothing to spare. */
.feel-pattern { position: absolute; left: 50%; top: 50%; width: var(--diag); height: var(--diag); background-size: cover; background-position: center; }
.face-label, .face-avatar, .face-pill { position: absolute; z-index: 2; pointer-events: none; }
.face-label { display: flex; align-items: center; overflow: hidden; color: #fff; font-weight: 500; white-space: nowrap; }
.face-label > .feel { z-index: -1; }
.face-handle { position: relative; flex: 0 1 auto; min-width: 0; overflow: hidden; }
.face-suffix { position: relative; flex: none; }
.face-avatar { border-radius: 50%; }
.face-avatar > .feel { border-radius: 50%; }
.face-camera { position: absolute; z-index: 2; pointer-events: none; }
.face-camera > .feel { border-radius: inherit; }
.face-camera-view { position: absolute; overflow: hidden; background: #111; }
.face-camera-rim { position: absolute; border-style: solid; border-color: #fff; box-sizing: border-box; }
.face-rim { position: absolute; border-radius: 50%; background: #fff; }
.face-avatar img { position: absolute; border-radius: 50%; object-fit: cover; background: #222; }
/* The neon: strokes that ADD, cut to the outside of the face, the finished rim turning the hue wheel
   at the card's 15 fps (the duration is set inline from LIVE_NEON.hueCycleMs). */
/* The neon: four strokes on one road, cut to the outside of the face, the finished rim turning the hue
   wheel. NOT `plus-lighter`: the app's Skia `plus` sums light over a DARK ground (the card's picture is
   clipped away under it), where plus ≈ normal; on a bright picture plus-lighter drives orange to white
   and cyan — the "green glow" of 2026-09-21. Normal alpha, and the FIRE ramp turns at LIVE_NEON.hueCycleMs
   through the same 0.213/0.715/0.072 matrix the app's hueMatrix is (CSS Filter Effects `hue-rotate`). */
.face-neon { position: absolute; pointer-events: none; animation: neon-hue 4s steps(60) infinite; }
.face-neon-stroke {
  position: absolute; box-sizing: border-box; border-style: solid; border-color: transparent;
  -webkit-mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask: linear-gradient(#000 0 0) padding-box exclude, linear-gradient(#000 0 0);
}
.face-neon-breathes { animation: neon-breathe 2.4s ease-in-out infinite; }
@keyframes neon-hue { to { filter: hue-rotate(360deg); } }
@keyframes neon-breathe { 50% { opacity: 0.15; } }
.face-pill {
  display: flex; align-items: center; justify-content: center; overflow: hidden;
  box-sizing: border-box; z-index: 3; transform: translateX(-50%);
}
/* The rim sits ABOVE the paint (an inset box-shadow on the pill paints UNDER its children — the FEEL
   layer covered it, which is the "missing white line" of 2026-09-21). A shadow, not a border, so its
   1.5 px is not snapped to 1. */
.face-pill-rim { position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 var(--rim) #fff; z-index: 1; pointer-events: none; }
.face-pill > .feel { z-index: 0; }
.face-eye { position: relative; flex: none; fill: #fff; }
/* The digits are centred by their INK. The app lifts them 2 pt by hand because RN centres the line box
   and this face sits low in its own; a browser that can trim the box to the cap height needs no lift,
   and one that cannot gets the app's. */
.face-count { position: relative; color: #fff; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; transform: translateY(var(--dy)); }
@supports (text-box: trim-both cap alphabetic) { .face-count { text-box: trim-both cap alphabetic; transform: none; } }

/* ── player ─────────────────────────────────────────────────────────────── */
.hls { position: absolute; inset: 0; }
.hls video, .hls-still { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.hls video { opacity: 0; }
.hls-framed video { opacity: 1; }
.card-still { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.hls-still { transition: opacity 0.25s ease; }
.hls-still-out { opacity: 0; pointer-events: none; } /* fades only once a frame is really on screen */

/* ── avatar ring ────────────────────────────────────────────────────────── */
.ring { display: inline-block; border-radius: 50%; padding: 3px; box-shadow: 0 2px 10px rgb(0 0 0 / 0.5); }
.ring img, .ring-empty { display: block; width: 100%; height: 100%; border-radius: 50%; object-fit: cover; background: #222; border: 2px solid #000; }

/* ── viewer — the app's /live/[id]: a cover picture and the dock, on a phone-shaped stage ── */
.viewer { position: fixed; inset: 0; z-index: 20; background: #000; overflow: hidden; display: flex; justify-content: center; touch-action: none; }
.viewer::before {
  content: ""; position: absolute; inset: -40px; background: var(--backdrop) center / cover;
  filter: blur(40px) brightness(0.35); pointer-events: none;
}
.stage { position: relative; overflow: hidden; background: #000; }
.stage-picture { position: absolute; inset: 0; display: block; width: 100%; height: 100%; }
.stage-back { position: absolute; z-index: 6; left: 20px; top: max(10px, env(safe-area-inset-top)); width: 27px; height: 27px; color: #fff; filter: drop-shadow(0 0 3px rgb(0 0 0 / 0.6)); }
.stage-sound {
  position: absolute; z-index: 6; left: 50%; top: max(12px, env(safe-area-inset-top)); transform: translateX(-50%);
  padding: 5px 12px; border-radius: 999px; background: rgb(0 0 0 / 0.45); font-size: 12px; font-weight: 500; pointer-events: none;
}
.stage-ended { position: absolute; inset: 0; display: grid; place-content: center; justify-items: center; gap: 14px; color: #8a8a8a; font-size: 14px; }
.stage-ended button { color: #fff; font-size: 13px; font-weight: 700; }

.dock-scrim, .dock-scrim-foot { position: absolute; left: 0; right: 0; z-index: 1; pointer-events: none; }
.dock-scrim { background: linear-gradient(rgb(0 0 0 / 0), #000); }
.dock-scrim-foot { bottom: 0; background: #000; }
.dock-now {
  position: absolute; z-index: 2; display: block; color: #000; font-weight: 300;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none;
}
.dock-composer { position: absolute; z-index: 3; display: block; text-decoration: none; color: #fff; }
.dock-composer-face { position: absolute; top: 50%; transform: translateY(-50%); border-radius: 50%; background: rgb(255 255 255 / 0.14); }
.dock-composer-text { position: absolute; top: 50%; transform: translateY(-50%); font-weight: 300; white-space: nowrap; }
.dock-glyph { position: absolute; z-index: 3; display: block; }
.dock-glyph img { display: block; width: 100%; height: 100%; object-fit: contain; }
/* The app tints these two white (tintColor); the replay eye keeps its own colours. */
.dock-glyph-tint img { filter: brightness(0) invert(1); }

@media (prefers-reduced-motion: reduce) { .hls-still { transition: none; } .face-neon, .face-neon-breathes, body::before { animation: none; } }
/* The hand-written pages' own rules, verbatim but for being scoped to this page: terms.html's header
   and the home page's pills. Kept apart from studios.css so it is plain which numbers are the boss's. */
/* The header's three lengths, shared with the wall's top padding (studios.css). On a phone the mark
   gets room above it — it sat against the status bar (Joseph, 2026-09-21) — and the notch's inset on
   top of that; everywhere, the first row of studios starts a clear gap below it. */
:root { --header-top: 6px; --header-mark: 78px; --header-gap: 22px; }
@media (max-width: 768px) { :root { --header-top: calc(14px + env(safe-area-inset-top, 0px)); --header-mark: 64px; --header-gap: 20px; } }

.site-header { position: fixed; top: 0; left: 0; width: 100%; z-index: 9999; display: flex; align-items: flex-start; justify-content: center; pointer-events: none; padding-top: var(--header-top); }
.site-header a { pointer-events: auto; display: block; }
.site-header img { height: var(--header-mark); width: auto; display: block; filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2)); }

.bottom-left { position: fixed; bottom: 20px; left: 20px; z-index: 9999; display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.download-row { display: flex; align-items: center; }
/* The hand-written page's glass, verbatim — on the four pills and on the LIVE NOW box (Joseph, 2026-09-22:
   "the glass was fine before with the code I handed over"). */
.glass-pill-br, .glass-pill-sm, .glass-circle, .live-now-glass {
  background: rgba(0, 0, 0, 0.22); border: 1px solid rgba(255, 255, 255, 0.3);
  backdrop-filter: blur(20px) saturate(180%); -webkit-backdrop-filter: blur(20px) saturate(180%);
  border-radius: 999px; color: #fff; outline: none; -webkit-tap-highlight-color: transparent;
}
.glass-pill-br { flex-shrink: 0; width: 128px; height: 34px; margin-right: 8px; display: flex; align-items: center; justify-content: center; gap: 9.24px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); text-decoration: none; cursor: pointer; }
.glass-pill-br img { height: 19.8px; width: auto; display: block; }
.glass-pill-br span, .glass-pill-sm { font-family: "Asta Sans", Arial, Helvetica, sans-serif; font-size: 0.96rem; font-weight: 400; text-transform: lowercase; }
.glass-circle { flex-shrink: 0; width: 34px; height: 34px; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); cursor: pointer; user-select: none; }
.glass-circle img { height: 22px; width: auto; display: block; transition: transform 0.35s ease; }
.glass-circle.open img { transform: rotate(180deg); }
/* THE MENU IS GLASS FROM ITS FIRST FRAME (Joseph, 2026-09-22: "they open and then turn glass after").
   The hand-written page slid the menu open by animating `max-width` under `overflow: hidden` and
   fading the whole menu — and a backdrop-filter whose OWN box is being faded and clipped is composited
   from a snapshot until the transition ends, so the pills appeared flat and turned to glass after.
   Two changes keep the slide and lose the snapshot: the menu's box is clipped with `overflow: clip`
   (a paint-time clip, not a compositing layer like `hidden`), and nothing on the menu itself fades —
   each PILL fades and slides on its own, with its backdrop live from the first frame. The row still
   opens smoothly because `max-width` still animates. */
.chevron-menu { display: flex; align-items: center; gap: 8px; max-width: 0; margin-right: 0; overflow: clip; transition: max-width 0.4s ease, margin-right 0.4s ease; pointer-events: none; }
.chevron-menu.open { max-width: 500px; margin-right: 8px; pointer-events: auto; }
.chevron-menu .glass-pill-sm { opacity: 0; transform: translateX(-6px); transition: opacity 0.3s ease, transform 0.3s ease; }
.chevron-menu.open .glass-pill-sm { opacity: 1; transform: none; }
.glass-pill-sm { flex-shrink: 0; height: 34px; padding: 0 16px; display: inline-flex; align-items: center; justify-content: center; white-space: nowrap; text-decoration: none; cursor: pointer; user-select: none; }

@media (max-width: 768px) {
  .download-row { max-width: calc(100vw - 40px); }
  .glass-pill-br { width: auto; padding: 0 12px; gap: 6px; }
  .glass-pill-br img { height: 15px; }
  .glass-pill-br span { font-size: 0.88rem; }
  .glass-circle img { height: 17px; }
  .chevron-menu { gap: 6px; }
  /* THE THREE LINKS FIT, BY DEFAULT (Joseph, 2026-09-21: "the contact, privacy, terms boxes are getting
     cut off"). The hand-written page capped the open menu at `100vw - 160px` and let it scroll sideways
     with the scrollbar hidden — so on a phone Terms was simply cut, with nothing to say it could be
     dragged. The row has a fixed budget (the window less 20 px each side) and "download" was spending a
     third of it on a word; while the menu is open it folds to its mark, which is the room the links
     needed. Nothing scrolls, nothing is capped. */
  .chevron-menu.open { max-width: 400px; margin-right: 0; }
  /* The three links sit to the RIGHT of the arrow on one row, as the hand-written page had them
     (Joseph, 2026-09-21), and "download" keeps its word. The row's budget on a 390 px phone is the
     window less 20 px each side; download + arrow spend ~150 px, so the links are set a size down
     (0.8rem, 8 px pads, 5 px gaps) — which fits all three on a 375 px phone and still reads as the
     same pills. Nothing scrolls, nothing wraps, nothing is cut. */
  .download-row { flex-wrap: nowrap; }
  /* `order: 1` puts the menu AFTER the arrow in the row; the markup keeps the hand-written order. */
  .chevron-menu { order: 1; gap: 5px; }
  .chevron-menu.open { margin-left: 6px; }
  .glass-pill-sm { height: 30px; padding: 0 8px; font-size: 0.8rem; }
  .glass-pill-br { margin-right: 6px; }
}

/* The narrowest phones (320–360 px): the same row, a little tighter. */
/* The narrowest phones (320 px): the same row, tighter still — download loses only its padding. */
@media (max-width: 360px) {
  .chevron-menu { gap: 3px; }
  .chevron-menu.open { margin-left: 4px; }
  .glass-pill-sm { padding: 0 6px; font-size: 0.7rem; height: 28px; }
  .glass-pill-br { padding: 0 8px; gap: 4px; margin-right: 4px; }
  .glass-circle { width: 30px; height: 30px; }
}

/* A studio opened full screen owns the screen; the site's furniture steps back. */
body.viewer-open .site-header, body.viewer-open .bottom-left, body.viewer-open .live-now { display: none; }

/* "N STUDIO's LIVE NOW": the footer's glass, with the studio avatars' neon round it (SiteChrome.tsx).
   One box, in the tagline's old corner on every width (Joseph, 2026-09-22: "remove 'the cure and the
   cause' box on desktop and mobile and replace it with the live now box"). On a phone it fades while
   the menu is open, as the tagline folded, so the three links have the row. */
.live-now { position: fixed; right: 24px; bottom: 20px; z-index: 9999; display: inline-block; pointer-events: none; transition: opacity 0.3s ease; }
.live-now .face-neon { z-index: 0; }
.live-now-glass { position: relative; z-index: 1; height: 34px; padding: 0 16px; box-sizing: border-box; display: inline-flex; align-items: center; white-space: nowrap;
  font-family: "Asta Sans", Arial, Helvetica, sans-serif; font-size: 0.82rem; font-weight: 500; letter-spacing: 0.02em; }
@media (max-width: 768px) {
  .live-now.hidden { opacity: 0; }
}

/* Read by screen readers and crawlers, drawn for nobody. */
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* What the page shows when its script fails (ErrorBoundary.tsx): the mark, one line, a way back. */
.page-failed { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px; color: #fff; font-family: "Asta Sans", Arial, Helvetica, sans-serif; text-align: center; padding: 24px; }
.page-failed img { height: 64px; width: auto; }
.page-failed a { color: inherit; }
