/* ─────────────────────────────────────────────────────────────
   PYC ગરબા Juke Box - chrome for a cloth.

   The canvas paints undyed cotton, so this is all dark marks on a
   light field: madder and lamp-black on ivory, gold hairlines, no
   glass and no blur. Panels are cloth patches pinned to the cloth,
   which is why they have a hairline rule and a shadow rather than a
   translucent fill.
   ───────────────────────────────────────────────────────────── */

@font-face {
  font-family: 'Kumar One';
  src: url('/assets/fonts/kumar-one-latin.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
/* One family for the whole interface, both scripts. Anek Gujarati carries a
   full Latin set, so nothing here falls back to a system stack. */
@font-face {
  font-family: 'Anek';
  src: url('/assets/fonts/anek-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
/* Latin only. Bold Gujarati would have cost another 58 KB for one label, and
   the active genre is already carried by its ring. */
@font-face {
  font-family: 'Anek';
  src: url('/assets/fonts/anek-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-024F, U+2000-206F, U+2190-21FF, U+2713, U+2715;
}

:root {
  /* Six dyes. Same set the canvas uses - see themes.js. */
  --madder: #9e2b25;
  --madder-deep: #7a1d1e;
  --indigo: #1b3a5c;
  --turmeric: #e2a32d;
  --gold: #c9a227;
  --malachite: #1f6b4f;
  --ivory: #f2e8d5;
  --ivory-warm: #eaddc4;
  --lamp: #14100e;

  --ink: var(--lamp);
  --ink-dim: rgba(20, 16, 14, 0.62);
  --ink-faint: rgba(20, 16, 14, 0.42);
  --patch: #f6efdf;
  --rule: rgba(122, 29, 30, 0.28);
  --rule-soft: rgba(20, 16, 14, 0.14);
  --shadow: 0 1px 0 rgba(255, 255, 255, 0.5), 0 10px 30px -14px rgba(60, 30, 12, 0.5);

  --beat-off: rgba(20, 16, 14, 0.18);
  --gu: 'Anek', 'Noto Sans Gujarati', sans-serif;
  --sys: 'Anek', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --dock-max: 700px;
  /* Overwritten by scene.js on every resize with the painted border's
     inner edge. The fallback covers the first paint. */
  --frame: 46px;
  /* Also written by scene.js on resize - the bottom chrome's real height. */
  --bottom-h: 118px;
}

/* ── Sanedo: bandhani ─────────────────────────────────────────
   The cloth goes dark, so the chrome inverts with it. Zari takes over the
   job madder does on the pichhwai - it is the ink of this cloth. Every
   component reads these tokens, so nothing below needs a second rule. */
:root[data-theme='sanedo'] {
  --madder: #d4a93c;
  --madder-deep: #e8ae33;
  --patch: #3a0d16;
  --ivory: #7a1522;
  --ivory-warm: #5e0f1a;
  --ink: #f5e7cf;
  --ink-dim: rgba(245, 231, 207, 0.68);
  --ink-faint: rgba(245, 231, 207, 0.46);
  --rule: rgba(212, 169, 60, 0.5);
  --rule-soft: rgba(245, 231, 207, 0.16);
  --shadow: 0 1px 0 rgba(255, 255, 255, 0.06), 0 12px 34px -14px rgba(0, 0, 0, 0.75);
  --beat-off: rgba(245, 231, 207, 0.24);
}

/* ── Filmi: abla and zari ─────────────────────────────────────
   A lit set rather than a cloth, so the chrome is zari on a deep jewel
   ground. Same tokens as everywhere else - the components never learn
   which theme is on. */
:root[data-theme='filmi'] {
  --madder: #e8b948;
  --madder-deep: #f2d98a;
  --patch: #1f1450;
  --ivory: #1a1040;
  --ivory-warm: #120b2e;
  --ink: #f5ecd2;
  --ink-dim: rgba(245, 236, 210, 0.7);
  --ink-faint: rgba(245, 236, 210, 0.48);
  --rule: rgba(232, 185, 72, 0.5);
  --rule-soft: rgba(245, 236, 210, 0.16);
  --shadow: 0 1px 0 rgba(255, 255, 255, 0.07), 0 12px 34px -14px rgba(0, 0, 0, 0.8);
  --beat-off: rgba(245, 236, 210, 0.24);
}

:root[data-theme='filmi'] .sheet {
  background: rgba(8, 4, 24, 0.62);
}

/* ── Khelaiya: lurex on georgette ─────────────────────────────
   Peacock ground, gold thread, hot pink for the brass. Gold does the job
   madder does on the pichhwai, the same way sanedo's zari does - but the
   accent stays pink, because this cloth has two metals' worth of glitter
   and needs one colour in it that is not shiny. */
:root[data-theme='khelaiya'] {
  --madder: #e7c468;
  --madder-deep: #f4e3b0;
  --patch: #10332c;
  --ivory: #0e3b32;
  --ivory-warm: #0a2e27;
  --ink: #f3ece0;
  --ink-dim: rgba(243, 236, 224, 0.7);
  --ink-faint: rgba(243, 236, 224, 0.46);
  --rule: rgba(217, 180, 90, 0.5);
  --rule-soft: rgba(243, 236, 224, 0.16);
  --shadow: 0 1px 0 rgba(255, 255, 255, 0.06), 0 12px 34px -14px rgba(0, 0, 0, 0.75);
  --beat-off: rgba(243, 236, 224, 0.24);
}

/* Gold wants dark text on it, the way fusion's teal does. The join hint is
   dyed from the same two variables as the toast, so it needs the same. */
:root[data-theme='khelaiya'] .btn-play,
:root[data-theme='khelaiya'] .toast,
:root[data-theme='khelaiya'] .join-hint {
  color: #0b241f;
}

:root[data-theme='khelaiya'] .sheet {
  background: rgba(3, 18, 15, 0.64);
}

/* ── Indipop: screen print ────────────────────────────────────
   Light like prachin, but a cool synthetic white rather than warm undyed
   cotton, and magenta does the job madder does there. */
:root[data-theme='indipop'] {
  --madder: #e5197f;
  --madder-deep: #c2126a;
  --patch: #f7f8f4;
  --ivory: #f0f1ec;
  --ivory-warm: #e4e6df;
  --ink: #1c1c1a;
  --ink-dim: rgba(28, 28, 26, 0.66);
  --ink-faint: rgba(28, 28, 26, 0.44);
  --rule: rgba(229, 25, 127, 0.32);
  --rule-soft: rgba(28, 28, 26, 0.14);
  --shadow: 0 1px 0 rgba(255, 255, 255, 0.6), 0 10px 30px -14px rgba(28, 28, 26, 0.35);
  --beat-off: rgba(28, 28, 26, 0.18);
}

/* ── Gujarati pop: digital print ──────────────────────────────
   A saturated ground, so light chrome - but hot pink rather than the zari
   the two dark cloths use, because nothing here is metal. */
:root[data-theme='gujpop'] {
  --madder: #ff2d87;
  --madder-deep: #ffd84d;
  --patch: #2c0a52;
  --ivory: #3d0f6b;
  --ivory-warm: #1a0533;
  --ink: #fdf2ff;
  --ink-dim: rgba(253, 242, 255, 0.72);
  --ink-faint: rgba(253, 242, 255, 0.5);
  --rule: rgba(255, 45, 135, 0.55);
  --rule-soft: rgba(253, 242, 255, 0.18);
  --shadow: 0 1px 0 rgba(255, 255, 255, 0.09), 0 12px 34px -14px rgba(0, 0, 0, 0.75);
  --beat-off: rgba(253, 242, 255, 0.26);
}

:root[data-theme='gujpop'] .sheet {
  background: rgba(14, 3, 30, 0.62);
}

/* ── Fusion: aari on a grid ───────────────────────────────────
   Natural indigo ground, ecru thread, and the one modern colour carries
   the accent - the same rule the cloth follows. */
:root[data-theme='fusion'] {
  --madder: #00e5c0;
  --madder-deep: #e8dcc0;
  --patch: #12222f;
  --ivory: #16283d;
  --ivory-warm: #0f1d2e;
  --ink: #f0e8d6;
  --ink-dim: rgba(240, 232, 214, 0.7);
  --ink-faint: rgba(240, 232, 214, 0.46);
  --rule: rgba(0, 229, 192, 0.4);
  --rule-soft: rgba(240, 232, 214, 0.16);
  --shadow: 0 1px 0 rgba(255, 255, 255, 0.06), 0 12px 34px -14px rgba(0, 0, 0, 0.75);
  --beat-off: rgba(240, 232, 214, 0.24);
}

/* Teal needs dark text on it where madder wanted light. */
:root[data-theme='fusion'] .btn-play {
  color: #06121a;
}

:root[data-theme='fusion'] .toast,
:root[data-theme='fusion'] .join-hint {
  color: #06121a;
}

:root[data-theme='fusion'] .sheet {
  background: rgba(4, 12, 20, 0.62);
}

/* ── Techno: abla in the dark ─────────────────────────────────
   Near-black, chrome, one laser colour. The darkest chrome in the set. */
:root[data-theme='techno'] {
  --madder: #a93dff;
  --madder-deep: #c8ceda;
  --patch: #101018;
  --ivory: #08080c;
  --ivory-warm: #0e0e16;
  --ink: #e6e8ee;
  --ink-dim: rgba(230, 232, 238, 0.68);
  --ink-faint: rgba(230, 232, 238, 0.44);
  --rule: rgba(169, 61, 255, 0.45);
  --rule-soft: rgba(230, 232, 238, 0.14);
  --shadow: 0 1px 0 rgba(255, 255, 255, 0.05), 0 12px 34px -14px rgba(0, 0, 0, 0.9);
  --beat-off: rgba(230, 232, 238, 0.22);
}

:root[data-theme='techno'] .sheet {
  background: rgba(0, 0, 0, 0.66);
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

body {
  margin: 0;
  background: var(--ivory);
  color: var(--ink);
  font-family: var(--sys);
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior: none;
}

#yt {
  position: fixed;
  width: 1px;
  height: 1px;
  left: -10px;
  top: -10px;
  opacity: 0;
  pointer-events: none;
}

#floor {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}

:focus-visible {
  outline: 2px solid var(--madder);
  outline-offset: 3px;
  border-radius: 3px;
}

/* ── Top ──────────────────────────────────────────────────── */

.logo {
  display: flex;
  align-items: center;
  gap: 9px;
  text-decoration: none;
  color: var(--madder-deep);
  pointer-events: auto;
}

.logo-type {
  display: block;
  line-height: 1;
}

/* ── Top, continued ───────────────────────────────────────── */

/* Logo, genres and clock share one row. They were three fixed layers before,
   all wanting the same band, and the logo widening was enough to make them
   collide. A row cannot overlap itself. */
.top {
  position: fixed;
  z-index: 10;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 16px;
  padding: calc(var(--frame) + max(12px, env(safe-area-inset-top))) calc(var(--frame) + 16px) 0;
  pointer-events: none;
}

.brand,
.meta {
  flex: 0 0 auto;
}

.brand {
  display: flex;
  flex-direction: column;
  gap: 0;
}

/* Three tiers stacked - PYC's mark, the name, and what it is - so this needs
   real height where the old single-line wordmark did not. Below about 40px
   "JUKE BOX" stops being readable and becomes texture, which is the floor the
   clamp is set against. */
.wordmark {
  display: block;
  height: clamp(42px, 5.2vw, 58px);
  width: auto;
}

.meta {
  text-align: right;
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin-left: auto;
}

.clock {
  margin: 0;
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink-dim);
}

.crowd {
  margin: 0;
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}


/* ── The genre switcher ───────────────────────────────────────
   Each genre wears its own two colours and nothing else - no textures, no
   panel, no shared background. Which means the active state cannot be
   carried by colour, because every chip already has its own: it is carried
   by weight, a ring in the chip's own ink, and everything else stepping
   back. */

.genres {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  justify-content: center;
  pointer-events: auto;
}

.genres ol {
  display: flex;
  align-items: center;
  gap: 5px;
  margin: 0;
  padding: 4px 2px;
  list-style: none;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -ms-overflow-style: none;
  max-width: 100%;
}

.genres ol::-webkit-scrollbar {
  display: none;
}

.genres li {
  flex: 0 0 auto;
}

.swatch {
  appearance: none;
  border: 0;
  padding: 7px 13px;
  margin: 0;
  border-radius: 2px;
  font: inherit;
  cursor: pointer;
  display: block;
  position: relative;
  white-space: nowrap;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  opacity: 0.62;
  /* A hairline in the chip's own ink. Without it a chip whose ground matches
     the page - techno's black on near-black, filmi's violet on violet -
     loses its edge entirely and reads as floating text. */
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--chip-ink) 38%, transparent),
    0 1px 3px -1px rgba(0, 0, 0, 0.35);
  transition: opacity 0.18s ease, box-shadow 0.18s ease;
}

.sw-gu {
  font-family: var(--gu);
  font-size: 0.9375rem;
  line-height: 1.1;
  display: block;
}

.swatch:hover {
  opacity: 0.85;
}

/* Picked. Every chip is already coloured, so this has to read without using
   colour: full strength, a ring in the chip's own ink, and a shade more
   weight than the rest. */
.swatch.on {
  opacity: 1;
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--chip-ink) 55%, transparent),
    0 0 0 2px var(--chip-ink),
    0 2px 7px -2px rgba(0, 0, 0, 0.5);
}

/* Curated but not woven. It wears the page's own tokens rather than a cloth's,
   because it has no cloth yet, and the rule through it says so without needing
   a caption. Kept in the strip on purpose: hiding it would make the set look
   finished when it is not. */
.swatch.pending {
  background: transparent;
  color: var(--ink-faint);
  border: 1px dashed var(--rule-soft);
  cursor: default;
  opacity: 0.7;
}

.swatch.pending .sw-gu {
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

.swatch.pending:hover {
  opacity: 0.7;
}

/* Present for assistive tech at every size; clipped rather than `display`,
   so the button is never announced as just "button". */
.sw-en {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Below this the row wraps: logo and clock keep the first line, the genres
   take the whole of the second. */
@media (max-width: 900px) {
  .genres {
    order: 3;
    flex-basis: 100%;
    justify-content: flex-start;
  }
}

@media (max-width: 560px) {
  .top {
    padding-left: calc(var(--frame) + 10px);
    padding-right: calc(var(--frame) + 10px);
  }
}

/* ── Dock ─────────────────────────────────────────────────── */

/* ── Bottom ───────────────────────────────────────────────────
   The dock sits in one measured container. scene.js sizes the floor against
   this, so the bottom row can never collide with the floor.

   Three columns so the dock stays centred whatever sits beside it: the dock
   owns the middle one outright. Column 1 is the WhatsApp link, column 3 the
   YouTube credit - the curation lockup that used to sit there is gone, since
   the header lockup already says PYC.
*/
.bottom {
  position: fixed;
  z-index: 10;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--frame) + max(10px, env(safe-area-inset-bottom)));
  width: calc(100vw - var(--frame) * 2 - 32px);
  display: grid;
  /* minmax(0, ...) rather than a bare 1fr. A bare `1fr` is `minmax(auto, 1fr)`,
     and that auto minimum is the column's *min-content* - so the dock could
     push its own column wider than this container and run off the screen
     instead of making its text ellipsize. It did, by 73px on a 412px phone,
     the moment a fourth control went in the row. */
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: end;
  gap: 14px;
  pointer-events: none;
}

.bottom > * {
  pointer-events: auto;
}

.yt-credit {
  /* Names its row and column explicitly. Auto-placement walks forward only,
     and this comes before the dock in the markup - left implicit it took the
     dock's column and pushed it onto a second row, which grows the bottom bar
     and comes straight out of the circle. */
  grid-column: 3;
  grid-row: 1;
  justify-self: end;
  align-self: end;
  /* Never wider than the column it was given. End-justified, an item that
     outgrows its track overflows to the LEFT - which is how a 240px mark ended
     up lying across the dock on every window under about 1100px, with no
     overlap in the grid itself to explain it. */
  max-width: 100%;
  /* One line: "Streaming via" then their mark, reading as a single sentence.
     No gap of mine - the asset carries YouTube's clear space inside its own
     canvas, so anything added here would sit on top of theirs. */
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 0;
  text-decoration: none;
  pointer-events: auto;
  opacity: 0.72;
  transition: opacity 0.2s ease;
}

/* The plain-language half, in the site's own voice and the same small caps the
   curation credit used to carry. The lockup below is the mark; this is the
   sentence, for anyone who does not read logos. */
/* The words half of the sentence, in the site's own voice. Sentence case, not
   the small caps the curation credit used: this runs into a wordmark that is
   set in mixed case, and shouting the first half looked like two designs. */
.yt-by {
  font-size: 0.8125rem;
  color: var(--ink-dim);
  white-space: nowrap;
  flex: 0 0 auto;
}

.yt-credit:hover,
.yt-credit:focus-visible {
  opacity: 1;
}

/* Their artwork, at their proportions. Never recoloured, never redrawn - the
   only thing this decides is how big it is and which of the two files shows.
   Sized for the box, not the mark: only 1294 of these 1705 pixels are ink, so
   the wordmark reads about 76% of whatever width is set here. */
.yt-mark {
  display: block;
  width: 172px;
  max-width: 100%;
  height: auto;
}

/* One corner: the way to ask a human, or the bot standing in for one. Quiet,
   off to the side, and never competing with the dock for the middle. The
   opposite corner used to carry a "curated by PYC" lockup; the header lockup
   says PYC already, so the column is deliberately left empty. */
.wa {
  grid-column: 1;
  grid-row: 1;
  justify-self: start;
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  color: var(--ink-dim);
  opacity: 0.75;
  transition: opacity 0.18s ease, color 0.18s ease;
  /* Clear of the painted frame, the same twenty pixels the credit takes. */
  margin-bottom: 20px;
}

.wa:hover {
  opacity: 1;
  color: var(--madder);
}

.wa-mark {
  width: 42px;
  height: 42px;
  flex: none;
  display: block;
}

.wa-label {
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1.4;
}

.dock {
  grid-column: 2;
  grid-row: 1;
  /* Shrinkable. Without it the panel takes its min-content width from the
     controls row and the now-playing text never gets the chance to ellipsize. */
  min-width: 0;
  max-width: 100%;
  width: min(var(--dock-max), 100%);
  display: flex;
  flex-direction: column;
  gap: 11px;
  padding: 13px 15px;
  background: var(--patch);
  border: 1px solid var(--rule);
  border-radius: 2px;
  box-shadow: var(--shadow);
}

.now {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.cover-wrap {
  width: 44px;
  height: 44px;
  flex: none;
  overflow: hidden;
  border: 1px solid var(--rule);
  background: var(--ivory-warm);
}

.cover {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.now-text {
  min-width: 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.track {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Reads as the title, behaves as a link. The underline is held back until
   hover because the dock is already dense, and `[href]` keeps it off the
   boot-failure message, which is the same element with its link removed. */
.track-link {
  color: inherit;
  text-decoration: none;
}

.track-link[href]:hover {
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-underline-offset: 3px;
}

.artist {
  margin: 0;
  font-size: 0.8125rem;
  color: var(--ink-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.time {
  margin: 0;
  font-size: 0.8125rem;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  flex: none;
  display: flex;
  gap: 4px;
}

.slash {
  opacity: 0.5;
}

.controls {
  display: flex;
  align-items: center;
  gap: 8px;
  /* The backstop. Every control here has nowrap text and cannot shrink, so on a
     narrow enough screen the row will not fit however tight the padding gets -
     and a row that cannot fit should drop a control to the next line, not spill
     it out through the side of the panel. */
  flex-wrap: wrap;
}

.btn {
  appearance: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--ink);
  border-radius: 2px;
  padding: 8px 14px;
  min-height: 42px;
  font: inherit;
  font-size: 0.8125rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 7px;
  white-space: nowrap;
  transition: background 0.16s ease, border-color 0.16s ease;
}

/* ── Stamps ───────────────────────────────────────────────────
   Block printing is patola's sibling craft, so the two controls are cut
   blocks rather than pills: notched corners where a carver would relieve
   them, a hand-cut motif, and a real ink press on :active. The frame is two
   clipped layers because clip-path eats a border. */
.btn-stamp {
  position: relative;
  border: 0;
  border-radius: 0;
  padding: 9px 15px;
  background: var(--madder);
  clip-path: polygon(
    6px 0, calc(100% - 6px) 0, 100% 6px, 100% calc(100% - 6px),
    calc(100% - 6px) 100%, 6px 100%, 0 calc(100% - 6px), 0 6px
  );
  color: var(--madder-deep);
  transition: transform 0.08s ease, color 0.14s ease;
}

.btn-stamp::before {
  content: '';
  position: absolute;
  inset: 1.5px;
  background: var(--patch);
  clip-path: polygon(
    5px 0, calc(100% - 5px) 0, 100% 5px, 100% calc(100% - 5px),
    calc(100% - 5px) 100%, 5px 100%, 0 calc(100% - 5px), 0 5px
  );
  transition: background 0.14s ease;
}

.btn-stamp > * {
  position: relative;
  z-index: 1;
}

.btn-stamp .cut {
  width: 17px;
  height: 17px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 0.4s cubic-bezier(0.3, 1.4, 0.5, 1);
}

.btn-stamp .cut .solid {
  fill: currentColor;
  stroke-width: 1.2;
}

/* Tinted toward this cloth's accent, not toward a fixed colour. This was a
   hard-coded cream, which is right on undyed cotton and wrong on the six dark
   cloths: hovering flipped a near-black stamp to near-white while the ink
   stayed pale, so the label vanished into the face it sat on. */
.btn-stamp:hover::before {
  background: color-mix(in srgb, var(--patch) 84%, var(--madder));
}

/* A stamp with no word in it, so the padding closes up around the cut. */
.btn-cut-only {
  padding: 9px 11px;
}

.i-contract {
  display: none;
}

:root[data-fs='on'] .i-expand {
  display: none;
}

:root[data-fs='on'] .i-contract {
  display: block;
}

/* `.btn` sets display:flex, which a bare [hidden] cannot beat. */
.btn[hidden] {
  display: none;
}

/* Pressed: the block goes down and the ink takes. */
.btn-stamp:active {
  transform: translateY(1px);
  color: var(--ivory);
}

.btn-stamp:active::before {
  background: var(--madder);
}

/* A nudge on press. The icon used to be a spiral that turned a full circuit
   for `એક ફેરો`; with a plain skip mark the rotation made no sense. */
#next:active .cut {
  transform: translateX(2px);
}

#songs[aria-expanded='true'] {
  color: var(--ivory);
}

#songs[aria-expanded='true']::before {
  background: var(--madder);
}

.btn [lang='gu'] {
  font-family: var(--gu);
  font-size: 0.9375rem;
  line-height: 1.1;
}

.btn:hover {
  background: color-mix(in srgb, var(--madder) 11%, transparent);
  border-color: var(--madder);
}

.gloss {
  color: var(--ink-faint);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.btn-ghost {
  border-color: transparent;
}

.btn-icon {
  padding: 0;
  width: 42px;
  justify-content: center;
  font-size: 0.9375rem;
  color: var(--ink-dim);
}

/* The same block, already inked - it is the one control that is always on. */
.btn-play {
  width: 44px;
  min-width: 44px;
  padding: 0;
  border: 0;
  border-radius: 0;
  justify-content: center;
  background: var(--madder);
  color: var(--ivory);
  clip-path: polygon(
    6px 0, calc(100% - 6px) 0, 100% 6px, 100% calc(100% - 6px),
    calc(100% - 6px) 100%, 6px 100%, 0 calc(100% - 6px), 0 6px
  );
  transition: background 0.14s ease, transform 0.08s ease;
}

/* Darker by a fixed step, not `--madder-deep`. That token is the cloth's
   *second* accent rather than a shade of the first, so on half the themes it
   sent the button somewhere else entirely: gujpop's pink turned yellow,
   techno's purple turned grey. A play button should not change identity
   because the pointer is over it. */
.btn-play:hover {
  background: color-mix(in srgb, var(--madder) 84%, #000);
}

.btn-play:active {
  transform: translateY(1px);
}

.btn-play svg {
  width: 17px;
  height: 17px;
  fill: currentColor;
}

.i-pause {
  display: none;
}

body.playing .i-play {
  display: none;
}

body.playing .i-pause {
  display: block;
}

/* Four beats to the bar - the shuttle passing, not a clap count. Past the
   old clap forms there is nothing honest to count, so this counts time. */
.beat {
  display: flex;
  gap: 5px;
  margin-left: auto;
  padding-right: 3px;
  flex: none;
}

.beat i {
  width: 8px;
  height: 8px;
  background: var(--beat-off);
  display: block;
  /* The register's stepped diamond, so the beat belongs to the cloth. */
  clip-path: polygon(50% 0, 67% 25%, 100% 50%, 67% 75%, 50% 100%, 33% 75%, 0 50%, 33% 25%);
  transition: background 0.09s linear, transform 0.09s linear;
}

.beat i.hit {
  background: var(--madder);
  transform: scale(1.55);
}

@media (max-width: 1000px) {
  /* Two rows rather than three, and this switches earlier than the 760px it
     used to. Three across only really fits above about 1100px: below that the
     dock keeps its width and the side columns pay for it, which squeezed the
     YouTube mark down to 65px on an 820px window - smaller than it renders on
     a phone. Stacking costs forty vertical pixels out of the circle and buys
     back a credit that can be read. */
  .bottom {
    grid-template-columns: auto minmax(0, 1fr);
    justify-items: center;
    gap: 10px;
  }
  .wa {
    grid-column: 1;
    grid-row: 1;
    justify-self: start;
    margin-bottom: 0;
  }
  .yt-credit {
    grid-column: 2;
    grid-row: 1;
    justify-self: end;
  }
  .yt-mark {
    width: 150px;
  }
  .dock {
    grid-column: 1 / -1;
    grid-row: 2;
    justify-self: center;
  }
  .join-hint {
    font-size: 0.7rem;
    max-width: min(13rem, calc(100vw - 28px));
  }
}

@media (max-width: 640px) {
  /* The bottom container is 290px here and the WhatsApp link takes 138 of it,
     so the column this shares with it is only about 142px wide. */
  .yt-mark {
    width: 128px;
  }
  .gloss {
    display: none;
  }
  .bottom {
    width: calc(100vw - var(--frame) * 2 - 20px);
  }
}

/* Two stamps and the play block already fill the dock at this width; the
   beat indicator was being clipped by the panel edge rather than wrapping. */
@media (max-width: 520px) {
  .beat {
    display: none;
  }
}

/* Four controls on a 360px phone come to 285px against 258px of dock. Rather
   than wrap - which would make the dock taller for the sake of one icon - the
   stamps give up the padding they can spare and the row closes to fit. The
   first attempt at this landed on 259 against 258 and wrapped anyway, which is
   why the numbers below look over-specific: they are measured, not chosen. */
@media (max-width: 420px) {
  .controls {
    gap: 6px;
  }
  .btn-stamp {
    padding: 9px 10px;
  }
  .btn-cut-only {
    padding: 9px 8px;
  }
}

/* ── The song list ────────────────────────────────────────── */

.sheet {
  position: fixed;
  inset: 0;
  z-index: 20;
  background: rgba(60, 34, 14, 0.34);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 24px 16px max(24px, env(safe-area-inset-bottom));
}

.sheet[hidden] {
  display: none;
}

.sheet-inner {
  width: min(560px, 100%);
  max-height: min(72vh, 580px);
  display: flex;
  flex-direction: column;
  background: var(--patch);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 16px 16px 6px;
  box-shadow: 0 24px 60px -18px rgba(60, 30, 12, 0.6);
}

.sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.sheet-head h2 {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 600;
  display: flex;
  align-items: baseline;
  gap: 9px;
}

.sheet-head [lang='gu'] {
  font-family: var(--gu);
  font-size: 1.25rem;
  color: var(--madder-deep);
}

:root[data-theme='sanedo'] .sheet {
  background: rgba(20, 4, 8, 0.6);
}

.sheet-note {
  margin: 8px 0 10px;
  font-size: 0.8125rem;
  color: var(--ink-dim);
  line-height: 1.5;
  border-bottom: 1px solid var(--rule-soft);
  padding-bottom: 10px;
}

.sheet-note em {
  font-style: normal;
  color: var(--madder);
  font-weight: 600;
}

.sheet-list {
  list-style: none;
  margin: 0;
  padding: 0 0 10px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}

/* Two controls to a row: the row queues the track, the mark at its end opens
   the video it plays from. The rule moved off the button and onto the row when
   the second control arrived, or it would have stopped short of it. */
.sheet-list li {
  display: flex;
  align-items: stretch;
}

.sheet-list li + li {
  border-top: 1px solid var(--rule-soft);
}

.sheet-list button {
  appearance: none;
  flex: 1;
  min-width: 0;
  text-align: left;
  background: transparent;
  border: 0;
  color: var(--ink);
  font: inherit;
  padding: 10px 10px;
  cursor: pointer;
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-height: 44px;
}

.sheet-list button:hover:not([disabled]) {
  background: color-mix(in srgb, var(--madder) 11%, transparent);
}

.s-src {
  flex: none;
  width: 42px;
  display: grid;
  place-items: center;
  color: var(--ink-faint);
  border-left: 1px solid var(--rule-soft);
  transition: color 0.16s ease, background 0.16s ease;
}

.s-src svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.s-src:hover {
  color: var(--madder);
  background: color-mix(in srgb, var(--madder) 11%, transparent);
}

.sheet-list .s-title {
  font-size: 0.875rem;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sheet-list .s-artist {
  font-size: 0.75rem;
  color: var(--ink-faint);
  flex: none;
  max-width: 44%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sheet-list button[disabled] {
  opacity: 0.45;
  cursor: default;
}

/* ── Toast ────────────────────────────────────────────────── */

.toast {
  position: fixed;
  z-index: 30;
  left: 50%;
  bottom: calc(var(--frame) + max(10px, env(safe-area-inset-bottom)) + var(--bottom-h) + 14px);
  transform: translate(-50%, 6px);
  margin: 0;
  padding: 9px 16px;
  background: var(--madder);
  color: var(--ivory);
  border-radius: 2px;
  font-size: 0.8125rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease, transform 0.22s ease;
  max-width: calc(100vw - 40px);
  text-align: center;
  box-shadow: var(--shadow);
}

.toast [lang='gu'] {
  font-family: var(--gu);
  font-size: 0.9375rem;
}

.toast.show {
  opacity: 1;
  transform: translate(-50%, 0);
}

.noscript {
  position: fixed;
  z-index: 40;
  inset: auto 0 0;
  margin: 0;
  padding: 16px;
  background: var(--madder);
  color: var(--ivory);
  text-align: center;
  font-size: 0.875rem;
}

@media (prefers-reduced-motion: reduce) {
  .btn,
  .beat i,
  .toast,
  .join-hint,
  .swatch,
  .sw-gu {
    transition: none;
  }
}


/* ── The join hint ─────────────────────────────────────────────
   Parked under the newcomer by app.js, which sets left/top; everything about
   how it looks is here. It never takes a pointer: she is the target, and a
   bubble sitting over her would swallow the tap meant for her. */
.join-hint {
  position: fixed;
  z-index: 12;
  transform: translate(-50%, -4px);
  /* Sized to its own text, not to whatever room is left. A fixed element with
     only `left` set shrink-to-fits against the gap between that offset and the
     viewport edge, so parked near the right rail it collapsed to 80px wide and
     153px tall - one word per line. max-content takes the offset out of it. */
  width: max-content;
  max-width: min(15rem, calc(100vw - 32px));
  padding: 8px 13px 9px;
  background: var(--madder);
  color: var(--ivory);
  border-radius: 2px;
  box-shadow: var(--shadow);
  font-size: 0.75rem;
  line-height: 1.42;
  text-align: center;
  text-wrap: balance;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.join-hint b {
  display: block;
  font-weight: 600;
}

.join-hint-emoji {
  /* Keeps the emoji off the ivory text colour and on its own glyphs. */
  font-style: normal;
}

/* The notch that makes it point at her rather than merely sit near her. */
.join-hint::before {
  content: '';
  position: absolute;
  /* Follows her, not the bubble. When the bubble is pushed back to stay on
     screen the notch has to stay over her head or it points at nothing. */
  left: var(--notch, 50%);
  top: -5px;
  width: 10px;
  height: 10px;
  background: inherit;
  transform: translateX(-50%) rotate(45deg);
  border-radius: 1px;
}

.join-hint.show {
  opacity: 1;
  transform: translate(-50%, 0);
  animation: join-nudge 2.6s ease-in-out 0.9s infinite;
}

/* A slow lean toward her - enough to catch the eye on a floor that is already
   turning, small enough not to join in the dancing. */
@keyframes join-nudge {
  0%, 100% { transform: translate(-50%, 0); }
  50% { transform: translate(-50%, 3px); }
}

@media (prefers-reduced-motion: reduce) {
  .join-hint.show { animation: none; }
}
