@font-face {
  font-family: 'Khand';
  src: url('/assets/fonts/khand-400-devanagari.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
}

@font-face {
  font-family: 'Khand';
  src: url('/assets/fonts/khand-400-latin-ext.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Khand';
  src: url('/assets/fonts/khand-400-latin.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Khand';
  src: url('/assets/fonts/khand-500-devanagari.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
}

@font-face {
  font-family: 'Khand';
  src: url('/assets/fonts/khand-500-latin-ext.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Khand';
  src: url('/assets/fonts/khand-500-latin.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ─────────────────────────────────────────────────────────────
   कटिंग चाय

   The whole picture is repainted from custom properties on
   :root[data-slot]. Four slots, four palettes, one set of shapes -
   so the tapri at 7am and the tapri at 11pm are the same drawing
   under different light, which is what a real one is.
   ───────────────────────────────────────────────────────────── */

/* ── Palettes ────────────────────────────────────────────────── */

:root {
  --ink: #fff;
  --ink-dim: rgba(255, 255, 255, 0.62);
  --chrome: rgba(20, 12, 6, 0.42);
  --chrome-line: rgba(255, 255, 255, 0.16);
  --radius: 999px;
  --dock-max: 640px;

  /* defaults, overwritten per slot */
  --sky-top: #21365e;
  --sky-mid: #b8562f;
  --sky-bot: #f0b25e;
  --far: #2a1b16;
  --near: #150d09;
  --ground: #1c110c;
  --wall: #3a2415;
  --sun-x: 78%;
  --sun-y: 62%;
  --sun-size: 190px;
  --sun-color: #ffd48a;
  --sun-opacity: 0.85;
  --bulb-op: 1;
  --bulb-size: 340px;
  --grain: 0.05;
  --logo: #fff;
  --logo-shadow: 0 6px 34px rgba(0, 0, 0, 0.42);
  --sky-photo: none;
  --scrim: 0.22;

}

:root[data-slot='subah'] {
  --sky-top: #6f9dc4;
  --sky-mid: #cbb3a6;
  --sky-bot: #f2cfa6;
  --far: #4a5566;
  --near: #2b3440;
  --ground: #38414d;
  --wall: #56606d;
  --sun-x: 22%;
  --sun-y: 60%;
  --sun-size: 150px;
  --sun-color: #fff0cd;
  --sun-opacity: 0.7;
  --bulb-op: 0.18;
  --bulb-size: 220px;
  --ink: #14202c;
  --ink-dim: rgba(20, 32, 44, 0.62);
  --chrome: rgba(255, 255, 255, 0.42);
  --chrome-line: rgba(20, 32, 44, 0.14);
  --logo: #16232f;
  --logo-shadow: 0 4px 22px rgba(255, 255, 255, 0.4);
  --sky-photo: url('/assets/sky-subah.jpg');
  --scrim: 0;
}

:root[data-slot='dopahar'] {
  --sky-top: #8fb6d2;
  --sky-mid: #c3d4dd;
  --sky-bot: #e7e6df;
  --far: #5d6670;
  --near: #3b4249;
  --ground: #5a5f62;
  --wall: #656c73;
  --sun-x: 52%;
  --sun-y: 16%;
  --sun-size: 130px;
  --sun-color: #fffdf2;
  --sun-opacity: 0.55;
  --bulb-op: 0;
  --bulb-size: 180px;
  --ink: #1b2227;
  --ink-dim: rgba(27, 34, 39, 0.6);
  --chrome: rgba(255, 255, 255, 0.5);
  --chrome-line: rgba(27, 34, 39, 0.14);
  --grain: 0.035;
  --logo: #1d262c;
  --logo-shadow: 0 4px 22px rgba(255, 255, 255, 0.45);
  --sky-photo: url('/assets/sky-dopahar.jpg');
  --scrim: 0;
}

:root[data-slot='shaam'] {
  --sky-top: #1d2f57;
  --sky-mid: #c05a2c;
  --sky-bot: #f3ba63;
  --far: #2c1a14;
  --near: #140c08;
  --ground: #1b100a;
  --wall: #3b2415;
  --sun-x: 76%;
  --sun-y: 63%;
  --sun-size: 200px;
  --sun-color: #ffcf85;
  --sun-opacity: 0.9;
  --bulb-op: 1;
  --sky-photo: url('/assets/sky-shaam.jpg');
  --scrim: 0.26;
}

:root[data-slot='raat'] {
  --sky-top: #05070f;
  --sky-mid: #0d1626;
  --sky-bot: #1a2438;
  --far: #080c15;
  --near: #03050a;
  --ground: #060910;
  --wall: #1d1208;
  --sun-x: 20%;
  --sun-y: 20%;
  --sun-size: 56px;
  --sun-color: #e6ecf7;
  --sun-opacity: 0.82;
  /* A defined core with a faint halo. All bloom and it is a smudge; no bloom
     at all and it is a flat white circle sitting on the sky. */
  --sun-core: 46%;
  --sun-edge: 92%;
  --bulb-op: 1;
  --bulb-size: 420px;
  --grain: 0.07;
  --sky-photo: url('/assets/sky-raat.jpg');
  --scrim: 0.3;
}

/* ── Base ────────────────────────────────────────────────────── */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

/* iOS Safari resolves 100% against the large viewport, the one with the URL
   bar collapsed, so the dock can slide under the bar when it expands. The
   dynamic unit tracks whatever is actually visible. */
@supports (height: 100dvh) {
  html,
  body {
    height: 100dvh;
  }
}

body {
  margin: 0;
  overflow: hidden;
  background: #0a0a0a;
  color: var(--ink);
  font-family: 'Khand', 'Kohinoor Devanagari', 'Noto Sans Devanagari', ui-sans-serif, system-ui, sans-serif;
  font-weight: 500;
  /* Khand is condensed, so the same pixel size reads smaller than the system
     face did. Everything in the UI goes up a notch to compensate. */
  font-size: 17px;
  -webkit-font-smoothing: antialiased;
  transition: color 900ms ease;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

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

kbd {
  font: inherit;
  font-size: 0.92em;
  padding: 0.05em 0.42em;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid var(--chrome-line);
}

.noscript {
  position: fixed;
  inset: auto 0 40% 0;
  text-align: center;
  color: #fff;
}

/* ── Scene ───────────────────────────────────────────────────── */

.scene {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}

.sky {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    var(--sky-top) 0%,
    var(--sky-top) 22%,
    var(--sky-mid) 56%,
    var(--sky-bot) 72%,
    var(--sky-bot) 100%
  );
  transition: background 1200ms ease;
}

/* The generated sky plate. A missing file paints nothing, which is exactly the
   fallback we want - the gradient below stays visible and the page is never
   broken by an asset that has not been made yet. Expect one 404 per slot in
   the console until the files land. */
.sky__photo {
  position: absolute;
  inset: 0;
  background-image: var(--sky-photo);
  background-size: cover;
  /* The silhouettes start around 55% down, so bias the crop upward: the part
     of the plate worth seeing is the sky, not the ground it will never show. */
  background-position: center 28%;
  background-repeat: no-repeat;
  transition: opacity 1200ms ease;
}

/* Keeps the wordmark and the top bar legible over a busy plate. Near zero on
   the two light slots, where a dark wash would fight the dark ink. */
.sky__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    rgba(0, 0, 0, var(--scrim)) 0%,
    rgba(0, 0, 0, 0) 42%,
    rgba(0, 0, 0, 0) 68%,
    rgba(0, 0, 0, calc(var(--scrim) * 0.8)) 100%
  );
  transition: background 1200ms ease;
}

/* Escape hatch: if a plate ever contains the whole scene rather than just the
   sky, hide the drawn one. You lose the bench that fills with real listeners,
   the kettle that rocks while the radio is on, and the bulb's throw on the
   back wall - so this is a trade, not an upgrade. */
:root[data-art='off'] .art,
:root[data-art='off'] .bulb {
  display: none;
}

/* Grain. A flat gradient reads as a CSS gradient; a grained one reads as
   printed. Cheap SVG noise, no image request. */
.scene::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: var(--grain);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  mix-blend-mode: overlay;
}

.sun {
  position: absolute;
  left: var(--sun-x);
  top: var(--sun-y);
  width: var(--sun-size);
  height: var(--sun-size);
  margin: calc(var(--sun-size) / -2) 0 0 calc(var(--sun-size) / -2);
  border-radius: 50%;
  background: radial-gradient(
    circle,
    var(--sun-color) 0%,
    var(--sun-color) var(--sun-core, 42%),
    rgba(255, 255, 255, 0) var(--sun-edge, 70%)
  );
  opacity: var(--sun-opacity);
  transition: all 1200ms ease;
}

.art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.ground {
  display: none;
}

.art__far {
  fill: var(--far);
  opacity: 0.86;
  transition: fill 1200ms ease;
}
.art__near,
.art__people {
  fill: var(--near);
  transition: fill 1200ms ease;
}
.art__ground {
  fill: var(--ground);
  transition: fill 1200ms ease;
}
.art__wall {
  fill: var(--wall);
  transition: fill 1200ms ease;
}
.art__wash {
  fill: url(#wash);
  opacity: var(--bulb-op);
  transition: opacity 1200ms ease;
}
.art__counter {
  fill: var(--near);
}
.art__kerb {
  fill: var(--near);
  opacity: 0.96;
}
.art__glass {
  fill: var(--near);
  opacity: 0.9;
}
.art__wire {
  stroke: var(--near);
  stroke-width: 3;
  fill: none;
  opacity: 0.8;
}
/* The birds are the only thing in that group that wants a fill. Leaving it on
   the group closed both wire curves into filled shapes. */
.art__wire circle {
  fill: var(--near);
  stroke: none;
}
.art__thin {
  stroke: var(--far);
  stroke-width: 4;
  fill: none;
}
.art__thin2 {
  stroke: var(--ground);
  stroke-width: 3;
  fill: none;
  opacity: 0.5;
}
.art__spout {
  stroke: var(--near);
  stroke-width: 7;
  fill: none;
  stroke-linecap: round;
}

/* The kettle gets a barely-there rock while the radio is on. Motion this
   small is the difference between a drawing and a place. */
.kettle {
  transform-origin: 860px 522px;
}
body.is-playing .kettle {
  animation: rock 3.6s ease-in-out infinite;
}
@keyframes rock {
  0%,
  100% {
    transform: rotate(-0.35deg);
  }
  50% {
    transform: rotate(0.35deg);
  }
}

/* ── The bulb ────────────────────────────────────────────────── */

.bulb {
  position: absolute;
  left: 42%;
  top: 50%;
  transform: translate(-50%, -50%);
  opacity: var(--bulb-op);
  transition: opacity 1200ms ease;
  pointer-events: none;
}

.bulb__glow {
  display: block;
  width: var(--bulb-size);
  height: var(--bulb-size);
  margin: calc(var(--bulb-size) / -2);
  border-radius: 50%;
  background: radial-gradient(
    circle,
    rgba(255, 214, 148, 0.55) 0%,
    rgba(255, 186, 106, 0.22) 34%,
    rgba(255, 170, 80, 0) 68%
  );
  animation: flicker 7s ease-in-out infinite;
}

.bulb__filament {
  position: absolute;
  left: 0;
  top: 0;
  width: 13px;
  height: 13px;
  margin: -6px;
  border-radius: 50%;
  background: #ffeec6;
  box-shadow: 0 0 22px 7px rgba(255, 208, 140, 0.85);
}

@keyframes flicker {
  0%,
  100% {
    opacity: 1;
  }
  46% {
    opacity: 0.94;
  }
  48% {
    opacity: 0.7;
  }
  50% {
    opacity: 0.97;
  }
  72% {
    opacity: 0.88;
  }
}

/* ── Steam ───────────────────────────────────────────────────── */

.steam {
  position: absolute;
  pointer-events: none;
  opacity: 0;
  transition: opacity 700ms ease;
}
body.is-playing .steam {
  opacity: 1;
}

.steam span {
  position: absolute;
  bottom: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.5);
  filter: blur(3px);
  animation: rise 4.2s ease-in infinite;
}
.steam span:nth-child(1) {
  left: 0;
  animation-delay: 0s;
}
.steam span:nth-child(2) {
  left: 11px;
  animation-delay: 1.4s;
}
.steam span:nth-child(3) {
  left: 22px;
  animation-delay: 2.8s;
}

.steam--kettle {
  left: 56.5%;
  top: 48%;
  width: 30px;
  height: 10px;
}
.steam--glass {
  left: 50%;
  top: -6px;
  margin-left: -16px;
  width: 30px;
  height: 8px;
}

@keyframes rise {
  0% {
    transform: translateY(0) scale(0.6);
    opacity: 0;
  }
  16% {
    opacity: 0.7;
  }
  100% {
    transform: translateY(-74px) translateX(12px) scale(1.9);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .steam span,
  .bulb__glow,
  body.is-playing .kettle {
    animation: none;
  }
  .steam {
    opacity: 0.5;
  }
}

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

.topbar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 20;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 12px;
  padding: max(18px, env(safe-area-inset-top)) 22px 0;
  font-size: 14px;
  font-weight: 600;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.34);
}

.corner {
  justify-self: start;
  display: inline-flex;
  align-items: baseline;
  gap: 9px;
  min-width: 0;
}

.clock {
  font-variant-numeric: tabular-nums;
}

/* Only appears once the sky has actually reported in. */
.wx {
  color: var(--ink-dim);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  animation: wxIn 900ms ease both;
}
.wx[hidden] {
  display: none;
}
@keyframes wxIn {
  from {
    opacity: 0;
    transform: translateY(-3px);
  }
}

.presence {
  justify-self: center;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.presence__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #6fe08a;
  box-shadow: 0 0 9px rgba(111, 224, 138, 0.9);
}

.presence__count {
  font-variant-numeric: tabular-nums;
}
.presence__label {
  color: var(--ink-dim);
  font-weight: 500;
}

/* The nav holds the ordering link and the slot picker. */
.links {
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.order {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 3px 12px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--ink-dim);
  text-decoration: none;
  font-weight: 500;
  transition: color 200ms ease, background 200ms ease, border-color 200ms ease;
}
.order:hover,
.order:focus-visible {
  color: var(--ink);
  background: rgba(128, 128, 128, 0.16);
  border-color: var(--chrome-line);
  outline: none;
}
.order:focus-visible {
  outline: 2px solid #ffd08c;
  outline-offset: 2px;
}

/* A button, not a label: it walks you round the day. Styled to sit as quietly
   as the clock opposite it until you go near it. */
.slot-name {
  padding: 3px 11px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink-dim);
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  transition: color 200ms ease, background 200ms ease, border-color 200ms ease;
}
.slot-name:hover,
.slot-name:focus-visible {
  color: var(--ink);
  background: rgba(128, 128, 128, 0.16);
  border-color: var(--chrome-line);
  outline: none;
}
.slot-name:focus-visible {
  outline: 2px solid #ffd08c;
  outline-offset: 2px;
}
.slot-name:active {
  transform: scale(0.96);
}
.slot-name.is-swapping {
  animation: slotFlash 620ms ease;
}
@keyframes slotFlash {
  0% {
    color: var(--ink);
    background: rgba(255, 196, 122, 0.5);
  }
  100% {
    color: var(--ink-dim);
    background: transparent;
  }
}

/* ── Wordmark ────────────────────────────────────────────────── */

main {
  position: relative;
  z-index: 10;
  height: 100%;
  display: grid;
  grid-template-rows: 1fr auto;
  align-items: center;
  padding: 82px 20px max(22px, env(safe-area-inset-bottom));
}

.logo {
  margin: 0;
  align-self: center;
  justify-self: center;
  text-align: center;
  font-family: inherit;
  font-size: clamp(60px, 12vw, 150px);
  line-height: 0.94;
  /* Down from 700. A condensed face at display size does not need the weight,
     and the lighter cut lets the scene behind it read through. */
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--logo);
  text-shadow: var(--logo-shadow);
  transition: color 1200ms ease, text-shadow 1200ms ease;
  pointer-events: none;
  transform: translateY(-6vh);
}
.logo__line {
  display: block;
}

/* Once the viewport can hold it, कटिंग चाय reads as one line, it is a
   signboard, and a signboard is not stacked unless the board is narrow. */
@media (min-width: 700px) {
  .logo__line {
    display: inline;
  }
  /* No margin here: going inline already turns the newline between the two
     spans into a space, and adding to it gave a double-wide gap. Tune the one
     space we have instead. */
  .logo {
    word-spacing: 0.06em;
  }
  .logo {
    white-space: nowrap;
  }
}

/* ── The rate board ──────────────────────────────────────────── */

.board {
  position: absolute;
  left: 50%;
  top: 62%;
  transform: translateX(-50%) rotate(-1.2deg);
  margin: 0;
  max-width: min(520px, 84vw);
  padding: 10px 20px;
  text-align: center;
  font-family: inherit;
  font-size: clamp(17px, 2.4vw, 23px);
  font-weight: 500;
  letter-spacing: 0.02em;
  color: #f6e2c2;
  background: rgba(28, 16, 8, 0.5);
  border: 1px solid rgba(246, 226, 194, 0.22);
  border-radius: 4px;
  box-shadow: 0 0 34px rgba(255, 186, 106, 0.14);
  transition: opacity 250ms ease, transform 250ms ease;
}
.board.is-swapping {
  opacity: 0;
  transform: translateX(-50%) rotate(-1.2deg) translateY(5px);
}

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

.dock {
  position: relative;
  justify-self: center;
  width: 100%;
  max-width: var(--dock-max);
  display: grid;
  gap: 10px;
  justify-items: center;
}

.player {
  width: 100%;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 18px;
  padding: 14px 20px;
  /* Fully round on all four corners. A pill radius on a box this tall reads as
     a stadium, which is the intent. */
  border-radius: var(--radius);
  background: var(--chrome);
  border: 1px solid var(--chrome-line);
  backdrop-filter: blur(22px) saturate(150%);
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  box-shadow: 0 10px 42px rgba(0, 0, 0, 0.42), inset 0 1px 0 rgba(255, 255, 255, 0.2);
}

/* ── The glass ─────────────────────────────────────────────────
   This is the progress bar. Poured full when a song starts, empty
   by the end, drag it to seek.
   ──────────────────────────────────────────────────────────── */

.glass {
  position: relative;
  width: 58px;
  height: 88px;
  cursor: grab;
  touch-action: none;
  border-radius: 8px;
  outline-offset: 4px;
}
.glass:active {
  cursor: grabbing;
}
.glass svg {
  width: 100%;
  height: 100%;
  overflow: visible;
}
.glass__rim {
  fill: none;
  stroke: rgba(255, 255, 255, 0.72);
  stroke-width: 2.4;
  stroke-linejoin: round;
}
.glass__shine {
  fill: none;
  stroke: rgba(255, 255, 255, 0.34);
  stroke-width: 2.6;
  stroke-linecap: round;
}
#chaiFill,
#chaiTop {
  transition: y 260ms linear;
}
.glass.is-scrubbing #chaiFill,
.glass.is-scrubbing #chaiTop {
  transition: none;
}
/* A pour should read as a pour, not a jump: slower, and eased out at the top
   the way a glass actually fills. */
.glass.is-pouring #chaiFill,
.glass.is-pouring #chaiTop {
  transition: y 1050ms cubic-bezier(0.3, 0.75, 0.35, 1);
}

/* ── Meta ────────────────────────────────────────────────────── */

.meta {
  min-width: 0;
}
.meta__title,
.meta__artist {
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.meta__title {
  font-size: 19px;
  font-weight: 600;
  transition: opacity 180ms ease;
}
.meta__artist {
  font-size: 16px;
  color: var(--ink-dim);
  transition: opacity 180ms ease;
}
.player.is-swapping .meta__title,
.player.is-swapping .meta__artist {
  opacity: 0;
}
.time {
  margin-top: 3px;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-dim);
}

/* ── Controls ────────────────────────────────────────────────── */

.controls {
  display: flex;
  align-items: center;
  gap: 8px;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  min-width: 44px;
  padding: 0 14px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.1);
  color: var(--ink);
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  transition: background 160ms ease, transform 120ms ease, opacity 160ms ease;
}
.btn:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.2);
}
.btn:active:not(:disabled) {
  transform: scale(0.95);
}
.btn:disabled {
  opacity: 0.45;
  cursor: default;
}
.btn:focus-visible {
  outline: 2px solid #ffd08c;
  outline-offset: 3px;
}

.btn--play {
  width: 54px;
  height: 54px;
  padding: 0;
  background: #fff;
  color: #17110c;
}
.btn--play:hover:not(:disabled) {
  background: #fff;
}
.i-pause {
  display: none;
}
body.is-playing .i-play {
  display: none;
}
body.is-playing .i-pause {
  display: block;
}

.btn--wide {
  white-space: nowrap;
}
.btn--toggle.is-on {
  background: rgba(255, 255, 255, 0.28);
}

.btn--more-pouring {
  animation: pourPulse 700ms ease;
}
@keyframes pourPulse {
  0%,
  100% {
    background: rgba(255, 255, 255, 0.1);
  }
  40% {
    background: rgba(255, 196, 122, 0.55);
  }
}


/* ── Request list ────────────────────────────────────────────── */

.list {
  width: 100%;
  max-height: min(46vh, 330px);
  overflow-y: auto;
  padding: 14px 10px 10px;
  border-radius: 30px;
  background: var(--chrome);
  border: 1px solid var(--chrome-line);
  backdrop-filter: blur(22px) saturate(150%);
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  box-shadow: 0 10px 42px rgba(0, 0, 0, 0.42);
}
.list[hidden] {
  display: none;
}
.list__head {
  margin: 0 0 8px;
  padding: 0 12px;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-dim);
}
.list ol {
  margin: 0;
  padding: 0;
  list-style: none;
}
.list li button {
  display: grid;
  width: 100%;
  gap: 1px;
  padding: 8px 12px;
  border: 0;
  border-radius: 12px;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.list li button:hover,
.list li button:focus-visible {
  background: rgba(255, 255, 255, 0.14);
  outline: none;
}
.list li.is-current button {
  background: rgba(255, 255, 255, 0.1);
}
.t-title {
  font-size: 16px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.t-artist {
  font-size: 14px;
  color: var(--ink-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.t-queued {
  font-size: 13px;
  font-weight: 700;
  color: #ffd08c;
}

.queued {
  max-width: 100%;
  margin: 0;
  padding: 6px 16px;
  font-size: 14px;
  font-weight: 600;
  color: #ffd9a8;
  background: rgba(28, 16, 8, 0.42);
  border: 1px solid rgba(255, 208, 140, 0.28);
  border-radius: var(--radius);
  /* One line. A raw YouTube title is long enough to wrap this to three. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.queued[hidden] {
  display: none;
}

.hint {
  margin: 0;
  font-size: 12px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.7);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.55);
}
.hint kbd {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.22);
}

/* ── Narrow ──────────────────────────────────────────────────
   The fix Truck Wala got right and Deluxe Saloon did not: the
   controls get their own row instead of being squeezed into the
   same line as the title, which is what truncated Saloon's title
   to five characters on a phone. -->
   ──────────────────────────────────────────────────────────── */

@media (max-width: 620px) {
  /* Controls get their own row rather than being squeezed onto one line, which
     is what truncated the title to five characters on Deluxe Saloon. */
  /* Max radius is a clean pill on the single-row desktop dock. Here the panel
     is two rows and roughly 200px tall, so a 999px radius clamps to a stadium
     and the curve cuts across the glass and the play button. Large, but
     bounded, keeps every corner round and every control inside the shape. */
  .player {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      'glass meta'
      'controls controls';
    gap: 12px 16px;
    padding: 16px 18px;
    border-radius: 42px;
  }
  .glass {
    grid-area: glass;
    width: 50px;
    height: 76px;
  }
  .meta {
    grid-area: meta;
  }
  .controls {
    grid-area: controls;
    justify-content: center;
  }
  .btn--wide {
    flex: 1;
    max-width: 160px;
  }
  .hint {
    display: none;
  }
  .board {
    top: auto;
    bottom: calc(100% + 0px);
    display: none;
  }
  .logo {
    transform: translateY(-4vh);
  }
  .topbar {
    font-size: 14px;
    padding-inline: 12px;
    gap: 6px;
  }
  .order__label {
    display: none;
  }
  .order {
    padding: 3px 7px;
  }
  .slot-name {
    padding: 3px 8px;
  }
  /* Keeps the count from shoving the slot picker off the right edge. */
  .presence {
    min-width: 0;
  }
  .presence__label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

/* The board has nowhere to sit on a short screen; the scene keeps it. */
@media (min-width: 621px) and (max-height: 620px) {
  .board {
    display: none;
  }
}


/* ── Portrait composition ────────────────────────────────────
   See the note above .ground: covering a tall screen with a wide
   scene crops away everything that makes it a tapri, so here the
   scene sits as a band at its own aspect ratio instead. */

@media (max-aspect-ratio: 1/1) {
  /* Wider than the viewport so the stall is not a distant speck: this trims a
     fifth off each side, which costs the far edges of the skyline and nothing
     that matters. The stall and most of the bench stay in frame. */
  .art {
    inset: auto -20% 34% -20%;
    width: 140%;
    height: auto;
  }

  .ground {
    display: block;
    position: absolute;
    inset: auto 0 0 0;
    height: 35%;
    background: var(--ground);
    transition: background 1200ms ease;
  }

  /* Both of these are positioned against the viewport, so they have to be
     brought back to the counter once the scene stops filling it. */
  /* Smaller and higher than on a wide screen: at the old size the throw
     spilled past the counter and pooled on the road as a bright smudge. */
  .bulb {
    top: auto;
    bottom: 48%;
    --bulb-size: 190px;
  }

  .steam--kettle {
    top: auto;
    bottom: 47%;
    left: 58%;
  }

  /* Sit the wordmark in the clear sky above the band, not across the roof,
     but not so high that a quarter of a tall screen is empty gradient. */
  .logo {
    transform: translateY(-13vh);
  }

  /* The moon sits upper-left, where a centred wordmark on a narrow screen
     crowds it. Push it out to the corner. */
  .sun {
    left: 14%;
  }
}

/* Very short landscape windows: the band would leave nothing for the dock. */
@media (min-aspect-ratio: 1/1) and (max-height: 560px) {
  .logo {
    transform: translateY(-3vh);
    font-size: clamp(44px, 8vh, 92px);
  }
}


/* Under ~360px the count label is the first thing to give way, so the clock,
   the number and the slot picker all keep their place. */
@media (max-width: 360px) {
  .presence__label,
  .wx {
    display: none;
  }
}


/* ── Weather ──────────────────────────────────────────────────
   Layered over the hour, never replacing it. A wet evening is the
   evening palette with rain falling through it, which is how a
   real place behaves. Everything here is inert until
   /api/weather sets data-weather on the root.
   ──────────────────────────────────────────────────────────── */

.weather {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.weather__near,
.weather__far,
.weather__haze,
.weather__flash {
  position: absolute;
  opacity: 0;
  transition: opacity 1600ms ease;
}

/* Oversized and translated rather than scrolling a background position: the
   transform stays on the compositor and the edges never show. */
.weather__near,
.weather__far {
  left: -12%;
  right: -12%;
  top: -180px;
  bottom: -180px;
  background-repeat: repeat;
  will-change: transform;
}

.weather__near {
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27150%27%20height%3D%27150%27%3E%3Cg%20stroke%3D%27%23dfeaff%27%20stroke-width%3D%271.7%27%20stroke-linecap%3D%27round%27%20opacity%3D%270.62%27%3E%3Cline%20x1%3D%2714%27%20y1%3D%272%27%20x2%3D%276%27%20y2%3D%2740%27%2F%3E%3Cline%20x1%3D%2758%27%20y1%3D%2724%27%20x2%3D%2750%27%20y2%3D%2762%27%2F%3E%3Cline%20x1%3D%27100%27%20y1%3D%270%27%20x2%3D%2792%27%20y2%3D%2738%27%2F%3E%3Cline%20x1%3D%27136%27%20y1%3D%2734%27%20x2%3D%27128%27%20y2%3D%2772%27%2F%3E%3Cline%20x1%3D%2732%27%20y1%3D%2778%27%20x2%3D%2724%27%20y2%3D%27116%27%2F%3E%3Cline%20x1%3D%2776%27%20y1%3D%27104%27%20x2%3D%2768%27%20y2%3D%27142%27%2F%3E%3Cline%20x1%3D%27118%27%20y1%3D%2786%27%20x2%3D%27110%27%20y2%3D%27124%27%2F%3E%3Cline%20x1%3D%274%27%20y1%3D%27120%27%20x2%3D%27-4%27%20y2%3D%27158%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  background-size: 150px 150px;
  animation: rainNear 0.62s linear infinite;
}
.weather__far {
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%2790%27%20height%3D%2790%27%3E%3Cg%20stroke%3D%27%23cddcf5%27%20stroke-width%3D%271.1%27%20stroke-linecap%3D%27round%27%20opacity%3D%270.4%27%3E%3Cline%20x1%3D%2710%27%20y1%3D%273%27%20x2%3D%275%27%20y2%3D%2724%27%2F%3E%3Cline%20x1%3D%2738%27%20y1%3D%2716%27%20x2%3D%2733%27%20y2%3D%2737%27%2F%3E%3Cline%20x1%3D%2766%27%20y1%3D%272%27%20x2%3D%2761%27%20y2%3D%2723%27%2F%3E%3Cline%20x1%3D%2784%27%20y1%3D%2730%27%20x2%3D%2779%27%20y2%3D%2751%27%2F%3E%3Cline%20x1%3D%2720%27%20y1%3D%2750%27%20x2%3D%2715%27%20y2%3D%2771%27%2F%3E%3Cline%20x1%3D%2752%27%20y1%3D%2762%27%20x2%3D%2747%27%20y2%3D%2783%27%2F%3E%3Cline%20x1%3D%2776%27%20y1%3D%2754%27%20x2%3D%2771%27%20y2%3D%2775%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  background-size: 90px 90px;
  animation: rainFar 1.05s linear infinite;
}

@keyframes rainNear {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(-34px, 150px, 0);
  }
}
@keyframes rainFar {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(-16px, 90px, 0);
  }
}

/* Haze does the work for fog and for overcast, at different strengths. */
.weather__haze {
  inset: 0;
  background: linear-gradient(
    to bottom,
    rgba(214, 222, 232, 0) 0%,
    rgba(214, 222, 232, 0.38) 46%,
    rgba(214, 222, 232, 0.62) 74%,
    rgba(214, 222, 232, 0.34) 100%
  );
  animation: hazeDrift 26s ease-in-out infinite alternate;
}
@keyframes hazeDrift {
  from {
    transform: translateX(-2%) scaleY(1);
  }
  to {
    transform: translateX(2%) scaleY(1.06);
  }
}

.weather__flash {
  inset: 0;
  background: #dce8ff;
}

/* ── Per condition ──────────────────────────────────────────── */

[data-weather='drizzle'] .weather__near {
  opacity: 0.4;
  animation-duration: 0.95s;
}

[data-weather='rain'] .weather__near {
  opacity: 0.85;
}
[data-weather='rain'] .weather__far {
  opacity: 0.55;
}

[data-weather='storm'] .weather__near {
  opacity: 1;
  animation-duration: 0.5s;
}
[data-weather='storm'] .weather__far {
  opacity: 0.7;
}
[data-weather='storm'] .weather__flash {
  animation: lightning 9s linear infinite;
}
@keyframes lightning {
  0%,
  92.5%,
  100% {
    opacity: 0;
  }
  93% {
    opacity: 0.5;
  }
  93.6% {
    opacity: 0.05;
  }
  94.2% {
    opacity: 0.34;
  }
  95.4% {
    opacity: 0;
  }
}

[data-weather='fog'] .weather__haze {
  opacity: 0.9;
}
[data-weather='cloudy'] .weather__haze {
  opacity: 0.24;
}

[data-weather='snow'] .weather__near {
  opacity: 0.9;
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27120%27%20height%3D%27120%27%3E%3Cg%20fill%3D%27%23eef4ff%27%20opacity%3D%270.7%27%3E%3Ccircle%20cx%3D%2714%27%20cy%3D%2716%27%20r%3D%271.9%27%2F%3E%3Ccircle%20cx%3D%2758%27%20cy%3D%2734%27%20r%3D%271.5%27%2F%3E%3Ccircle%20cx%3D%2796%27%20cy%3D%2712%27%20r%3D%272.1%27%2F%3E%3Ccircle%20cx%3D%2734%27%20cy%3D%2772%27%20r%3D%271.6%27%2F%3E%3Ccircle%20cx%3D%2778%27%20cy%3D%2792%27%20r%3D%271.9%27%2F%3E%3Ccircle%20cx%3D%27110%27%20cy%3D%2762%27%20r%3D%271.4%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  background-size: 120px 120px;
  animation: rainNear 7s linear infinite;
}

/* The sun loses its edge under cloud and all but disappears in fog. */
[data-weather='cloudy'] .sun {
  opacity: calc(var(--sun-opacity) * 0.45);
  filter: blur(6px);
}
[data-weather='rain'] .sun,
[data-weather='drizzle'] .sun {
  opacity: calc(var(--sun-opacity) * 0.3);
  filter: blur(9px);
}
[data-weather='fog'] .sun,
[data-weather='storm'] .sun {
  opacity: calc(var(--sun-opacity) * 0.16);
  filter: blur(12px);
}

/* Wet air carries the bulb further. The one thing rain improves. */
[data-weather='rain'] .bulb,
[data-weather='storm'] .bulb,
[data-weather='drizzle'] .bulb {
  --bulb-size: 420px;
}

/* Fog eats the far distance before it touches anything close. */
[data-weather='fog'] .art__far {
  opacity: 0.3;
}
[data-weather='fog'] .art__near,
[data-weather='fog'] .art__people {
  opacity: 0.86;
}

@media (prefers-reduced-motion: reduce) {
  .weather__near,
  .weather__far,
  .weather__haze,
  .weather__flash {
    animation: none;
  }
}
