/* ══════════ मंदिर रेडियो — theme tokens ══════════ */
:root {
  color-scheme: dark;      /* belt and braces with the meta tag */
  --cream: #f6ead8;
  --sand: #e8d5b5;
  --live: #4ade80;
  --glass: rgba(26, 15, 10, 0.55);
  --glass-border: rgba(246, 234, 216, 0.16);
  /* cover disc size. In rem so it scales with the fluid :root font —
     the seek row derives its indent from this, so the two cannot drift. */
  --disc: 3.25rem;
  font-size: 16px;
}

/* scene phases — sky, light, silhouettes */
[data-phase="morning"] {
  --glass: rgba(26, 15, 10, 0.80);
  --glass-border: rgba(246, 234, 216, 0.22);
  --sky-a: #ffd9a0; --sky-b: #ff9e5e; --sky-c: #d96f3f;
  --sil: #6b3a24; --sil-far: #9c5c38; --ghat: #a9705a;
  --river-a: #e8945e; --river-b: #b05c3c;
  --door: #ffce7a; --star-op: 0;
  --rays-op: 0.55; --birds-op: 0.8; --ff-op: 0; --win-op: 0.25;
  --door-glow: 14px;
}
[data-phase="day"] {
  --glass: rgba(26, 15, 10, 0.80);
  --glass-border: rgba(246, 234, 216, 0.22);
  --sky-a: #9fd4e8; --sky-b: #cbe6d9; --sky-c: #f2e3c0;
  --sil: #7a4a2e; --sil-far: #a06844; --ghat: #b57d5d;
  --river-a: #7fb9c9; --river-b: #4e8a9c;
  --door: #ffdf9e; --star-op: 0;
  --rays-op: 0; --birds-op: 1; --ff-op: 0; --win-op: 0.12;
  --door-glow: 8px;
}
[data-phase="evening"] {
  --glass: rgba(26, 15, 10, 0.62);
  --sky-a: #2b1740; --sky-b: #97365a; --sky-c: #f2803e;
  --sil: #2f1420; --sil-far: #57263a; --ghat: #6b3348;
  --river-a: #c25a3e; --river-b: #5c2340;
  --door: #ffb84d; --star-op: 0.6;
  --rays-op: 0; --birds-op: 0.3; --ff-op: 0.4; --win-op: 0.7;
  --door-glow: 26px;
}
[data-phase="night"] {
  --sky-a: #060a1e; --sky-b: #14203e; --sky-c: #27355c;
  --sil: #0c1226; --sil-far: #1c2742; --ghat: #263252;
  --river-a: #22335c; --river-b: #0c1430;
  --door: #ffa63e; --star-op: 1;
  --rays-op: 0; --birds-op: 0; --ff-op: 1; --win-op: 1;
  --door-glow: 32px;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
/* must outrank each component's own display value */
[hidden] { display: none !important; }

/* visible keyboard focus */
:focus-visible {
  outline: 2px solid #ffb85c;
  outline-offset: 3px;
  border-radius: 6px;
}

/* background tab: freeze every animation (battery on phones) */
body.page-hidden *, body.page-hidden *::before, body.page-hidden *::after {
  animation-play-state: paused !important;
}

/* calm mode: honor prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
  .grain, .rays, .bird, .firefly, .fdiya, .pulse::before,
  .smoke circle, .temple .flag,
  .thumb, .tap-hint span { animation: none !important; }
  .scene-svg, .stars, .diya-layer, .celestial, .birds, .fireflies { transition: none !important; transform: none !important; }
  .float-emoji, .ripple { animation-duration: 0.6s !important; }
  .sky, .far-bank, .ghat rect, .river { transition-duration: 0.5s !important; }
}
html {
  scroll-behavior: smooth;
  /* body has carried overflow-x: clip for a long time, but html never did — and
     the viewport's own scrolling is governed by the root, not by body. So
     anything escaping body sideways (a fixed ornament near the edge, the
     cabinet's antenna and lip, a drifting feather) still panned the page on a
     phone. Measured on a 375px-wide layout: scrollWidth 452 against a 375
     viewport, with every overflowing element position:fixed and none of them in
     normal flow — which is exactly the case body's clip cannot catch.

     `clip`, not `hidden`, for the same reason recorded on body below: `hidden`
     would make the root a scroll container and break position: sticky. */
  overflow-x: clip;
}
body {
  font-family: "Mukta", system-ui, sans-serif;
  background: var(--sky-a);
  color: var(--cream);
  min-height: 100dvh;
  /* `clip`, not `hidden`: `hidden` makes body a scroll container, which breaks
     sticky positioning and scrolling on Samsung Internet. `clip` just clips. */
  overflow-x: clip;
}
.mono { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; }
button { font-family: inherit; color: inherit; background: none; border: none; cursor: pointer; }
a { color: inherit; text-decoration: none; }

/* ══════════ scene ══════════ */
.scene { position: fixed; inset: 0; z-index: 0; overflow: hidden; }
.sky {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, var(--sky-a) 0%, var(--sky-b) 52%, var(--sky-c) 78%);
  transition: background 2.5s ease;
}
.stars { position: absolute; inset: 0 0 40% 0; opacity: var(--star-op); transition: opacity 2s; }
.star {
  position: absolute; width: 2px; height: 2px; border-radius: 50%;
  background: #fff; animation: twinkle 3s infinite alternate;
}
@keyframes twinkle { from { opacity: 0.25; } to { opacity: 1; } }

/* ambient tint from current cover art — each bhajan colours the scene */
.cover-tint {
  position: absolute; inset: -12%;
  background-size: cover; background-position: center;
  filter: blur(90px) saturate(1.3);
  opacity: 0; transition: opacity 3s ease, background-image 1.5s ease;
  mix-blend-mode: soft-light;
}
.cover-tint.on { opacity: 0.18; } /* subtle — 0.5 turned morning skies muddy red */

/* morning god-rays */
.rays {
  position: absolute; inset: -20% -10%;
  background: conic-gradient(from 200deg at 14% 18%,
    transparent 0deg, rgba(255, 236, 180, 0.28) 6deg, transparent 12deg,
    transparent 20deg, rgba(255, 236, 180, 0.2) 26deg, transparent 33deg,
    transparent 42deg, rgba(255, 236, 180, 0.24) 49deg, transparent 57deg,
    transparent 100deg);
  opacity: var(--rays-op, 0);
  transition: opacity 2.5s;
  animation: raysDrift 14s ease-in-out infinite alternate;
  pointer-events: none;
  -webkit-mask: radial-gradient(circle at 14% 18%, transparent 0 18%, #000 34%, transparent 82%);
          mask: radial-gradient(circle at 14% 18%, transparent 0 18%, #000 34%, transparent 82%);
}
@keyframes raysDrift { from { transform: rotate(-1.5deg); } to { transform: rotate(1.5deg); } }

/* day birds */
.birds { position: absolute; inset: 6% 0 55% 0; opacity: var(--birds-op, 0); transition: opacity 2.5s; }
.bird {
  position: absolute; width: 26px; height: 10px;
  animation: flyAcross linear infinite;
}
.bird::before, .bird::after {
  content: ""; position: absolute; top: 4px; width: 13px; height: 7px;
  border: solid 2.2px var(--sil); border-color: var(--sil) transparent transparent transparent;
  border-radius: 50% 50% 0 0;
}
.bird::after { left: 11px; }
.bird span { display: block; animation: flap 0.6s ease-in-out infinite alternate; }
@keyframes flyAcross {
  from { transform: translateX(-6vw) translateY(0); }
  50% { transform: translateX(50vw) translateY(-18px); }
  to { transform: translateX(106vw) translateY(6px); }
}

/* night fireflies */
.fireflies { position: absolute; inset: 30% 0 12% 0; opacity: var(--ff-op, 0); transition: opacity 2.5s; }
.firefly {
  position: absolute; width: 4px; height: 4px; border-radius: 50%;
  background: #ffe89a;
  box-shadow: 0 0 8px 3px rgba(255, 226, 130, 0.55);
  animation: ffFloat ease-in-out infinite alternate, ffBlink 2.4s ease-in-out infinite;
}
@keyframes ffFloat {
  from { transform: translate(0, 0); }
  to { transform: translate(34px, -26px); }
}
@keyframes ffBlink { 0%, 100% { opacity: 0.15; } 50% { opacity: 1; } }

.celestial {
  position: absolute; border-radius: 50%;
  transition: opacity 2s, transform 2s;
}
.sun {
  width: 110px; height: 110px; left: 12%; top: 16%;
  background: radial-gradient(circle, #fff3ce 0%, #ffce6b 55%, rgba(255, 176, 78, 0) 72%);
  box-shadow: 0 0 90px 40px rgba(255, 190, 90, 0.35);
  opacity: 0;
}
.moon {
  width: 72px; height: 72px; right: 14%; top: 12%;
  background: radial-gradient(circle at 38% 38%, #fdf6e3 0%, #d9cfae 70%);
  box-shadow: 0 0 60px 18px rgba(240, 235, 200, 0.25);
  opacity: 0;
}
[data-phase="morning"] .sun, [data-phase="day"] .sun { opacity: 1; }
[data-phase="evening"] .sun { opacity: 0.85; transform: translateY(160px) scale(1.35); }
[data-phase="evening"] .moon { opacity: 0.5; }
[data-phase="night"] .moon { opacity: 1; }

.scene-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.far-bank { fill: var(--sil-far); transition: fill 2.5s; }
.far-temples path { fill: var(--sil-far); transition: fill 2.5s; }
/* ellipse and circle were missing here, so the amalaka, kalash and shrine
   finials painted solid black instead of taking the silhouette colour */
.temple rect, .temple path, .temple ellipse, .temple circle,
.shrine rect, .shrine path, .tree path { fill: var(--sil); transition: fill 2.5s; }
.temple .flag {
  fill: #e8542f;
  transform-box: fill-box; transform-origin: left center;
  animation: flagWave 3.2s ease-in-out infinite;
}
@keyframes flagWave {
  0%, 100% { transform: rotate(0deg) scaleY(1); }
  35% { transform: rotate(-4deg) scaleY(0.94); }
  65% { transform: rotate(2.5deg) scaleY(1.04); }
}
.smoke circle {
  fill: #f6ead8; opacity: 0;
  transform-box: fill-box; transform-origin: center;
  animation: smokeRise 7s ease-out infinite;
}
.smoke circle:nth-child(2) { animation-delay: 2.3s; animation-duration: 8s; }
.smoke circle:nth-child(3) { animation-delay: 4.6s; animation-duration: 6.5s; }
@keyframes smokeRise {
  0% { transform: translateY(0) scale(0.6); opacity: 0; }
  12% { opacity: 0.35; }
  70% { opacity: 0.14; }
  100% { transform: translateY(-150px) translateX(18px) scale(2.2); opacity: 0; }
}
.temple .door, .shrine .door {
  fill: var(--door);
  filter: drop-shadow(0 0 var(--door-glow, 18px) var(--door));
  animation: doorGlow 4s ease-in-out infinite alternate;
  transition: filter 2.5s;
}
.temple .window {
  fill: var(--door);
  opacity: var(--win-op, 0.2);
  filter: drop-shadow(0 0 10px var(--door));
  transition: opacity 2.5s;
}
@keyframes doorGlow { from { opacity: 0.85; } to { opacity: 1; } }
.temple .bell { fill: #d9a441; }
.ghat rect { fill: var(--ghat); transition: fill 2.5s; }
.river { fill: var(--river-b); transition: fill 2.5s; }
.river-shine ellipse { fill: var(--river-a); opacity: 0.5; transition: fill 2.5s; }

/* floating diyas */
.diya-layer { position: absolute; left: 0; right: 0; bottom: 0; height: 22%; pointer-events: none; }
.fdiya {
  position: absolute; font-size: 18px; bottom: 20%;
  filter: drop-shadow(0 0 8px rgba(255, 170, 60, 0.9));
  animation: drift linear forwards;
}
@keyframes drift {
  from { transform: translateX(0) translateY(0); opacity: 0; }
  8% { opacity: 1; }
  90% { opacity: 1; }
  to { transform: translateX(48vw) translateY(12px); opacity: 0; }
}

.vignette {
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(rgba(2, 13, 23, 0.34) 0%, transparent 22% 62%, rgba(2, 9, 15, 0.55) 100%),
    radial-gradient(circle at 50% 46%, transparent 26%, rgba(0, 10, 20, 0.17) 58%, rgba(0, 7, 14, 0.6) 110%);
  box-shadow: inset 0 0 150px rgba(1, 8, 14, 0.38);
}
.grain {
  position: absolute; inset: 0; pointer-events: none;
  opacity: 0.13; mix-blend-mode: soft-light;
  background-image:
    repeating-radial-gradient(circle at 17% 31%, rgba(255,255,255,0.2) 0 0.45px, transparent 0.8px 3px),
    repeating-radial-gradient(circle at 76% 62%, rgba(0,0,0,0.28) 0 0.5px, transparent 0.9px 4px);
}

/* ══════════ UI layout ══════════ */
.ui {
  position: relative; z-index: 2;
  display: flex; flex-direction: column;
  min-height: 100dvh;
  padding: 1.1rem 1.2rem 0;
}
.top {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 0.8rem;
  position: relative; /* anchors the पंचांग popover under the date line */
}
/* flex:0 0 auto — this block sits in a nowrap flex row beside the दर्शक count and
   the chip row, and as a shrinkable item it could be squeezed narrower than its
   own text. nowrap on the date keeps "23 अगस्त 2026 · श्रावण एकादशी" on one line;
   at 184px it was wrapping to two lines at 1414px wide and three at 707px, which
   with the dotted underline beneath it reads as truncated text. */
.clock-wrap {
  display: flex; flex-direction: column; gap: 0.3rem;
  padding-top: 0.4rem; min-width: 4.5rem; flex: 0 0 auto;
}
.date-line { white-space: nowrap; }
.clock { font-size: 0.85rem; letter-spacing: 0.22em; }
.date-line {
  font-size: 0.72rem; font-weight: 600; color: #ffd98f;
  text-shadow: 0 2px 10px rgba(10, 4, 1, 0.8);
  border-left: 2px solid rgba(255, 184, 92, 0.55);
  padding-left: 0.45rem; line-height: 1.35;
}
.online {
  display: flex; align-items: center; gap: 0.45rem;
  font-size: 0.85rem; padding-top: 0.4rem;
}
.online-label { opacity: 0.75; }
.pulse { position: relative; width: 9px; height: 9px; }
.pulse::before, .pulse::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%; background: var(--live);
}
.pulse::before { animation: ping 1.6s cubic-bezier(0, 0, 0.2, 1) infinite; }
@keyframes ping { 75%, 100% { transform: scale(2.4); opacity: 0; } }

.chips { display: flex; flex-direction: column; align-items: flex-end; gap: 0.45rem; }
/* on a phone the row overflowed the viewport; let it wrap back under itself.
   max-width is what makes that wrap actually happen: .chips is a column flex with
   align-items:flex-end, which sizes each row to max-content, so flex-wrap alone
   never triggered and the row simply overflowed — measured 675px of buttons in a
   620px box at every viewport from 1180 to 2560, clipping the last control. */
.chip-row {
  display: flex; gap: 0.45rem; flex-wrap: wrap; justify-content: flex-end;
  max-width: 100%;
}
.chip {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;
  /* measured 42x44 on a 375px phone — under the 44x44 minimum on the short axis */
  min-width: 44px; min-height: 44px;
  padding: 0.42rem 0.75rem; border-radius: 999px;
  background: var(--glass); border: 1px solid var(--glass-border);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  font-size: 0.8rem; font-weight: 600; color: var(--cream);
  transition: border-color 0.2s, transform 0.15s;
}
.chip:hover { border-color: rgba(246, 234, 216, 0.45); transform: translateY(-1px); }
.chip b { color: #ffcf7d; }

/* hero */
.hero { text-align: center; padding-top: 2.6rem; }

/* time-aware greeting — the site should feel like it noticed you arrived */
.greeting {
  font-size: 1.05rem; font-weight: 600; letter-spacing: 0.01em;
  color: #ffd98f; margin-bottom: 0.5rem;
  text-shadow: 0 2px 14px rgba(10, 4, 1, 0.75);
  opacity: 0.96;
  animation: greetIn 1.1s ease 0.35s both;
}
@keyframes greetIn {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 0.96; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) { .greeting { animation: none; } }
[data-phase="morning"] .greeting, [data-phase="day"] .greeting {
  color: #7a4212; text-shadow: 0 1px 8px rgba(255, 242, 214, 0.6);
}
@media (max-width: 640px) { .greeting { font-size: 0.95rem; } }
.title {
  font-family: "Yatra One", "Mukta", sans-serif;
  font-size: clamp(4rem, 13vw, 9.5rem);
  line-height: 1.08; font-weight: 400; /* Devanagari top marks clip below ~1.05 */
  color: var(--cream);
  text-shadow: 0 8px 40px rgba(20, 8, 2, 0.7);
}
/* Hindi-first, readable size — this is the value proposition, not a footnote */
.tagline {
  margin-top: 0.9rem; font-size: 1.05rem; font-weight: 600;
  opacity: 0.95; /* no letter-spacing: it breaks Devanagari conjuncts */
  text-shadow: 0 2px 14px rgba(10, 4, 1, 0.7);
}
.trust-line {
  margin-top: 0.9rem; font-size: 0.85rem; font-weight: 600;
  color: #ffd98f;
}

.day-banner {
  display: inline-block; margin-top: 1.2rem;
  padding: 0.5rem 1.2rem; border-radius: 999px;
  background: var(--glass); border: 1px solid var(--glass-border);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  font-size: 0.95rem; font-weight: 600; color: #ffd98f;
}
.spacer { flex: 1; min-height: 2rem; }

/* rotating bhakti quote */
.quote {
  max-width: 34rem; margin: 1.4rem auto 0; padding: 0 1rem;
  font-size: 0.95rem; line-height: 1.7; color: var(--sand);
  text-shadow: 0 2px 14px rgba(10, 4, 1, 0.8);
  opacity: 0; transition: opacity 1.6s ease;
  min-height: 3.2em;
}
.quote.show { opacity: 0.92; }
.quote .src { display: block; margin-top: 0.25rem; font-size: 0.7rem; letter-spacing: 0.14em; text-transform: uppercase; opacity: 0.8; }
/* light skies need dark text — cream was invisible in morning/day */
[data-phase="morning"] .quote, [data-phase="day"] .quote {
  color: #3d2412; text-shadow: 0 1px 10px rgba(255, 242, 214, 0.65);
}
[data-phase="morning"] .tagline, [data-phase="day"] .tagline {
  color: #4a2c18; opacity: 0.95; text-shadow: 0 1px 8px rgba(255, 242, 214, 0.5);
}
[data-phase="morning"] .contact, [data-phase="day"] .contact,
[data-phase="morning"] .kbd-hints, [data-phase="day"] .kbd-hints,
[data-phase="morning"] .date-line, [data-phase="day"] .date-line,
[data-phase="morning"] .clock-wrap, [data-phase="day"] .clock-wrap,
[data-phase="morning"] .online, [data-phase="day"] .online {
  color: #3d2412; text-shadow: 0 1px 8px rgba(255, 242, 214, 0.5);
}

/* WhatsApp card */
.wa-card {
  position: relative; display: flex; align-items: center; gap: 0.85rem;
  width: 100%; max-width: 30rem; margin: 0 auto 1rem;
  padding: 0.8rem 1rem; border-radius: 1.1rem; overflow: hidden;
  background: var(--glass); border: 1px solid var(--glass-border);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  transition: border-color 0.2s;
}
.wa-card:hover { border-color: rgba(37, 211, 102, 0.6); }
.wa-stripe { position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: #25d366; }
.wa-icon {
  flex-shrink: 0; display: grid; place-items: center;
  width: 42px; height: 42px; border-radius: 0.8rem; background: #25d366;
  box-shadow: 0 6px 20px -6px rgba(37, 211, 102, 0.5);
}
.wa-text { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 0.1rem; }
/* the pitch must be readable — it was truncating to "रोज़ सुबह की आरती, सीधे W…" */
.wa-text b { font-size: 0.9rem; line-height: 1.3; }
.wa-text small { font-size: 0.72rem; opacity: 0.8; line-height: 1.35; }
.wa-join {
  flex-shrink: 0; padding: 0.45rem 0.85rem; border-radius: 999px;
  background: #25d366; color: #05391a; font-size: 0.78rem; font-weight: 700;
}

/* home-screen install card */
.install-card {
  position: relative; display: flex; align-items: center; gap: 0.8rem;
  width: 100%; max-width: 30rem; margin: 0 auto 0.8rem;
  padding: 0.75rem 0.9rem; border-radius: 1.1rem;
}
.ic-icon {
  display: grid; place-items: center; flex-shrink: 0;
  width: 40px; height: 40px; border-radius: 0.8rem;
  background: rgba(255, 184, 92, 0.18); font-size: 1.25rem;
}
.ic-text { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 0.1rem; }
/* Devanagari headings must wrap, not ellipsise — "मंदिर को अपनी h…" is not a label */
.ic-text b { font-size: 0.86rem; line-height: 1.3; }
.ic-text small { font-size: 0.7rem; opacity: 0.7; line-height: 1.3; }
.ic-add {
  flex-shrink: 0; padding: 0.45rem 1rem; border-radius: 999px;
  background: var(--cream); color: #2b160a; font-size: 0.8rem; font-weight: 700;
  transition: transform 0.15s;
}
.ic-add:hover { transform: scale(1.05); }
.ic-close { flex-shrink: 0; opacity: 0.6; }
body.zen .install-card { opacity: 0 !important; pointer-events: none !important; }

/* reactions */
.reactions {
  display: flex; justify-content: center; gap: 0.55rem;
  /* The player is sticky at the bottom and paints over whatever shares that
     band, so the column's last item must end above it. Padding on .ui cannot
     do this — it shrinks the sticky containing block and lifts the player off
     the bottom edge, which is what parked it mid-screen in 2.6.1. */
  margin-bottom: calc(var(--player-h, 11rem) + 1rem);
}
.reactions button {
  font-size: 1.25rem; width: 2.7rem; height: 2.7rem; border-radius: 50%;
  background: var(--glass); border: 1px solid var(--glass-border);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  transition: transform 0.15s, border-color 0.2s;
}
.reactions button:hover { transform: translateY(-3px) scale(1.08); border-color: rgba(246, 234, 216, 0.4); }
.reactions button:active { transform: scale(0.92); }

/* ॐ — real glyph, gold, glowing (emoji 🕉️ renders badly on many devices) */
.om-btn {
  font-family: "Yatra One", serif;
  font-size: 1.45rem !important;
  color: #ffd98f;
  text-shadow: 0 0 12px rgba(255, 184, 92, 0.8);
}
.om-btn:hover { text-shadow: 0 0 20px rgba(255, 184, 92, 1); }

/* share as labeled pill — recognizable, not a mystery circle */
.share-pill {
  width: auto !important; border-radius: 999px !important;
  padding: 0 1rem;
  display: inline-flex; align-items: center; gap: 0.45rem;
  font-size: 0.85rem !important; font-weight: 700; color: var(--cream);
}
.float-emoji.om {
  font-family: "Yatra One", serif; color: #ffd98f;
  text-shadow: 0 0 18px rgba(255, 184, 92, 0.95), 0 0 40px rgba(255, 140, 46, 0.5);
}
.float-layer { position: fixed; inset: 0; pointer-events: none; z-index: 30; }
.float-emoji {
  position: absolute; bottom: 16%; font-size: 1.6rem;
  animation: floatUp 2.6s ease-out forwards;
}
@keyframes floatUp {
  from { transform: translateY(0) scale(0.7) rotate(0deg); opacity: 0; }
  15% { opacity: 1; }
  to { transform: translateY(-46vh) scale(1.25) rotate(14deg); opacity: 0; }
}

/* ══════════ wallpaper (zen) mode ══════════ */
body.zen .chips, body.zen .online, body.zen .wa-card,
body.zen .reactions, body.zen .share-pop,
body.zen .footer, body.zen .panel {
  opacity: 0 !important; pointer-events: none !important;
  transition: opacity 0.8s ease;
}
/* not fully 0 — iOS suspends playback inside invisible ancestors */
body.zen .player-wrap {
  opacity: 0.02 !important; pointer-events: none !important;
  transition: opacity 0.8s ease;
}
body.zen .day-banner { opacity: 0.55; }
body.zen .quote { opacity: 0.95; }
body.zen .hero { padding-top: 6rem; }
body.zen { cursor: none; }
body.zen.zen-awake { cursor: default; }

/* mini media pill — macOS-style hover controls */
.zen-bar {
  visibility: hidden;
  position: fixed; left: 50%; bottom: 2.2rem; z-index: 50;
  transform: translateX(-50%) translateY(10px);
  display: flex; align-items: center; gap: 0.35rem;
  padding: 0.45rem 0.6rem 0.45rem 1rem;
  width: max-content; max-width: calc(100vw - 1.5rem);
  border-radius: 999px;
  background: rgba(24, 13, 8, 0.92);
  border: 1px solid var(--glass-border);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 14px 40px rgba(5, 2, 0, 0.6);
  opacity: 0; pointer-events: none;
  transition: opacity 0.35s, transform 0.35s;
}
body.zen.zen-awake .zen-bar { visibility: visible; opacity: 1; pointer-events: auto; transform: translateX(-50%) translateY(0); }
.zen-title {
  flex: 0 1 auto; min-width: 0; max-width: 14rem; margin-right: 0.4rem;
  font-size: 0.8rem; font-weight: 600; color: var(--cream);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.zen-play {
  display: grid; place-items: center;
  width: 2.4rem; height: 2.4rem; border-radius: 50%;
  background: var(--cream); color: #2b160a;
}
.zen-exit { margin-left: 0.3rem; font-size: 0.85rem; opacity: 0.7; }
.zen-exit:hover { opacity: 1; }

/* ══════════ tooltips ══════════ */
[data-tip] { position: relative; }
[data-tip]::after {
  content: attr(data-tip);
  position: absolute; left: 50%; bottom: calc(100% + 8px);
  transform: translateX(-50%) translateY(4px);
  padding: 0.35rem 0.65rem; border-radius: 0.5rem;
  background: rgba(18, 10, 6, 0.95);
  border: 1px solid rgba(246, 234, 216, 0.2);
  color: var(--cream); font-size: 0.68rem; font-weight: 500;
  white-space: nowrap; pointer-events: none;
  opacity: 0; transition: opacity 0.18s 0.25s, transform 0.18s 0.25s;
  z-index: 60;
}
[data-tip]:hover::after, [data-tip]:focus-visible::after {
  opacity: 1; transform: translateX(-50%) translateY(0);
}
/* header chips: tooltip below (no clipping at screen top) */
.top [data-tip]::after { bottom: auto; top: calc(100% + 8px); transform: translateX(-50%) translateY(-4px); }
.top [data-tip]:hover::after, .top [data-tip]:focus-visible::after { transform: translateX(-50%) translateY(0); }
/* Tooltips used to be switched off wholesale on touch, which meant every phone
   — most of this audience — got no explanation of any control, ever. They now
   show on :active instead, so a press-and-hold names the button before the
   finger lifts. Hover devices keep the hover behaviour unchanged. */
@media (hover: none) {
  [data-tip]::after { transition: opacity 0.12s, transform 0.12s; }
  [data-tip]:active::after { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* diya button with count badge */
.diya-btn { position: relative; }
.diya-badge {
  position: absolute; top: -4px; right: -6px;
  min-width: 1.15rem; height: 1.15rem; padding: 0 0.25rem;
  display: grid; place-items: center;
  border-radius: 999px; font-size: 0.58rem; font-weight: 700;
  background: #ffb85c; color: #2b160a;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
.dot-sep { opacity: 0.4; margin: 0 0.35rem; }
.bmc-link { color: #ffd98f; }
.bmc-link:hover { text-decoration: underline; }


.toast.clickable { pointer-events: auto; cursor: pointer; }

/* ══════════ playlist panel ══════════ */
/* Docked above the player. --player-h is measured in JS, because the
   player's height changes between the one-row desktop pill and the
   two-row mobile layout — a fixed offset let the panel sit on top of it. */
.panel {
  position: fixed; left: 50%; z-index: 42;
  bottom: calc(var(--player-h, 9rem) + 0.6rem);
  width: min(94vw, 36rem);
  max-height: min(52dvh, 28rem);
  transform: translate(-50%, 12px) scale(0.98);
  transform-origin: bottom center;
  border-radius: 1.4rem;
  display: flex; flex-direction: column;
  overflow: hidden;
  opacity: 0; visibility: hidden;
  transition: transform 0.28s cubic-bezier(0.32, 0.72, 0.2, 1), opacity 0.22s, visibility 0.28s;
  box-shadow: 0 -18px 60px rgba(5, 2, 0, 0.65);
}
/* One layout, both modes. The vertical channel column was built for the TV's
   MENU and Rohit asked for it everywhere — which also fixes two bugs that came
   with the wrapping-pill version: the footer had no grid area of its own, so in
   TV mode it fell into an implicit cell inside the 11.5rem channel column and
   wrapped to nonsense, and in normal mode it was a flex sibling that took its
   height straight out of the track list and then spilled past the rounded edge.
   Named areas mean neither can happen: the list owns minmax(0, 1fr) and the
   footer owns its own row. */
.panel {
  display: grid;
  grid-template-columns: 11.5rem minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr) auto auto;
  grid-template-areas: "head head" "tabs list" "foot foot" "share share";
}
.panel .panel-head { grid-area: head; }
.panel .ptabs-wrap {
  grid-area: tabs; min-height: 0; overflow-y: auto;
  border-right: 1px solid rgba(246, 234, 216, 0.14);
}
.panel .plist { grid-area: list; min-height: 0; }
/* A fixed cap, never a percentage. `max-height: 40%` resolved against this
   element's own grid track: the track sized to the content (132px), the element
   was then capped to 53px, and the 79px difference stayed in the track as dead
   space above the share button — measured, not guessed. */
.panel .panel-foot { grid-area: foot; min-height: 0; max-height: 11rem; overflow-y: auto; }
.panel .panel-share { grid-area: share; }
/* channels read as a column, not a wrapping row */
.panel .ptabs {
  flex-direction: column; flex-wrap: nowrap;
  align-items: stretch; gap: 0.16rem;
  overflow-x: visible; padding: 0.45rem 0.55rem 0.6rem;
}
.panel .ptabs > * { width: 100%; justify-content: flex-start; border-radius: 0.4rem; text-align: left; }
.panel .ptabs-wrap::after { display: none; }   /* a horizontal strip's scroll shadow */

/* Collapsed by default. Open it and it scrolls inside its own row rather than
   pushing the list out of the panel. */
.panel-tv > summary {
  cursor: pointer; list-style: none; padding: 0.1rem 0;
  font-size: 0.62rem; letter-spacing: 0.06em; opacity: 0.75;
}
.panel-tv > summary::-webkit-details-marker { display: none; }
.panel-tv > summary::before { content: "▸ "; opacity: 0.7; }
.panel-tv[open] > summary::before { content: "▾ "; }

.panel.open { transform: translate(-50%, 0) scale(1); opacity: 1; visibility: visible; }
/* near-opaque: page content must not bleed through the sheet
   (.panel.glass beats the later .glass background rule) */
.panel.glass { background: rgba(24, 13, 8, 0.97); }
.panel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.9rem 1.1rem 0.4rem;
}
.panel-title { font-weight: 700; font-size: 0.95rem; }
.panel-tools { display: flex; align-items: center; gap: 0.35rem; }
.track-count { font-size: 0.62rem; letter-spacing: 0.12em; opacity: 0.55; margin-right: 0.3rem; }
.prow-num {
  width: 1.2rem; flex-shrink: 0; text-align: right;
  font-size: 0.62rem; opacity: 0.4;
}
.prow.now .prow-num { color: #ffb85c; opacity: 1; }
.panel-share {
  display: flex; align-items: center; justify-content: center; gap: 0.5rem;
  margin: 0 0.9rem 0.9rem; padding: 0.65rem;
  border-radius: 0.9rem; border: 1px dashed rgba(246, 234, 216, 0.25);
  font-size: 0.8rem; font-weight: 600; color: var(--cream);
  transition: border-color 0.2s, background 0.2s;
}
.panel-share:hover { border-color: rgba(255, 184, 92, 0.6); background: rgba(255, 184, 92, 0.08); }
.panel-share .mono { font-size: 0.62rem; opacity: 0.6; }
.ptabs {
  display: flex; gap: 0.4rem; overflow-x: auto;
  padding: 0.4rem 1.1rem 0.7rem;
  scrollbar-width: none;
}
.ptabs::-webkit-scrollbar { display: none; }

@media (min-width: 768px) {
  .ptabs { flex-wrap: wrap; overflow-x: visible; }
  .panel.max .ptabs { flex-wrap: wrap; }
  .plist { padding-bottom: 0.4rem; }
}
/* small phones: panel takes more height, tabs stay one scrollable row */
@media (max-width: 640px) {
  .panel { max-height: 62dvh; width: 96vw; }
}
.ptab {
  flex-shrink: 0; padding: 0.4rem 0.8rem; border-radius: 999px;
  font-size: 0.78rem; font-weight: 600; color: var(--cream);
  background: rgba(246, 234, 216, 0.08);
  border: 1px solid transparent;
  transition: background 0.2s, border-color 0.2s;
}
.ptab:hover { background: rgba(246, 234, 216, 0.16); }
.ptab.active { background: rgba(255, 184, 92, 0.22); border-color: rgba(255, 184, 92, 0.5); color: #ffd98f; }
.plist { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 0 0.6rem 1rem; overscroll-behavior: contain; }
.prow {
  display: flex; align-items: center; gap: 0.7rem;
  padding: 0.45rem 0.5rem; border-radius: 0.8rem;
  transition: background 0.15s;
}
.prow:hover { background: rgba(246, 234, 216, 0.07); }
.prow.now { background: rgba(255, 184, 92, 0.14); }
.prow img { width: 36px; height: 36px; border-radius: 0.5rem; object-fit: cover; flex-shrink: 0; }
.prow-meta {
  flex: 1; min-width: 0; text-align: left;
  display: flex; flex-direction: column; gap: 0.05rem;
}
.prow-meta b { font-size: 0.84rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.prow-meta small { font-size: 0.68rem; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.prow.now .prow-meta b { color: #ffd98f; }
.pstar { font-size: 1rem; padding: 0.35rem; opacity: 0.6; transition: transform 0.15s, opacity 0.15s; }
.pstar:hover { transform: scale(1.25); opacity: 1; }
.pstar.faved { opacity: 1; }

/* ══════════ depth: parallax + tilt + ripple ══════════ */
.scene { --px: 0px; --py: 0px; }
.scene-svg, .diya-layer { transform: translate(calc(var(--px) * 1), calc(var(--py) * 0.6)); transition: transform 0.2s ease-out; }
.stars { transform: translate(calc(var(--px) * -0.5), calc(var(--py) * -0.3)); transition: opacity 2s, transform 0.2s ease-out; }
.celestial { transition: opacity 2s, transform 0.25s ease-out; }
.sun { transform: translate(calc(var(--px) * -0.8), calc(var(--py) * -0.5)); }
[data-phase="evening"] .sun { transform: translate(calc(var(--px) * -0.8), calc(160px + var(--py) * -0.5)) scale(1.35); }
.moon { transform: translate(calc(var(--px) * -0.8), calc(var(--py) * -0.5)); }
.birds, .fireflies { transform: translate(calc(var(--px) * 1.4), calc(var(--py) * 0.8)); transition: opacity 2.5s, transform 0.2s ease-out; }

.tilt { transform-style: preserve-3d; will-change: transform; }

.ripple {
  position: fixed; z-index: 44; pointer-events: none;
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid rgba(255, 200, 110, 0.85);
  box-shadow: 0 0 18px rgba(255, 190, 90, 0.5), inset 0 0 8px rgba(255, 190, 90, 0.4);
  transform: translate(-50%, -50%) scale(0.4);
  animation: rippleOut 0.9s ease-out forwards;
}
@keyframes rippleOut {
  to { transform: translate(-50%, -50%) scale(5.5); opacity: 0; }
}

/* share popover */
.share-pop {
  visibility: hidden;
  position: fixed; left: 50%; bottom: 12.5rem; z-index: 38;
  transform: translateX(-50%) translateY(8px) scale(0.96);
  display: flex; flex-direction: column; gap: 0.15rem;
  padding: 0.5rem; border-radius: 1rem;
  background: rgba(24, 13, 8, 0.97);
  opacity: 0; pointer-events: none;
  transition: opacity 0.2s, transform 0.2s;
  box-shadow: 0 16px 48px rgba(5, 2, 0, 0.6);
}
.share-pop.open { visibility: visible; opacity: 1; pointer-events: auto; transform: translateX(-50%) translateY(0) scale(1); }
.share-item {
  display: flex; align-items: center; gap: 0.65rem;
  padding: 0.5rem 0.9rem 0.5rem 0.5rem; border-radius: 0.7rem;
  font-size: 0.84rem; font-weight: 600; color: var(--cream);
  transition: background 0.15s;
}
.share-item:hover { background: rgba(246, 234, 216, 0.1); }
/* the YouTube entry is an <a>, not a <button>, so it arrives underlined */
a.share-item { text-decoration: none; cursor: pointer; }
.share-ic {
  display: grid; place-items: center;
  width: 1.9rem; height: 1.9rem; border-radius: 0.55rem;
  color: #fff; font-size: 0.95rem; font-weight: 700;
}

/* toast */
.toast {
  position: fixed; left: 50%; top: 4.4rem; z-index: 45;
  transform: translateX(-50%) translateY(-12px);
  padding: 0.7rem 1.3rem; border-radius: 999px;
  background: var(--glass); border: 1px solid rgba(255, 184, 92, 0.4);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  color: #ffd98f; font-size: 0.82rem; letter-spacing: 0.04em;
  opacity: 0; pointer-events: none;
  transition: opacity 0.3s, transform 0.3s;
  max-width: 88vw; text-align: center;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* temple bell ring */
.bell-btn:active { animation: bellShake 0.5s ease; }
.temple .bell { transform-box: fill-box; transform-origin: center top; }
.temple .bell.ringing { animation: bellShake 1.4s ease; }
@keyframes bellShake {
  0% { transform: rotate(0); }
  20% { transform: rotate(14deg); }
  40% { transform: rotate(-12deg); }
  60% { transform: rotate(8deg); }
  80% { transform: rotate(-5deg); }
  100% { transform: rotate(0); }
}
.install-chip { border-color: rgba(255, 184, 92, 0.55); color: #ffd98f; }

/* ══════════ player ══════════ */
.player-wrap {
  position: sticky; bottom: 0; z-index: 20;
  padding-bottom: max(1rem, env(safe-area-inset-bottom));
  width: 100%; max-width: 44rem; margin: 0 auto;
}
.glass {
  background: var(--glass); border: 1px solid var(--glass-border);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
}
.player {
  display: flex; align-items: center; gap: 0.8rem;
  /* 999px made sense when this was a low lozenge. At 90px tall the left end
     became a 45px-radius curve, and a 52px disc set 8.8px in had under 2px of
     clearance where that curve is tightest — hence the disc reading as though
     it were breaking out of the pill. A softer radius and even padding on all
     four sides fixes both. */
  border-radius: 1.6rem; padding: 0.7rem 1rem;
  box-shadow: 0 18px 50px -12px rgba(8, 3, 1, 0.65);
}
@keyframes spin { to { transform: rotate(360deg); } }
.meta { min-width: 0; flex: none; display: flex; align-items: center; gap: 0.3rem; }
.meta__text { min-width: 0; flex: 1 1 auto; }
.song {
  font-size: 0.88rem; font-weight: 700;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.artist { font-size: 0.72rem; opacity: 0.65; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* desktop keeps the single-row pill: seek sits under the title column */
/* inside .player__mid now, so it no longer needs to clear the cover disc or
   claim its own full-width row */
.seek-row {
  display: flex; align-items: center; gap: 0.5rem;
  flex: none; margin-top: 0;
}
.player__mid {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; gap: 0.15rem;
}
.player { flex-wrap: wrap; }
/* The controls row took only its content width — 361px inside a 576px player,
   so 36% of the pill was trailing dead space, which is the lopsided shape people
   noticed. Full width now, with the volume slider pushed to the right edge by an
   auto margin so the space is occupied rather than merely stretched. */
/* a cluster at the right of the band, not a row of its own */
.controls {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 0.3rem;
}
.controls .vol-range { max-width: 5rem; }
/* on a phone there is no room for the cluster beside the title, so it drops to
   its own line and spreads — the one case where a second row is correct */
@media (max-width: 640px) {
  .controls { flex: 1 1 100%; justify-content: space-between; gap: 0.15rem; }
  /* Six 44px targets leave nothing for a slider — it was being squeezed to 0x0,
     which is a broken control rather than a small one. A phone already has a
     volume rocker, so the on-screen slider is redundant here; मौन still works. */
  .controls .vol-range { display: none; }
}
.time { font-size: 0.58rem; opacity: 0.65; flex-shrink: 0; }

.range {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 4px; border-radius: 999px;
  background: linear-gradient(to right, #ffb85c var(--p), rgba(246, 234, 216, 0.22) var(--p));
  outline: none; cursor: pointer;
}
.range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--cream); box-shadow: 0 1px 6px rgba(0, 0, 0, 0.5);
}
.range::-moz-range-thumb {
  width: 12px; height: 12px; border-radius: 50%; border: none;
  background: var(--cream); box-shadow: 0 1px 6px rgba(0, 0, 0, 0.5);
}

/* Wrapping is the safety net: on a very small phone seven buttons genuinely do
   not fit on one line, and two centred rows of full-size buttons beat one row
   of squashed ones. */
.controls {
  display: flex; align-items: center; justify-content: center;
  gap: 0.3rem; flex-shrink: 0; flex-wrap: wrap; row-gap: 0.45rem;
}
/* tighten just enough that the usual 360–414px phone still gets one row */
/* Seven buttons at 40px need 309px and the row had 306, so it wrapped one
   button onto a line of its own — which looks more broken than a tight row.
   The three missing pixels come back off the player's side padding rather than
   out of the buttons, which are already at the smallest they should be. */
@media (max-width: 420px) {
  .controls .icon-btn { min-width: 40px; min-height: 40px; width: 40px; height: 40px; }
}
.icon-btn {
  display: grid; place-items: center;
  width: 2.1rem; height: 2.1rem; border-radius: 50%;
  transition: background 0.2s;
}
.icon-btn:hover { background: rgba(246, 234, 216, 0.12); }
.icon-btn.active { background: rgba(255, 184, 92, 0.22); color: #ffd98f; }
.play-btn {
  display: grid; place-items: center;
  /* Never shrink the primary control. .icon-btn carries min-width: 44px and
     holds its size, but this had none — so when the row grew to seven buttons
     flex took the difference out of the one button that would give, and the
     play circle came out 23px wide and 54 tall. An ellipse where the ▶ is. */
  flex: none;
  width: 3rem; height: 3rem; border-radius: 50%;
  background: var(--cream); color: #2b160a;
  box-shadow: 0 6px 22px -6px rgba(246, 234, 216, 0.45);
  transition: transform 0.15s;
}
.play-btn:hover { transform: scale(1.06); }
.play-btn:active { transform: scale(0.94); }
.vol { display: flex; align-items: center; gap: 0.3rem; flex-shrink: 0; }
.vol-range { width: 4rem; }
/* Under the player, as key-caps — the busdriver.wtf pattern. They lived in the
   footer before, which is to say nobody read them. Keys look like keys so the
   row scans without being read. */
.kbd-hints {
  position: fixed; left: 50%; transform: translateX(-50%);
  /* clear of the player, not tucked into it — --player-h is its full height */
  bottom: calc(var(--player-h, 9rem) + 0.45rem);
  z-index: 41; display: flex; flex-wrap: wrap; justify-content: center;
  gap: 0.15rem 0.9rem; width: min(94vw, 40rem);
  font-size: 0.6rem; letter-spacing: 0.06em; opacity: 0.55;
  pointer-events: none; transition: opacity 0.3s;
}
.kbd-hints:hover { opacity: 0.9; }
.kbd-hints span { display: inline-flex; align-items: center; gap: 0.3rem; white-space: nowrap; }
.kbd-hints kbd {
  font: inherit; font-size: 0.58rem; line-height: 1;
  padding: 0.24rem 0.36rem; border-radius: 0.28rem;
  background: rgba(246, 234, 216, 0.1);
  border: 1px solid rgba(246, 234, 216, 0.22);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.4);
}
/* The set covers this row when it opens, and half a keyboard hint peeking out
   from behind a wooden cabinet reads as breakage rather than as a hint. Nothing
   is lost by dropping them there: the TV carries its own controls — POWER, CH,
   VOL, MENU — and the keys keep working whether or not the row is on screen.
   visibility, not opacity alone, so they leave the tab order with the pixels. */
body.video-on .kbd-hints { opacity: 0; visibility: hidden; }

/* the one worth finding in a hurry */
.kbd-panic kbd { border-color: rgba(255, 140, 110, 0.5); background: rgba(255, 120, 90, 0.14); }
.kbd-panic { opacity: 1; }
.contact { text-align: center; margin-top: 0.3rem; font-size: 0.66rem; opacity: 0.75; }
.contact a:hover { opacity: 1; text-decoration: underline; }

/* tap hint */
/* First impression: scene + title + one instruction, nothing else.
   The rest of the UI arrives only after the user starts the radio. */
body:not(.started) .install-card,
body:not(.started) .wa-card,
body:not(.started) .reactions,
body:not(.started) .kbd-hints,
body:not(.started) .footer {
  opacity: 0; pointer-events: none;
}
.install-card, .wa-card, .reactions, .kbd-hints, .footer {
  transition: opacity 0.7s ease;
}

.tap-hint {
  position: fixed; inset: 0; z-index: 40;
  display: grid; place-items: end center;
  padding-bottom: 26vh;
  background: radial-gradient(ellipse at center 74%, rgba(12, 5, 2, 0.45) 0%, transparent 62%);
  cursor: pointer; transition: opacity 0.4s;
}
.tap-hint.loading span::before {
  content: ""; display: inline-block;
  width: 0.85em; height: 0.85em; margin-right: 0.55em;
  border: 2px solid rgba(246, 234, 216, 0.35);
  border-top-color: #ffd98f; border-radius: 50%;
  vertical-align: -0.1em;
  animation: spin 0.8s linear infinite;
}
.tap-hint span {
  padding: 0.8rem 1.4rem; border-radius: 999px;
  background: var(--glass); border: 1px solid var(--glass-border);
  font-size: 0.8rem; letter-spacing: 0.12em;
  animation: hintPulse 1.8s ease-in-out infinite;
}
@keyframes hintPulse { 50% { transform: scale(1.05); } }
.tap-hint.hidden { opacity: 0; pointer-events: none; }

/* live video disc — iOS requires a genuinely visible player, so the
   iframe itself is the round "cover art" in the bar (like the other
   sites of this genre: horn-ok-please shows it full-panel,
   nostalgiahits shows a 200px source deck) */
.yt-stage {
  position: relative; width: var(--disc); height: var(--disc);
  border-radius: 50%; overflow: hidden; flex-shrink: 0;
  background: #000;
  box-shadow: 0 4px 14px rgba(5, 2, 0, 0.5);
}
.yt-stage iframe {
  position: absolute; top: 50%; left: 50%;
  width: 120px !important; height: 68px !important;
  transform: translate(-50%, -50%);
  border: 0; pointer-events: none;
}

/* ══════════ footer ══════════ */
.footer {
  position: relative; z-index: 2;
  border-top: 1px solid rgba(246, 234, 216, 0.12);
  background: rgba(16, 8, 4, 0.82); backdrop-filter: blur(8px);
  /* clears the sticky player. The buffer was 2rem on top of the full wrap
     height; 1rem is enough once the contact line is no longer riding along
     inside that wrap and inflating the measurement. */
  padding: 2.6rem 1.4rem calc(var(--player-h, 11rem) + 1rem);
}
.foot-inner { max-width: 44rem; margin: 0 auto; }
.foot-title { font-family: "Yatra One", sans-serif; font-size: 1.7rem; }
.foot-sub { font-size: 0.6rem; letter-spacing: 0.3em; opacity: 0.6; margin-top: 0.2rem; }
.foot-desc { margin-top: 1.1rem; max-width: 30rem; font-size: 0.88rem; line-height: 1.65; opacity: 0.85; }
.foot-kicker { margin-top: 2rem; font-size: 0.62rem; letter-spacing: 0.28em; text-transform: uppercase; opacity: 0.6; }
.foot-links {
  margin-top: 0.8rem; display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: 0.45rem 1.4rem; font-size: 0.86rem;
}
.foot-links span { opacity: 0.9; }
.foot-links b { color: #ffd98f; font-weight: 600; }
.foot-legal { margin-top: 2.2rem; font-size: 0.72rem; line-height: 1.7; opacity: 0.55; display: grid; gap: 0.7rem; }
.foot-legal a { text-decoration: underline; text-underline-offset: 3px; }
.copyright { letter-spacing: 0.2em; text-transform: uppercase; font-size: 0.62rem; }

/* ══════════ mobile ══════════ */
@media (max-width: 640px) {
  .chip span { display: none; }
  .online-label { display: none; }
  .hero { padding-top: 2.2rem; }
  .vol-range { display: none; }
  .kbd-hints { display: none; }
  .tagline { font-size: 0.95rem; letter-spacing: 0.04em; }
  .trust-line { font-size: 0.78rem; }
  /* every tap target clears 44px for older thumbs */
  .chip { min-height: 2.75rem; padding: 0.42rem 0.85rem; }
  .reactions button { width: 2.9rem; height: 2.9rem; }
  .tagline { font-size: 0.55rem; letter-spacing: 0.26em; }
  .toast { top: auto; bottom: 13.5rem; }
  .toast.show { transform: translateX(-50%) translateY(0); }
  .share-pill span { display: none; }
  .share-pill { width: 2.7rem !important; padding: 0; justify-content: center; }
  /* Phones get a two-row player: a one-line pill cannot hold a cover,
     a Devanagari title, a seek bar and five controls in 375px — the
     seek bar was collapsing to 0px and the title to ~60px. */
  .player {
    --disc: 2.875rem;
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-areas: "cover meta" "seek seek" "controls controls";
    gap: 0.5rem 0.7rem;
    border-radius: 1.4rem;
    padding: 0.8rem 0.9rem 0.6rem;
  }
  .yt-stage { grid-area: cover; }
  .meta { grid-area: meta; align-self: center; }
  .song { font-size: 0.92rem; white-space: normal; line-height: 1.25; max-height: 2.5em; overflow: hidden; }
  .artist { font-size: 0.75rem; }
  .seek-row { grid-area: seek; margin-top: 0; gap: 0.6rem; padding-left: 0; padding-right: 0; }
  #seek { flex: 1 1 auto; min-width: 0; }
  .time { font-size: 0.65rem; }
  .controls {
    grid-area: controls;
    justify-content: space-between; width: 100%; gap: 0.2rem;
  }
  /* Seven buttons need 313.6px and the row had 305.8, so one wrapped onto a
     line of its own. The 8px comes back off the player's side padding rather
     than out of the buttons — they are already at the smallest they should be. */
  .player { padding-inline: 0.6rem; }
  /* volume/queue join the controls row so every target clears 44px */
  .vol { position: static; }
  .icon-btn { width: 2.75rem; height: 2.75rem; }
  .play-btn { width: 3.4rem; height: 3.4rem; }
  .quote { font-size: 0.85rem; min-height: 4.2em; }
  .date-line { font-size: 0.66rem; }
}

/* big screens: scale whole rem-based UI up */
@media (min-width: 1800px) {
  :root { font-size: 19px; }
  .player-wrap { max-width: 48rem; }
  .wa-card { max-width: 33rem; }
}
@media (min-width: 2400px) {
  :root { font-size: 23px; }
  .title { font-size: clamp(4rem, 11vw, 12rem); }
}
@media (min-width: 3200px) {
  :root { font-size: 28px; }
}

/* footer listening links (moved out of the header — they were the
   brightest thing above the fold and led users off the site) */
.foot-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 2rem 0 0; }
.foot-chip { font-size: 0.78rem; }

/* gentle "touch for sound" nudge shown while muted autoplay is running */
.sound-hint {
  position: fixed; left: 50%; z-index: 41;
  bottom: calc(11rem + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  padding: 0.6rem 1.1rem; border-radius: 999px;
  background: rgba(24, 13, 8, 0.9);
  border: 1px solid rgba(255, 184, 92, 0.45);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  color: #ffd98f; font-size: 0.85rem; font-weight: 600;
  pointer-events: none; white-space: nowrap;
  animation: hintPulse 1.8s ease-in-out infinite;
}
body.zen .sound-hint { display: none; }

/* Phones: skip the expensive glass. ~19 backdrop-filter layers over a
   continuously animating fixed scene is the main frame-rate killer on
   mid-range Android; a solid fill is indistinguishable at chip size. */
/* ── every control opts out of double-tap zoom ────────────────────────────
   iPad Safari reported "channel button slow, like stuck" together with the page
   zooming in and out on its own. Those are one fault, not two: iOS treats two
   quick taps on the same spot as double-tap-to-zoom, so pressing CH twice — the
   most natural thing to do with a channel button — zooms the page instead of
   changing channel. It also holds every tap for ~300ms waiting to see whether a
   second one is coming, which is the sluggishness.

   touch-action: manipulation switches off double-tap and the delay it implies,
   for these elements only. Pinch-to-zoom on the page is untouched, which
   matters — this audience does zoom, and viewport user-scalable=no is not an
   acceptable fix. */
button, a, [role="button"], input[type="range"], summary {
  touch-action: manipulation;
}

/* Widened twice over, after an iPad Safari report: doors opening slowly, the CH
   button sticking, and the tab crashing outright two or three times.

   The gate was max-width: 900px, so an iPad — 1024 to 1366 in landscape — took
   the full set. And even a phone only lost six of the eighteen blurred
   elements; the expensive ones were never in the list. .glass alone is
   blur(16px) and sits on the player, the panel and the trays, and .yt-stage
   blurs the animated scene behind the whole cabinet continuously.

   Each backdrop-filter forces its own compositing layer holding a snapshot of
   everything behind it. Eighteen of those over a moving scene is a memory
   figure, not a frame-rate one, which is why it crashed rather than merely
   stuttered — and 5% battery would have had iOS throttling the GPU on top.

   (pointer: coarse) rather than a width: the signal that matters is a touch
   device with a thermally limited GPU, not a narrow window. A desktop browser
   resized small keeps the glass. */
@media (max-width: 900px), (pointer: coarse) {
  .chip, .reactions button, .toast, .day-banner, .footer, .sound-hint,
  .glass, .panchang, .zen-bar, .wa-card, .sleep-menu, .dock__b,
  .fest-banner, .about__inner, .scene-chip b, .panel-scrim,
  .yt-stage, .player {
    backdrop-filter: none !important; -webkit-backdrop-filter: none !important;
  }
  /* Solid fills where the blur was doing the legibility work. Without these the
     panels turn into transparent boxes over a busy scene, which is worse than
     the cost being paid. */
  .chip, .reactions button { background: rgba(26, 15, 10, 0.78); }
  .day-banner { background: rgba(26, 15, 10, 0.82); }
  .glass, .panchang, .sleep-menu, .zen-bar { background: rgba(22, 13, 7, 0.96); }
  .player { background: rgba(20, 12, 6, 0.95); }
  .wa-card, .fest-banner, .about__inner { background: rgba(24, 15, 9, 0.93); }
  .dock__b { background: rgba(26, 15, 10, 0.85); }
  .panel-scrim { background: rgba(8, 4, 2, 0.5); }
}
/* Idle critters should not animate while their phase hides them */
[data-phase="morning"] .stars, [data-phase="day"] .stars { display: none; }
[data-phase="night"] .birds, [data-phase="evening"] .rays,
[data-phase="night"] .rays, [data-phase="day"] .rays { display: none; }
[data-phase="morning"] .fireflies, [data-phase="day"] .fireflies { display: none; }
.tap-hint.hidden { display: none; }

/* expanded ("बड़ा करें") — a near-full-height sheet for browsing 21 bhajans.
   max() so that on a short screen expanding can never shrink the list. */
.panel.max {
  max-height: max(min(52dvh, 28rem), calc(100dvh - var(--player-h, 9rem) - 4.5rem));
  width: min(96vw, 40rem);
}
/* landscape phones: the list was 19px tall — take the screen instead */
@media (max-height: 480px) {
  .panel, .panel.max {
    max-height: calc(100dvh - 1rem);
    bottom: 0.5rem;
  }
  .panel-head { padding-block: 0.3rem; }
  .panel-share { display: none; }
  .plist { min-height: 7rem; }
  .vadya { max-height: calc(100dvh - 1rem); overflow-y: auto; bottom: 0.5rem; }
}

.panel.max .plist { padding-bottom: 0.6rem; }

/* the tab strip scrolls horizontally — fade the right edge so it is
   visible that more playlists exist off-screen */
.ptabs-wrap { position: relative; }
.ptabs-wrap::after {
  content: ""; position: absolute; right: 0; top: 0; bottom: 0.7rem;
  width: 2.2rem; pointer-events: none;
  background: linear-gradient(to right, transparent, rgba(24, 13, 8, 0.97));
  transition: opacity 0.2s;
}
.ptabs-wrap.at-end::after, .panel.max .ptabs-wrap::after { opacity: 0; }

/* install card recedes on its own once it has been seen */
.install-card.faded { opacity: 0.22; transition: opacity 2.5s ease; }
.install-card.faded:hover, .install-card.faded:focus-within { opacity: 1; }

/* update prompt — asks, never interrupts playback */
.update-bar {
  position: fixed; left: 50%; z-index: 46;
  bottom: calc(var(--player-h, 9rem) + 0.6rem);
  transform: translateX(-50%);
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.6rem 0.7rem 0.6rem 1.1rem;
  border-radius: 999px; white-space: nowrap;
  background: rgba(24, 13, 8, 0.96);
  border: 1px solid rgba(255, 184, 92, 0.5);
  box-shadow: 0 12px 34px rgba(5, 2, 0, 0.6);
  font-size: 0.85rem; font-weight: 600; color: var(--cream);
}
.update-now {
  padding: 0.42rem 0.9rem; border-radius: 999px;
  background: #ffb85c; color: #2b160a; font-weight: 700; font-size: 0.8rem;
}
.update-later { font-size: 0.78rem; opacity: 0.6; padding: 0.42rem 0.4rem; }
.update-later:hover { opacity: 1; }
@media (max-width: 640px) { .update-bar { font-size: 0.8rem; padding-left: 0.9rem; } }

/* WhatsApp card is dismissible — it sits right above the player and
   some listeners just want the scene. The footer link keeps the
   community one tap away afterwards. */
.wa-wrap { position: relative; width: 100%; max-width: 30rem; margin: 0 auto 1rem; }
.wa-wrap .wa-card { margin: 0; }
.wa-close {
  position: absolute; top: -0.4rem; right: -0.4rem;
  width: 1.9rem; height: 1.9rem; border-radius: 50%;
  display: grid; place-items: center; font-size: 0.75rem;
  background: rgba(24, 13, 8, 0.95);
  border: 1px solid var(--glass-border);
  color: var(--cream); opacity: 0.75;
}
.wa-close:hover { opacity: 1; }
body:not(.started) .wa-wrap { opacity: 0; pointer-events: none; transition: opacity 0.7s ease; }
.wa-foot svg { flex-shrink: 0; }

/* footer: what the WhatsApp channel actually is */
.wa-block {
  margin-top: 0.9rem; padding: 1rem 1.1rem;
  border-radius: 1rem;
  background: rgba(37, 211, 102, 0.07);
  border: 1px solid rgba(37, 211, 102, 0.28);
}
.wa-block-lead { font-size: 0.88rem; line-height: 1.65; opacity: 0.92; }
.wa-list { list-style: none; margin: 0.8rem 0 0; display: grid; gap: 0.4rem; font-size: 0.84rem; opacity: 0.85; }
.wa-block-btn {
  display: inline-block; margin-top: 1rem;
  padding: 0.6rem 1.2rem; border-radius: 999px;
  background: #25d366; color: #05310f; font-weight: 700; font-size: 0.85rem;
  transition: transform 0.15s;
}
.wa-block-btn:hover { transform: translateY(-1px) scale(1.02); }


/* ══════════ प्रवेश द्वार — full-screen entry gate ══════════
   The browser's autoplay gesture requirement becomes the ritual: you
   open the doors, and the bhajan starts as they swing. */
.gate {
  position: fixed; inset: 0; z-index: 60;
  /* the fronds and garland deliberately overhang the frame; without this they
     overhang the viewport too and produce a horizontal scrollbar at ~768px */
  overflow: hidden;
  display: grid; place-items: center;
  background: radial-gradient(120% 90% at 50% 40%, #4a2a12 0%, #2a1608 55%, #150a03 100%);
  cursor: pointer;
  perspective: 1400px;
  transition: opacity 1.1s ease, transform 1.5s cubic-bezier(0.42, 0, 0.22, 1), visibility 1.5s;
}
.gate.is-gone { opacity: 0; visibility: hidden; transform: scale(1.35); pointer-events: none; }

/* ochre wall texture behind the frame */
.gate__wall {
  position: absolute; inset: 0;
  /* dark walnut panelling with a candle-lit vignette, as in the reference:
     light lives at the centre and near the diyas, the corners fall to black */
  background:
    radial-gradient(46% 34% at 15% 96%, rgba(255,160,60,0.15), transparent 70%),
    radial-gradient(46% 34% at 85% 96%, rgba(255,160,60,0.15), transparent 70%),
    radial-gradient(80% 62% at 50% 42%, rgba(255,190,110,0.10), transparent 68%),
    repeating-linear-gradient(0deg, rgba(0,0,0,0.10) 0 2px, transparent 2px 30px),
    repeating-linear-gradient(90deg, rgba(255,206,140,0.028) 0 1px, transparent 1px 7px),
    linear-gradient(180deg, #4d2c12 0%, #38200c 55%, #1c0f05 100%);
}
/* vignette above everything on the gate, so edges sink into darkness */
.gate::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 5;
  background: radial-gradient(105% 88% at 50% 44%, transparent 55%, rgba(8,3,0,0.55) 100%);
}

.gate__frame {
  position: relative;
  width: min(78vw, 30rem, 56vh);
  aspect-ratio: 3 / 4;          /* the arch keeps its shape at every size */
  height: auto;
  border-radius: 50% 50% 0.6rem 0.6rem / 34% 34% 0.6rem 0.6rem;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,0.05) 0 2px, transparent 2px 9px),
    linear-gradient(180deg, #2f7c86, #1f5b64 55%, #17454c);
  padding: clamp(0.9rem, 2.6vw, 1.6rem);
  box-shadow:
    0 0 0 3px #123a40,
    0 0 0 7px #c9a227,
    0 26px 70px rgba(0,0,0,0.7),
    inset 0 0 30px rgba(0,0,0,0.45);
  transform-style: preserve-3d;
}
.gate__opening {
  position: relative; width: 100%; height: 100%;
  border-radius: 50% 50% 0.3rem 0.3rem / 34% 34% 0.3rem 0.3rem;
  background: linear-gradient(#241606, #43290f);
  overflow: hidden;
  /* Safari ignores the overflow clip above for children in a 3D context, so the
     door leaves escaped the arch. clip-path is honoured there; keep both. */
  -webkit-clip-path: url(#archClip);
  clip-path: url(#archClip);
  transform-style: preserve-3d;
  box-shadow: inset 0 0 26px rgba(0,0,0,0.8);
}
/* the lit garbhagriha waiting behind the doors */
.gate__glow {
  position: absolute; inset: 0; opacity: 0.18;
  background: radial-gradient(70% 60% at 50% 58%, #fff0c4 0%, #f0a93c 42%, #6b3a12 78%, #2a1608 100%);
  transition: opacity 1.5s ease;
}
.gate.is-open .gate__glow { opacity: 1; }

.gate__leaf {
  position: absolute; top: 0; height: 100%; width: 50%;
  overflow: hidden;
  background:
    linear-gradient(96deg, rgba(255,214,150,0.10) 0%, transparent 18%, transparent 82%, rgba(0,0,0,0.30) 100%),
    repeating-linear-gradient(90deg, rgba(0,0,0,0.16) 0 1.5px, transparent 1.5px 12px),
    repeating-linear-gradient(88deg, rgba(255,196,120,0.05) 0 1px, transparent 1px 5px),
    linear-gradient(96deg, #6b3f1c 0%, #4e2c10 46%, #3a1f0a 100%);
  backface-visibility: hidden;
  transition: transform 1.5s cubic-bezier(0.42, 0, 0.22, 1);
  /* layout, padding and the wood itself are set by the carved-door block below;
     do not re-add them here — a second .gate__leaf rule has silently won before */
}
.gate__leaf::after { /* wood grain */
  content: ""; position: absolute; inset: 0;
  background: repeating-linear-gradient(93deg, rgba(255,226,178,0.055) 0 2px, transparent 2px 8px);
}
.gate__leaf--l { left: 0;  transform-origin: left center;  border-right: 1px solid rgba(0,0,0,0.4); }
.gate__leaf--r { right: 0; transform-origin: right center; border-left: 1px solid rgba(0,0,0,0.4); }
.gate.is-open .gate__leaf--l { transform: rotateY(-82deg); }
.gate.is-open .gate__leaf--r { transform: rotateY(82deg); }

/* ══════════ गेंदा तोरण — the marigold swag over the door ══════════ */
.gate__toran {
  /* An SVG is a replaced element: left+right do not resolve against it — it
     takes its width from height x viewBox ratio and pins to `left`, which
     hung the garland 44px off-centre. Same trap as the lintel. Explicit
     width, no `right`. */
  position: absolute; top: -3.4rem; left: -9%; width: 118%;
  height: 7.2rem; z-index: 4; pointer-events: none;
  overflow: visible;
  transform-origin: 50% 0;
  animation: toranSway 6.5s ease-in-out infinite alternate;
  filter: drop-shadow(0 5px 9px rgba(0, 0, 0, 0.45));
}
@keyframes toranSway {
  from { transform: rotate(-0.7deg); }
  to   { transform: rotate(0.7deg); }
}

/* ══════════ दीपस्तंभ — the standing brass lamps ══════════ */
.gate__deep {
  position: absolute; bottom: 3%; z-index: 2;
  width: clamp(3.4rem, 7vw, 5.4rem); height: auto;
  overflow: visible;
  filter: drop-shadow(0 10px 20px rgba(0, 0, 0, 0.6));
}
.gate__deep--l { left: 21%; }
.gate__deep--r { right: 21%; }
.lampFlame {
  transform-box: fill-box; transform-origin: 50% 100%;
  animation: lampFlicker 1.7s ease-in-out infinite alternate;
}
@keyframes lampFlicker {
  from { transform: scaleY(0.9) scaleX(1.04); opacity: 0.85; }
  to   { transform: scaleY(1.12) scaleX(0.94); opacity: 1; }
}

/* ══════════ floor offerings ══════════ */
.gate__offerings {
  position: absolute; bottom: 2.5%; left: 50%; transform: translateX(-50%);
  width: min(58rem, 94vw); height: 5rem; z-index: 1; pointer-events: none;
}
.offer { position: absolute; bottom: 0; overflow: visible;
  filter: drop-shadow(0 5px 9px rgba(0, 0, 0, 0.5)); }
/* clustered to either side — the middle belongs to the name and the button */
.offer--bowl-l { left: 2%;  width: 12%; }
.offer--thali  { left: 13%; width: 17%; bottom: -0.3rem; }
.offer--bowl-r { right: 2%; width: 12%; }
.offer--petals { left: 15%; width: 70%; bottom: -1.6rem; opacity: 0.9; }
.offer--diya-l { --dw: clamp(1.1rem, 2.4vw, 1.5rem); left: 31%; bottom: 0.2rem; }
.offer--diya-r { --dw: clamp(1.1rem, 2.4vw, 1.5rem); right: 14%; bottom: 0.2rem; }
.offer--diya-r::after { animation-delay: 0.8s; }

/* The step's top face sits 0.1rem above the frame bottom (bottom:-1.5rem +
   height:1.6rem), so that is where a lamp actually rests. It was at -0.35rem,
   which floated it below the stone against the door. */
.gate__diya {
  --dw: clamp(1.5rem, 3.4vw, 2.1rem);
  bottom: 0.1rem; z-index: 3;
  filter: drop-shadow(0 0 13px rgba(255, 170, 60, 0.75));
}
.gate__diya--l { left: 6%; }
.gate__diya--r { right: 6%; }
.gate__diya--r::after { animation-delay: 1.1s; }

.gate__plate {
  position: absolute; bottom: max(6%, env(safe-area-inset-bottom)); z-index: 6;
  left: 50%; transform: translateX(-50%);
  text-align: center; width: min(90vw, 24rem);
}
.gate__name {
  font-family: "Yatra One", serif; font-size: clamp(1.8rem, 6vw, 2.6rem);
  color: #ffe6b0; line-height: 1.15;
  text-shadow: 0 3px 22px rgba(0,0,0,0.8);
}
.gate__sub { margin-top: 0.3rem; font-size: 0.85rem; color: #e8c98f; opacity: 0.9; }
.gate__btn {
  margin-top: 1.1rem; padding: 0.85rem 2rem; border-radius: 999px;
  background: linear-gradient(180deg, #ffcf6b, #e39320);
  color: #3a1e05; font-size: 1.05rem; font-weight: 700;
  box-shadow: 0 8px 26px rgba(255, 170, 60, 0.35);
  animation: gateBreath 2.6s ease-in-out infinite;
}
@keyframes gateBreath { 50% { transform: scale(1.045); box-shadow: 0 10px 32px rgba(255,170,60,0.5); } }
.gate__hint { margin-top: 0.6rem; font-size: 0.72rem; color: #d9b478; opacity: 0.75; }
.gate.is-open .gate__plate { opacity: 0; transition: opacity 0.6s ease; }

@media (prefers-reduced-motion: reduce) {
  .gate__leaf { transition: opacity 0.6s ease; }
  .gate.is-open .gate__leaf { opacity: 0; transform: none; }
  .gate { transition: opacity 0.6s ease, visibility 0.6s; }
  .gate.is-gone { transform: none; }
  .gate__btn { animation: none; }
  .diya::after, .diya::before, .lampFlame, .gate__toran { animation: none; }
}

/* ══════════ आरती corona — flames orbiting the opened doorway ══════════
   Polar coordinates in pure CSS: rotate(--angle) then translateY(-r).
   Angles are deliberately irregular so it never reads as a spinner. */
.gate__corona {
  position: absolute; inset: 0; pointer-events: none;
  mix-blend-mode: screen;
  filter: drop-shadow(0 0 7px rgba(255, 164, 54, 0.48));
  opacity: 0; transition: opacity 1.4s ease 0.5s;
}
.gate.is-open .gate__corona { opacity: 1; }
.gate__corona i {
  position: absolute; left: 50%; top: 52%;
  width: clamp(4px, 0.6vw, 8px); height: clamp(11px, 1.5vw, 20px);
  opacity: 0; transform-origin: 50% 100%;
  background: linear-gradient(#fffcda, rgba(255, 196, 76, 0.86) 48%, rgba(239, 91, 28, 0.25) 100%);
  border-radius: 52% 48% 48% 52% / 72% 72% 28% 28%;
  animation: coronaRise 4s var(--delay) ease-out infinite;
}
@keyframes coronaRise {
  0%   { opacity: 0;    transform: translate(-50%, -50%) rotate(var(--angle)) translateY(-42%) scale(calc(var(--scale) * 0.72)); }
  20%  { opacity: 0.82; }
  48%  { opacity: 0.58; transform: translate(-50%, -50%) rotate(calc(var(--angle) + 3deg)) translateY(-54%) scale(var(--scale)); }
  100% { opacity: 0;    transform: translate(-50%, -50%) rotate(calc(var(--angle) - 2deg)) translateY(-68%) scale(calc(var(--scale) * 0.38)); }
}
@media (prefers-reduced-motion: reduce) {
  .gate__corona i { animation: none; opacity: 0.18; }
}

/* ══════════ carved teak temple doors ══════════ */
.gate__leaf {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: auto repeat(3, minmax(0, 1fr));
  gap: 5%;
  padding: 7% 9% 12%;
  align-content: stretch;
  background:
    linear-gradient(96deg, rgba(255,214,150,0.10) 0%, transparent 18%, transparent 82%, rgba(0,0,0,0.30) 100%),
    repeating-linear-gradient(90deg, rgba(0,0,0,0.16) 0 1.5px, transparent 1.5px 12px),
    repeating-linear-gradient(88deg, rgba(255,196,120,0.05) 0 1px, transparent 1px 5px),
    linear-gradient(96deg, #62381a 0%, #47270e 46%, #331b09 100%);
}
/* carved square panels with a rosette pressed into each */
.gate__carve {
  position: relative;
  min-height: clamp(2.2rem, 7vh, 4.6rem);
  border-radius: 0.2rem;
  background:
    radial-gradient(circle at 50% 46%, rgba(30,14,2,0.4), transparent 62%),
    linear-gradient(160deg, rgba(96,58,26,0.85), rgba(52,29,10,0.9));
  box-shadow:
    inset 0 0 0 2px rgba(24,11,2,0.85),
    inset 0 0 0 3.5px rgba(214,160,92,0.20),
    inset 0 4px 9px rgba(14,6,0,0.8),
    0 1px 0 rgba(255,214,150,0.10);
}
/* the brass boss at the heart of every panel — dome, petal ring, patina */
.gate__carve::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: clamp(1.15rem, 3.4vh, 1.9rem); height: clamp(1.15rem, 3.4vh, 1.9rem);
  transform: translate(-50%, -50%); border-radius: 50%;
  background:
    radial-gradient(circle at 38% 32%, #ffe9ad 0%, #e3b25b 34%, #8a5a18 72%, #52320a 100%);
  box-shadow:
    0 0 0 3px rgba(20,10,1,0.65),
    0 0 0 4.5px rgba(200,150,84,0.35),
    0 3px 6px rgba(0,0,0,0.6);
}
/* petal ring pressed around the boss */
.gate__carve::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: clamp(2.1rem, 6vh, 3.3rem); height: clamp(2.1rem, 6vh, 3.3rem);
  transform: translate(-50%, -50%); border-radius: 50%;
  background: repeating-conic-gradient(from 0deg,
    rgba(224,172,96,0.30) 0deg 14deg, rgba(30,14,2,0.35) 14deg 30deg);
  -webkit-mask: radial-gradient(circle, transparent 0 52%, #000 54% 74%, transparent 76%);
  mask: radial-gradient(circle, transparent 0 52%, #000 54% 74%, transparent 76%);
}

/* ॐ carved into the head of each leaf */
.gate__om {
  grid-column: 1 / -1;
  font-family: "Yatra One", serif;
  font-size: clamp(1.5rem, 4.2vw, 2.4rem);
  text-align: center; line-height: 1;
  color: #f0c675;
  text-shadow: 0 2px 3px rgba(40,18,2,0.9), 0 0 16px rgba(255,190,90,0.35);
}
/* brass studs down the meeting stile */
.gate__studs {
  position: absolute; top: 6%; bottom: 6%; width: 10px;
  background-image: radial-gradient(circle at 45% 38%, #ffedb0 0 1.2px, #e0aa4e 1.3px 2.6px, #6d4410 2.8px, transparent 3.4px);
  background-size: 10px 22px;
  filter: drop-shadow(0 1px 1px rgba(0,0,0,0.7));
}
.gate__leaf--l .gate__studs { right: 3%; }
.gate__leaf--r .gate__studs { left: 3%; }
/* brass ring handle */
.gate__ring {
  position: absolute; top: 52%; width: 1.6rem; height: 1.6rem;
  border: 3px solid; border-radius: 50%;
  border-color: #f3cf7c #b97f22 #8a5a12 #d9a944;   /* light falls from upper left */
  box-shadow: 0 3px 7px rgba(0,0,0,0.65), inset 0 1px 1px rgba(255,240,190,0.6);
}
/* the mounting plate the ring hangs from */
.gate__ring::before {
  content: ""; position: absolute; left: 50%; top: -0.62rem;
  width: 1.05rem; height: 0.66rem; transform: translateX(-50%);
  border-radius: 50% 50% 20% 20%;
  background: radial-gradient(circle at 40% 30%, #f0c568, #96621a 78%);
  box-shadow: 0 2px 4px rgba(0,0,0,0.6);
}
.gate__leaf--l .gate__ring { right: 9%; }
.gate__leaf--r .gate__ring { left: 9%; }

/* carved stone lintel over the frame */
.gate__lintel {
  position: absolute; top: -2.5rem; left: 50%; transform: translateX(-50%);
  width: 116%; height: 3rem; z-index: 2;
  filter: drop-shadow(0 4px 10px rgba(0,0,0,0.55));
}
/* two brass bells hanging in the doorway */
.gate__bells {
  /* hung from the underside of the lintel, in front of the arch */
  position: absolute; top: 0.1rem; left: 0; right: 0; height: 0;
  pointer-events: none; z-index: 4;
}
.gate__bell {
  position: absolute; left: var(--x); top: 0;
  display: flex; flex-direction: column; align-items: center;
  transform-origin: 50% 0;            /* swings from where the chain is fixed */
  transform: translateX(-50%) scale(var(--sc));
  animation: bellSwing var(--dur) ease-in-out var(--dl) infinite alternate;
  pointer-events: auto; cursor: pointer;
  background: none; border: 0; padding: 0;
  filter: drop-shadow(0 4px 7px rgba(0, 0, 0, 0.6));
}
/* brass links, not a plain wire */
.bell__chain {
  width: 7px; height: var(--len); flex: none;
  background-image: radial-gradient(ellipse 2.6px 3.6px at 50% 50%,
    transparent 0 1.1px, #e8bf62 1.3px 2.1px, #7d5512 2.2px, transparent 2.6px);
  background-size: 7px 7px;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.55));
}
.bell__body { width: clamp(2.3rem, 5vw, 3.4rem); height: auto; margin-top: -2px; }
/* a hanging bell settles — it does not tick side to side evenly */
@keyframes bellSwing {
  from { transform: translateX(-50%) scale(var(--sc)) rotate(-5deg); }
  to   { transform: translateX(-50%) scale(var(--sc)) rotate(5deg); }
}
/* struck: a hard swing that decays */
.gate__bell.rung { animation: bellStruck 1.5s cubic-bezier(0.36, 0.07, 0.19, 0.97); }
@keyframes bellStruck {
  0%   { transform: translateX(-50%) scale(var(--sc)) rotate(0deg); }
  12%  { transform: translateX(-50%) scale(var(--sc)) rotate(21deg); }
  30%  { transform: translateX(-50%) scale(var(--sc)) rotate(-16deg); }
  48%  { transform: translateX(-50%) scale(var(--sc)) rotate(10deg); }
  66%  { transform: translateX(-50%) scale(var(--sc)) rotate(-6deg); }
  82%  { transform: translateX(-50%) scale(var(--sc)) rotate(3deg); }
  100% { transform: translateX(-50%) scale(var(--sc)) rotate(0deg); }
}
.gate__bell:hover .bell__body { filter: brightness(1.12); }
@media (prefers-reduced-motion: reduce) {
  .gate__bell { animation: none; }
  .gate__bell.rung { animation: none; }
}
@media (max-width: 767px) {
  /* the inner pair would crowd the arch on a phone */
  #gateBell2, #gateBell3 { display: none; }
  .gate__deep { width: 2.6rem; }
  .gate__deep--l { left: 6%; }
  .gate__deep--r { right: 6%; }
  .gate__offerings { width: 92vw; height: 3.4rem; }
  .offer--petals { display: none; }
  .gate__bell { --len: 2.2rem; }
}

/* ══════════ वाद्य यंत्र tray ══════════ */
.vadya {
  position: fixed; left: 50%; z-index: 43;
  bottom: calc(var(--player-h, 9rem) + 0.6rem);
  transform: translate(-50%, 12px) scale(0.98);
  width: min(94vw, 27rem);
  border-radius: 1.3rem; padding: 0.9rem 1rem 1.1rem;
  background: rgba(24, 13, 8, 0.97);
  opacity: 0; visibility: hidden;
  transition: transform 0.28s cubic-bezier(0.32,0.72,0.2,1), opacity 0.22s, visibility 0.28s;
  box-shadow: 0 -16px 50px rgba(5,2,0,0.65);
}
.vadya.open { transform: translate(-50%, 0) scale(1); opacity: 1; visibility: visible; }
.vadya__head { display: flex; align-items: center; justify-content: space-between; }
.vadya__title { font-weight: 700; font-size: 0.95rem; }
.vadya__hint { margin-top: 0.15rem; font-size: 0.74rem; opacity: 0.65; }
.vadya__grid {
  margin-top: 0.9rem; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(4.6rem, 1fr)); gap: 0.55rem;
}
.vadya__item {
  display: flex; flex-direction: column; align-items: center; gap: 0.3rem;
  padding: 0.75rem 0.4rem; border-radius: 0.9rem;
  background: rgba(246,234,216,0.07);
  border: 1px solid rgba(246,234,216,0.12);
  color: var(--cream); font-size: 0.72rem; font-weight: 600;
  transition: transform 0.12s, background 0.2s, border-color 0.2s;
}
.vadya__item span { font-size: 1.5rem; line-height: 1; }
.vadya__item .om-glyph { font-family: "Yatra One", serif; color: #ffd98f; }
.vadya__item:hover { background: rgba(255,184,92,0.16); border-color: rgba(255,184,92,0.45); }
.vadya__item:active, .vadya__item.struck { transform: scale(0.93); background: rgba(255,184,92,0.3); }
body.zen .vadya { opacity: 0 !important; visibility: hidden !important; }

/* वाद्य: volume, and a strike you can feel */
.vadya__vol {
  display: flex; align-items: center; gap: 0.6rem;
  margin-top: 0.7rem; padding: 0.5rem 0.7rem;
  border-radius: 0.8rem; background: rgba(246,234,216,0.06);
}
.vadya__vol .range { flex: 1; }
.vadya__vol span { font-size: 0.85rem; opacity: 0.7; }

.vadya__item.struck {
  animation: strike 0.32s cubic-bezier(0.2, 0.9, 0.2, 1);
  background: rgba(255, 184, 92, 0.34);
  border-color: rgba(255, 200, 120, 0.75);
  box-shadow: 0 0 24px rgba(255, 184, 92, 0.5), inset 0 0 18px rgba(255, 210, 140, 0.35);
}
@keyframes strike {
  0%   { transform: scale(0.9); }
  45%  { transform: scale(1.09); }
  100% { transform: scale(1); }
}
/* the whole shrine answers a strike */
body.vadya-pulse .temple .door { filter: drop-shadow(0 0 46px var(--door)) brightness(1.25); }
.temple .door { transition: filter 0.3s ease; }
@media (prefers-reduced-motion: reduce) {
  .vadya__item.struck { animation: none; }
  body.vadya-pulse .temple .door { filter: none; }
}

/* a chip that wants to be found */
.chip.beckon {
  border-color: rgba(255, 184, 92, 0.75);
  animation: beckon 2.4s ease-in-out infinite;
}
@keyframes beckon {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 184, 92, 0.45); }
  50%      { box-shadow: 0 0 0 8px rgba(255, 184, 92, 0); }
}
@media (prefers-reduced-motion: reduce) { .chip.beckon { animation: none; } }

/* interactive things should look interactive on hover */
.reactions button, .vadya__item, .chip { cursor: pointer; }
.reactions button::after { transition: opacity 0.18s 0.2s, transform 0.18s 0.2s; }

/* threshold bells — struck before entering.
   The old rules here sized an emoji and drove `animation` and `transform`
   directly, which overrode the swing and, worse, dropped the translateX(-50%)
   during bellStrike so a struck bell jumped sideways. The invite halo now rides
   on the bell body, where it cannot fight the pendulum transform. */
.bell__body {
  animation: bellInvite 3s ease-in-out infinite;
  transition: filter 0.2s;
}
@keyframes bellInvite {
  0%, 100% { filter: drop-shadow(0 0 6px rgba(255, 190, 90, 0.40)); }
  50%      { filter: drop-shadow(0 0 17px rgba(255, 212, 122, 0.9)); }
}
.gate__bell:hover .bell__body,
.gate__bell:focus-visible .bell__body {
  filter: drop-shadow(0 0 20px rgba(255, 214, 130, 1)) brightness(1.14);
}
.gate__bell:focus-visible { outline: 2px solid #ffd98f; outline-offset: 4px; border-radius: 0.4rem; }
@media (prefers-reduced-motion: reduce) { .bell__body { animation: none; } }

/* ══════════ केले के पत्ते — banana plants at the threshold ══════════
   Sizing and placement live with the tall-banana rebuild further down; the
   short-plant rules that used to sit here were dead and are gone. */
@keyframes leafSway {
  from { transform: translateX(-50%) rotate(var(--tilt)) skewX(-2.5deg); }
  to   { transform: translateX(-50%) rotate(calc(var(--tilt) + 5deg)) skewX(2.5deg); }
}
@media (max-width: 640px) { .gate__plant { width: 4.6rem; height: 9rem; } .gate__plant i { height: 6rem; width: 0.85rem; } }
@media (prefers-reduced-motion: reduce) { .gate__plant i { animation: none; } }

/* पूजा row */
.vadya__title--sub { margin-top: 1.1rem; font-size: 0.88rem; }
.vadya__grid--pooja { grid-template-columns: repeat(3, 1fr); }
.vadya__item.lit {
  background: rgba(255, 184, 92, 0.26);
  border-color: rgba(255, 200, 120, 0.7);
  box-shadow: 0 0 20px rgba(255, 170, 60, 0.4);
}
.vadya__item.lit span { filter: drop-shadow(0 0 10px rgba(255,180,70,0.95)); animation: flameFlicker 1.9s ease-in-out infinite alternate; }
@keyframes flameFlicker { 50% { opacity: 0.82; transform: scale(0.95); } }
#matchBtn.burning span { animation: flameFlicker 0.55s ease-in-out infinite alternate; }
@media (prefers-reduced-motion: reduce) { .vadya__item.lit span, #matchBtn.burning span { animation: none; } }

/* अगरबत्ती smoke drifting once it is lit */
body.agar-lit .smoke circle { opacity: 1; }
body.agar-lit .scene::after {
  content: ""; position: absolute; left: 46%; bottom: 22%;
  width: 90px; height: 220px; pointer-events: none;
  border-left: 6px solid rgba(220, 232, 240, 0.28);
  border-radius: 50%; filter: blur(9px);
  animation: agarRise 7.5s ease-in-out infinite;
}
@keyframes agarRise {
  0%   { opacity: 0; transform: translateY(24px) rotate(-5deg) scaleX(0.7); }
  22%  { opacity: 0.55; }
  75%  { opacity: 0.18; }
  100% { opacity: 0; transform: translate(26px, -120px) rotate(14deg) scaleX(1.35); }
}
@media (prefers-reduced-motion: reduce) { body.agar-lit .scene::after { animation: none; opacity: 0.25; } }

/* tap targets: small controls need a 44px hit area even when drawn small */
#waClose, #installCardClose { min-width: 44px; min-height: 44px; }
/* content-box so the 4px track survives the 20px tap padding (border-box would zero it) */
/* A 4px track needs a 44px hit area for a thumb — but with a mouse that same
   padding is 40px of hollow space inside the player, which made the bar look
   detached from the controls. Pay for the tap target only on touch. */
#seek { box-sizing: content-box; padding-block: 10px; background-clip: content-box; }
/* Volume had the same 4px-tall hit area the seek bar used to have — it was
   simply missed when #seek was fixed. WCAG 2.2 AA wants 24x24 CSS px for a
   pointer target; 4px content + 10px each side lands exactly on 24. Hidden
   below 768px, so the coarse-pointer bump only needs to cover tablets. */
#volume { box-sizing: content-box; padding-block: 10px; background-clip: content-box; }
@media (pointer: coarse) { #seek, #volume { padding-block: 20px; } }
.contact a, .bmc-link { display: inline-block; padding: 0.35rem 0.1rem; }
/* Devanagari needs more optical size than Latin */
.tagline { font-size: clamp(0.95rem, 3.4vw, 1.05rem); }

/* a stone step for the lamps to stand on — they were floating before */
.gate__frame::after {
  content: ""; position: absolute; left: -6%; right: -6%; bottom: -1.5rem;
  height: 1.6rem; border-radius: 0.3rem;
  background: linear-gradient(180deg, #d8c39a, #a98d5f 60%, #7d6437);
  box-shadow: 0 6px 18px rgba(0,0,0,0.55), inset 0 1px 0 rgba(255,255,255,0.35);
}

/* footer weekday rows are playable */
.week-link {
  display: block; width: 100%; text-align: left;
  padding: 0.55rem 0.6rem; border-radius: 0.6rem;
  font-size: 0.86rem; color: var(--cream);
  transition: background 0.18s, transform 0.12s;
}
.week-link:hover { background: rgba(255, 184, 92, 0.14); }
.week-link:active { transform: scale(0.985); }
.week-link b { color: #ffd98f; }
.week-link em { font-style: normal; color: #ffd98f; font-weight: 700; }
.week-link.is-today { background: rgba(255, 184, 92, 0.12); }
.week-link::before { content: "▶ "; opacity: 0.45; font-size: 0.8em; }
.week-link[disabled] { opacity: 0.5; cursor: default; }

/* route pages carry a real heading for crawlers and for the reader */
.route-h { font-family: "Yatra One", serif; font-size: 1.5rem; color: #ffd98f; margin-bottom: 0.3rem; }

/* ══════════ पंचांग ══════════ */
.date-line {
  /* it is a button now — keep it looking like the text it replaced */
  background: none; border: 0; padding: 0; font: inherit; color: inherit;
  cursor: pointer; text-align: left; border-bottom: 1px dotted rgba(246, 234, 216, 0.35);
}
.date-line:hover { color: #ffd98f; border-bottom-color: rgba(255, 217, 143, 0.7); }
/* the tooltip sat on top of the open panel's title — pointless once it is open */
.date-line[aria-expanded="true"]::after { opacity: 0 !important; }
.panchang {
  /* A dropdown anchored to the header meant that opening it from the dock —
     which sits mid-screen — put the panel off-screen and nothing appeared to
     happen. It is a centred sheet now, so both entry points land the same. */
  position: fixed; left: 50%; z-index: 46;
  bottom: calc(var(--player-h, 9rem) + 0.6rem);
  transform: translateX(-50%);
  width: min(23rem, 92vw); max-height: 62svh; overflow-y: auto;
  border-radius: 1.1rem; padding: 0.95rem 1.1rem;
  background: #170d07; border: 1px solid rgba(255, 184, 92, 0.34);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 18px 44px -14px rgba(8, 3, 1, 0.75);
  text-align: left;
}
.panchang__head { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; }
.panchang__head b { color: #ffd98f; font-size: 0.92rem; }
.panchang__grid {
  display: grid; grid-template-columns: auto 1fr; gap: 0.3rem 0.8rem;
  margin: 0.6rem 0 0; font-size: 0.82rem;
}
.panchang__grid dt { opacity: 0.6; white-space: nowrap; }
.panchang__grid dd { margin: 0; font-weight: 600; color: var(--cream); }
.panchang__grid dd.hi { color: #ffd98f; }
.panchang__note {
  margin-top: 0.7rem; padding-top: 0.6rem; font-size: 0.66rem; line-height: 1.45; opacity: 0.55;
  border-top: 1px solid rgba(246, 234, 216, 0.12);
}
@media (max-width: 767px) {
  /* sits above the sticky player rather than on top of its controls */
  .panchang { bottom: calc(var(--player-h, 9rem) + 5.6rem); }
}

/* ══════════ केला और तुलसी — the plants that stand at a temple door ══════════
   Built from real botany rather than green blades: a banana has a fat tapering
   pseudostem, and each leaf is one blade split by a pale midrib with parallel
   veins running out at an angle to a torn edge. The two halves are separate so
   the veins can mirror the way they actually do. Everything is transform-only
   so it stays on the compositor. */

.gate__plant {
  /* a banana is a tall plant — the fronds belong up beside the arch, not on the
     ground. Pot at the base, a long pseudostem, leaves arcing in near the top. */
  position: absolute; bottom: 4%; width: min(15rem, 17vw); height: min(30rem, 68vh);
  pointer-events: none; z-index: 1;
  perspective: 900px; transform-style: preserve-3d;
}
.gate__plant--l { left: -6%; }
.gate__plant--r { right: -6%; transform: scaleX(-1); }

/* ---- carved sandstone planter ---- */
.plant__pot {
  position: absolute; left: 50%; bottom: 0; transform: translateX(-50%);
  width: 8.4rem; height: 4.8rem; border-radius: 0.24rem;
  background:
    repeating-linear-gradient(92deg, rgba(120, 96, 62, 0.07) 0 1px, transparent 1px 11px),
    linear-gradient(180deg, #e9dcc0 0%, #d8c5a1 40%, #bda882 72%, #9d8862 100%);
  box-shadow:
    inset 0 2px 0 rgba(255, 252, 240, 0.55),
    inset 0 -12px 20px rgba(92, 72, 44, 0.34),
    0 12px 28px rgba(0, 0, 0, 0.5);
}
/* the lipped cornice every temple planter has */
.plant__pot u {
  position: absolute; left: -7%; right: -7%; top: -0.6rem; height: 0.92rem;
  border-radius: 0.18rem;
  background: linear-gradient(180deg, #f2e6cd 0%, #dbc9a6 55%, #b9a37c 100%);
  box-shadow: 0 3px 7px rgba(0, 0, 0, 0.32);
}
/* recessed carved panel on the face */
.plant__pot::before {
  content: ""; position: absolute; inset: 22% 16% 16%;
  border-radius: 0.14rem;
  box-shadow: inset 0 0 0 1.5px rgba(146, 120, 82, 0.5), inset 0 2px 5px rgba(88, 68, 42, 0.35);
}
/* a small lotus rosette inside the panel */
.plant__pot::after {
  content: ""; position: absolute; left: 50%; top: 52%;
  width: 1.15rem; height: 1.15rem; transform: translate(-50%, -50%) rotate(45deg);
  border-radius: 42% 8% 42% 8%;
  background: rgba(150, 124, 84, 0.42);
}

/* ---- pseudostem: fat at the soil, tapering upward, striated ---- */
.plant__stem {
  position: absolute; left: 50%; bottom: 3.9rem;
  width: 2.5rem; height: calc(100% - 17.4rem); transform: translateX(-50%);
  clip-path: polygon(34% 0, 66% 0, 84% 100%, 16% 100%);
  background:
    repeating-linear-gradient(90deg, rgba(0, 0, 0, 0.14) 0 1px, transparent 1px 6px),
    linear-gradient(90deg, #24501f 0%, #37701f 26%, #5d8f33 52%, #33681f 78%, #1e451a 100%);
  box-shadow: inset -4px 0 9px rgba(0, 0, 0, 0.3);
}
/* dried sheath at the base, as on every real trunk */
.plant__stem::after {
  content: ""; position: absolute; left: 8%; right: 8%; bottom: 0; height: 26%;
  background: linear-gradient(180deg, transparent, rgba(120, 92, 40, 0.55));
}

/* ---- the fronds: real SVG geometry, animated per leaf ---- */
.plant__fronds {
  /* Anchored to the stem apex, not the box: the petiole sits 21.4% above this
     element's bottom edge (viewBox y=40 of 280), so translateY(21.4%) plants it
     exactly on the apex at calc(100% - 13.5rem) regardless of scale. */
  position: absolute; left: 50%; bottom: calc(100% - 13.5rem);
  width: 200%; height: auto; aspect-ratio: 420 / 280;
  transform: translate(-50%, 21.4%);
  overflow: visible; pointer-events: none;
  perspective: 900px;
}
.frond {
  transform-box: view-box; transform-origin: 0px 40px;   /* the petiole */
  /* --oy staggers where each leaf leaves the stem: they emerge one above the
     next, not all from one crown, which is what made it read as a fan palm.
     --br shades the older lower leaves; --rx tips them out of plane. */
  transform: translateY(var(--oy)) rotate(var(--tilt)) rotateX(var(--rx)) scale(var(--sc));
  animation: leafFlutter var(--dur) ease-in-out var(--dl) infinite alternate;
  filter: drop-shadow(0 5px 9px rgba(0, 0, 0, 0.35)) brightness(var(--br));
}
/* a banana leaf twists as it lifts — rotate plus a shear reads as real wind */
@keyframes leafFlutter {
  from { transform: translateY(var(--oy)) rotate(var(--tilt)) rotateX(var(--rx)) scale(var(--sc)); }
  to   { transform: translateY(var(--oy)) rotate(calc(var(--tilt) + 2.6deg))
                    rotateX(calc(var(--rx) + 9deg)) scale(calc(var(--sc) * 1.015)); }
}

/* a diya resting on the planter ledge, as in every courtyard */
.diya {
  position: absolute; width: var(--dw, 1.6rem); height: calc(var(--dw, 1.6rem) * 0.39);
  border-radius: 0 0 999px 999px;
  background: linear-gradient(180deg, #a4562a, #7c3b1c);
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.45);
}
.plant__diya { left: 20%; bottom: 4.5rem; }
.tulsi__diya { left: 50%; bottom: 0.2rem; transform: translateX(-50%); }
/* the flame: a hot pale core, an amber body, a soft tip */
.diya::after {
  content: ""; position: absolute; left: 50%; bottom: 74%;
  width: calc(var(--dw, 1.6rem) * 0.34); height: calc(var(--dw, 1.6rem) * 0.72);
  transform: translateX(-50%); transform-origin: 50% 100%;
  border-radius: 50% 50% 44% 44% / 64% 64% 36% 36%;
  background:
    radial-gradient(46% 30% at 50% 84%, #ffffff 0%, rgba(255,255,255,0) 100%),
    radial-gradient(58% 56% at 50% 74%, #fff6d2 0%, #ffd274 40%, #ff9a2e 74%, rgba(255,138,36,0) 100%);
  filter: drop-shadow(0 0 5px rgba(255, 190, 92, 0.95));
  animation: diyaFlicker 2.4s ease-in-out infinite alternate;
  z-index: 1;
}
/* the halo it throws — this is what makes a lamp read as lit rather than drawn */
.diya::before {
  content: ""; position: absolute; left: 50%; bottom: 22%;
  width: calc(var(--dw, 1.6rem) * 4.2); height: calc(var(--dw, 1.6rem) * 4.2);
  transform: translateX(-50%); border-radius: 50%;
  background: radial-gradient(circle,
    rgba(255, 186, 88, 0.42) 0%, rgba(255, 160, 60, 0.20) 34%,
    rgba(255, 140, 40, 0.07) 62%, rgba(255, 140, 40, 0) 78%);
  animation: diyaHalo 2.4s ease-in-out infinite alternate;
  pointer-events: none;
}
@keyframes diyaHalo {
  from { opacity: 0.72; transform: translateX(-50%) scale(0.92); }
  to   { opacity: 1;    transform: translateX(-50%) scale(1.08); }
}
@keyframes diyaFlicker {
  from { transform: translateX(-50%) scaleY(0.9) scaleX(1.05) skewX(-4deg); opacity: 0.9; }
  to   { transform: translateX(-50%) scaleY(1.14) scaleX(0.93) skewX(5deg); opacity: 1; }
}

/* ══════════ तुलसी चौरा ══════════ */
.gate__tulsi {
  position: absolute; left: 25%; bottom: 2%; z-index: 2;
  width: 8rem; height: 11rem; pointer-events: none; perspective: 700px;
}
/* scalloped bowl */
.tulsi__choura u {
  position: absolute; left: 50%; bottom: 4.3rem; transform: translateX(-50%);
  width: 5.2rem; height: 1.5rem; border-radius: 0.2rem 0.2rem 0.5rem 0.5rem;
  background: linear-gradient(180deg, #f2e7cf 0%, #dccaa8 48%, #b9a37c 100%);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4), inset 0 2px 0 rgba(255, 255, 255, 0.5);
}
.tulsi__choura u::before {
  content: ""; position: absolute; left: 4%; right: 4%; top: -0.42rem; height: 0.55rem;
  border-radius: 999px;
  background: linear-gradient(180deg, #f4e9d2 0%, #ddcbaa 60%, #bda887 100%);
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.28);
}
/* the pedestal shaft with its carved face */
.tulsi__choura b {
  position: absolute; left: 50%; bottom: 0; transform: translateX(-50%);
  width: 3.6rem; height: 4.6rem; border-radius: 0.16rem;
  background:
    repeating-linear-gradient(90deg, rgba(120, 96, 62, 0.06) 0 1px, transparent 1px 10px),
    linear-gradient(180deg, #e6d8bb 0%, #cfbb95 46%, #a89065 100%);
  box-shadow: inset 0 0 0 1.5px rgba(150, 124, 84, 0.4), 0 12px 24px rgba(0, 0, 0, 0.48);
}
.tulsi__choura b::after {
  content: ""; position: absolute; left: 50%; top: 48%;
  width: 1.5rem; height: 1.5rem; transform: translate(-50%, -50%) rotate(45deg);
  border-radius: 44% 8% 44% 8%;
  box-shadow: inset 0 0 0 1.5px rgba(146, 120, 82, 0.5);
}
/* the bush itself: real SVG, many small opposite leaf pairs on upright stems */
.tulsi__svg {
  position: absolute; left: 50%; bottom: 5.5rem;
  width: 7.4rem; height: 6.9rem; transform: translateX(-50%);
  overflow: visible;
  transform-origin: 50% 100%;
  animation: tulsiSway 5.4s ease-in-out infinite alternate;
  filter: drop-shadow(0 4px 7px rgba(0, 0, 0, 0.32));
}
@keyframes tulsiSway {
  from { transform: translateX(-50%) rotate(-1.4deg); }
  to   { transform: translateX(-50%) rotate(1.8deg); }
}

@media (prefers-reduced-motion: reduce) {
  .frond, .tulsi__svg,
  .plant__diya::after, .tulsi__diya::after { animation: none; }
}
/* phones: smaller pots, fewer huge leaves crowding the door */
@media (max-width: 767px) {
  .gate__plant { width: 9.5rem; height: 13rem; bottom: 3%; }
  .gate__plant--l { left: -14%; }
  .gate__plant--r { right: -14%; }
  .plant__pot { width: 5.6rem; height: 3.2rem; }
  .plant__stem { width: 1.7rem; height: 4.2rem; bottom: 2.6rem; }
  /* the leaves paint well beyond their svg box; at 375px they swamped the door,
     so scale the whole canopy rather than each leaf */
  .plant__fronds { bottom: calc(100% - 8.4rem); width: 200%; height: auto;
    transform: translate(-50%, 21.4%) scale(0.55); transform-origin: 50% 79%; }
  .gate__plant { height: min(19rem, 52vh); }
  .plant__stem { height: calc(100% - 11rem); }
  .plant__diya { bottom: 3rem; }
  .gate__tulsi { left: 20%; bottom: 1%; width: 5.6rem; height: 7.6rem; }
  .tulsi__choura u { width: 3.6rem; height: 1.05rem; }
  .tulsi__choura b { width: 2.5rem; height: 3.2rem; top: 1rem; }
  .tulsi__svg { bottom: 3.8rem; width: 5rem; height: 4.7rem; }
  .tulsi__choura u { bottom: 3.1rem; }
}

/* ══════════ कमल — lotus on the ghat water ══════════ */
.lotus-layer { pointer-events: none; }
.kamal {
  transform-box: fill-box; transform-origin: 50% 100%;
  animation: kamalBob var(--dur, 7s) ease-in-out var(--dl, 0s) infinite alternate;
}
/* a flower on water rocks; it does not slide */
@keyframes kamalBob {
  from { transform: translateY(0) rotate(-1.6deg); }
  to   { transform: translateY(-3px) rotate(1.6deg); }
}

/* floating lamps are drawn now, so they light the water they sit on */
.fdiya { --dw: 1.35rem; }   /* bottom and animation already set above */

/* ══════════ बारिश ══════════ */
.rain { position: absolute; inset: 0; pointer-events: none; z-index: 3;
  opacity: 0; transition: opacity 1.2s ease; }
body.raining .rain { opacity: 1; }
/* three sheets at different speeds and angles read as depth, not wallpaper */
.rain i {
  position: absolute; inset: -20% -10%;
  background-repeat: repeat;
  animation: rainFall linear infinite;
}
.rain i:nth-child(1) {
  background-image: repeating-linear-gradient(103deg,
    rgba(214, 236, 255, 0.42) 0 1px, transparent 1px 3px, transparent 3px 68px);
  background-size: 100% 340px; animation-duration: 0.62s; opacity: 0.55;
}
.rain i:nth-child(2) {
  background-image: repeating-linear-gradient(100deg,
    rgba(214, 236, 255, 0.30) 0 1px, transparent 1px 4px, transparent 4px 96px);
  background-size: 100% 480px; animation-duration: 0.94s; opacity: 0.42;
}
.rain i:nth-child(3) {
  background-image: repeating-linear-gradient(106deg,
    rgba(230, 244, 255, 0.55) 0 2px, transparent 2px 6px, transparent 6px 150px);
  background-size: 100% 620px; animation-duration: 0.44s; opacity: 0.3;
}
@keyframes rainFall { to { transform: translate3d(-60px, 620px, 0); } }
/* the world goes cool and grey under rain, and the water takes the light */
body.raining .scene::before {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(180deg, rgba(120, 150, 175, 0.26), rgba(60, 85, 110, 0.34));
  mix-blend-mode: soft-light;
}
body.raining .river { filter: brightness(1.12) saturate(0.82); }
body.raining .kamal { animation-duration: calc(var(--dur, 7s) * 0.6); }
.chip.active { background: rgba(255, 184, 92, 0.24); color: #ffd98f; }
@media (prefers-reduced-motion: reduce) {
  .rain i { animation: none; }
  .kamal { animation: none; }
}

/* ══════════ मानसून के बादल ══════════
   Rain out of a clear blue sky reads as a bug. The clouds gather first, the
   sun goes behind them, and the whole sky cools — then the rain arrives. */
.clouds {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  opacity: 0; transition: opacity 2.4s ease;
}
body.raining .clouds { opacity: 1; }
.clouds i {
  position: absolute; top: var(--top); left: -34%;
  width: 30rem; height: 8.5rem;
  transform: scale(var(--sc));
  opacity: var(--o);
  /* a cloud is lumps, not an oval: overlapping radials give it a soft ragged top */
  background:
    radial-gradient(52% 78% at 22% 62%, rgba(238,242,247,0.95), rgba(238,242,247,0) 72%),
    radial-gradient(46% 92% at 42% 44%, rgba(246,249,252,0.97), rgba(246,249,252,0) 72%),
    radial-gradient(50% 74% at 62% 60%, rgba(232,237,244,0.93), rgba(232,237,244,0) 72%),
    radial-gradient(40% 62% at 80% 68%, rgba(220,227,236,0.88), rgba(220,227,236,0) 72%);
  filter: blur(2px) drop-shadow(0 14px 22px rgba(40, 60, 80, 0.28));
  animation: cloudDrift var(--dur) linear var(--dl) infinite;
}
/* the underside is always darker than the top */
.clouds i::after {
  content: ""; position: absolute; inset: 42% 6% 0 6%;
  background: radial-gradient(60% 100% at 50% 20%, rgba(96, 116, 138, 0.5), rgba(96,116,138,0) 74%);
  filter: blur(5px);
}
@keyframes cloudDrift { to { transform: scale(var(--sc)) translateX(calc(100vw + 46rem)); } }

/* sun and moon go behind the weather */
body.raining .sun  { opacity: 0.22 !important; filter: blur(3px) saturate(0.6); }
body.raining .moon { opacity: 0.3 !important;  filter: blur(2px); }
body.raining .stars { opacity: 0.25; }
body.raining .birds { opacity: 0; }            /* birds shelter when it rains */
.sun, .moon, .stars, .birds { transition: opacity 2s ease, filter 2s ease; }

@media (prefers-reduced-motion: reduce) { .clouds i { animation: none; } }
@media (max-width: 767px) { .clouds i:nth-child(4) { display: none; } }

/* ══════════ the floor the door stands on ══════════
   Without a ground plane the whole gate read as floating: the wall ran to the
   bottom edge and the step, planters and lamps had nothing under them. */
.gate__floor {
  position: absolute; left: 0; right: 0; bottom: 0; height: 22%;
  z-index: 0; pointer-events: none;
  background:
    /* the joint where floor meets wall — the darkest line in the scene */
    linear-gradient(180deg, rgba(0,0,0,0.62) 0 2px, rgba(0,0,0,0.28) 3px, transparent 14%),
    /* flagstone joints, widening toward the viewer so the floor recedes */
    repeating-linear-gradient(90deg, rgba(0,0,0,0.22) 0 2px, transparent 2px 12%),
    linear-gradient(180deg, #3b2712 0%, #4a3218 34%, #5a3d1d 68%, #3d2712 100%);
}
/* light pools on the floor in front of the doorway */
.gate__floor::before {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(58% 130% at 50% 0%, rgba(255, 186, 92, 0.22), transparent 70%);
}
/* the contact shadow that makes the step sit ON the floor rather than above it */
.gate__floor::after {
  content: ""; position: absolute; left: 50%; top: -0.4rem; transform: translateX(-50%);
  width: min(58vw, 26rem); height: 2.2rem; border-radius: 50%;
  background: radial-gradient(50% 50%, rgba(0, 0, 0, 0.55), transparent 72%);
  filter: blur(6px);
}
@media (max-width: 767px) { .gate__floor { height: 17%; } }

/* ══════════ dark mode is the only mode ══════════
   The palette is dark by design. Samsung Internet's night mode force-inverts
   any page that does not ship dark-mode CSS — `color-scheme` alone did not
   stop it — so re-assert the tokens explicitly here. Chrome and Safari read
   this as a no-op; Samsung reads it as "this page handles dark itself".*/
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --cream: #f6ead8;
    --sand: #e8d5b5;
    --live: #4ade80;
  }
  html, body { background: var(--sky-a); color: var(--cream); }
}
@media (prefers-color-scheme: light) {
  :root { color-scheme: dark; }   /* there is no light variant — do not invent one */
}

/* ══════════ दृश्य — scene presets ══════════
   Each scene repaints the palette and swaps which silhouettes paint, so the
   same SVG becomes a different place. The queue changes with it. */
.sc-only { display: none; }
/* my scene silhouettes need the same fill treatment the temple/tree get,
   otherwise they default to solid black */
.sc-only .sil-far, .sc-only ellipse { fill: var(--sil-far); transition: fill 2.5s; }
.sc-only .sil, .sc-only rect { fill: var(--sil); transition: fill 2.5s; }
.sc-only path.sil-far { fill: var(--sil-far); }
[data-scene="ghat"]      .sc-only--ghat,
[data-scene="vrindavan"] .sc-only--vrindavan,
[data-scene="himalaya"]  .sc-only--himalaya { display: block; }
/* the temple belongs to मन्दिर, घाट and रात्रि; the others get their own skyline */
[data-scene="vrindavan"] .temple,
[data-scene="himalaya"]  .temple { opacity: 0.28; }
[data-scene="himalaya"]  .tree { opacity: 0; }
/* the dham belongs to the plains. At altitude it should be a small shrine on a
   far saddle, not the thing filling the frame — the ridges are the subject. */
[data-scene="himalaya"] .temple {
  transform-box: fill-box; transform-origin: 50% 100%;
  transform: translate(1%, -19%) scale(0.34);
}
/* floating aarti lamps are a Ganga prop; there is no ghat up here */
[data-scene="himalaya"] .diya-layer { opacity: 0; pointer-events: none; }
[data-scene="himalaya"]  .ghat { opacity: 0.18; }
[data-scene="himalaya"]  .lotus-layer,
[data-scene="himalaya"]  .far-bank { opacity: 0; }
[data-scene="himalaya"]  .river-shine { opacity: 0.35; }
[data-scene="vrindavan"] .ghat { opacity: 0.6; }
.snow { fill: #eaf3f8; opacity: 0.9; }

/* a sample layer that has not finished decoding says so, instead of just
   doing nothing when the slider moves */
.mix-row.loading .mix-ic { animation: mixwait 1.1s ease-in-out infinite; }
.mix-row.loading .mix-val::after { content: " ·"; opacity: 0.6; }
@keyframes mixwait { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .mix-row.loading .mix-ic { animation: none; opacity: 0.5; } }

/* घाट — a riverfront at first light. मन्दिर is a courtyard you stand in; घाट
   is the far shore seen across open water, so the temple steps back, the
   skyline moves to the other bank and the river carries the frame. Without
   this block घाट inherited मन्दिर wholesale and the two looked identical. */
/* palette: generated per phase at the end of this file — see
   build-scene-palettes.mjs. A flat [data-scene] block here would be dead
   weight, since [data-phase][data-scene] outranks it in every real state. */
/* the temple is across the water now, not overhead */
[data-scene="ghat"] .temple {
  opacity: 0.5;
  /* an SVG <g> resolves percentage transforms against the viewport unless it is
     told to use its own box */
  transform-box: fill-box; transform-origin: 50% 100%;
  transform: translate(-19%, 6.5%) scale(0.6);
}
[data-scene="ghat"] .tree { opacity: 0.55; }
[data-scene="ghat"] .river-shine { opacity: 1; }
[data-scene="ghat"] .far-bank { opacity: 0.85; }
.sc-only--ghat .far-shore { opacity: 0.72; }
/* नाव — a moored boat rides, it does not sail */
.boat .hull { fill: #46352a; }
.boat .mast { fill: #46352a; }
.boat .sail { fill: #e8d6b8; opacity: 0.88; }
.boat { animation: bob var(--dur, 10s) ease-in-out infinite; animation-delay: var(--dl, 0s); transform-box: fill-box; }
@keyframes bob {
  0%, 100% { transform: translateY(0) rotate(-1.1deg); }
  50%      { transform: translateY(-3px) rotate(1.1deg); }
}

/* वृंदावन and हिमालय palettes: generated per phase at the end of this file */
/* हिमालय — thin cold air. The fix here is aerial perspective: at altitude
   distance washes contrast out toward the sky colour rather than toward black,
   so the far ridges must be *paler* than the near ones, not darker. The old
   palette had them all mid-blue, which is why it read flat. */
[data-phase="morning"][data-scene="himalaya"],
[data-phase="day"][data-scene="himalaya"] { --star-op: 0; --ff-op: 0; }
[data-scene="himalaya"] .sun { opacity: 0.45 !important; filter: blur(2px); }
.sc-only--himalaya .pine { fill: #2e4152; opacity: 0.92; }
.sc-only--himalaya .himshrine { opacity: 0.85; }
.sc-only--himalaya .himshrine rect,
.sc-only--himalaya .himshrine path { fill: #6d8194; }
.sc-only--himalaya .flagline { stroke: #4a5f72; stroke-width: 1.6; opacity: 0.75; }
.sc-only--himalaya .flag {
  transform-box: fill-box; transform-origin: 50% 0;
  animation: flagflap 2.6s ease-in-out infinite;
  animation-delay: var(--dl, 0s);
}
@keyframes flagflap {
  0%, 100% { transform: skewX(0deg)  scaleX(1);    }
  35%      { transform: skewX(-11deg) scaleX(0.88); }
  70%      { transform: skewX(8deg)   scaleX(0.96); }
}
/* बर्फ़बारी — only in हिमालय, and it drifts sideways rather than falling straight */
.snowfall { display: none; }
[data-scene="himalaya"] .snowfall {
  display: block; position: absolute; inset: 0;
  pointer-events: none; overflow: hidden; z-index: 2;
}
.snowfall i {
  position: absolute; top: -6%; left: var(--x); width: var(--sz); height: var(--sz);
  border-radius: 50%; background: #fff; opacity: 0;
  animation: snowdrift var(--dur) linear infinite;
  animation-delay: var(--dl);
}
@keyframes snowdrift {
  0%   { transform: translate(0, 0);            opacity: 0; }
  8%   {                                         opacity: var(--o, 0.85); }
  92%  {                                         opacity: var(--o, 0.85); }
  100% { transform: translate(var(--drift), 112vh); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .snowfall i, .sc-only--himalaya .flag, .boat { animation: none; }
}
/* रात्रि आरती — the ghat after dark, lamps on the water */
[data-scene="ratri"] {
  --sky-a: #1b2a4a; --sky-b: #101a30; --sky-c: #070c18;
  --sil: #0d1220; --sil-far: #1b2540; --ghat: #2a2436;
  --river-a: #22304f; --river-b: #0d1524;
  --star-op: 1; --ff-op: 0.9; --win-op: 0.9; --door-glow: 26px;
}
[data-scene="ratri"] .sun { opacity: 0 !important; }
[data-scene="ratri"] .moon { opacity: 1 !important; }
.sky, .far-bank, .ghat rect, .river, .temple, .tree { transition: fill 1.4s ease, opacity 1.4s ease; }

/* ── the rail ── */
.scenes {
  display: flex; gap: 0.5rem; overflow-x: auto; scrollbar-width: none;
  padding: 0.2rem 0.1rem 0.6rem; margin: 0 auto 0.7rem;
  max-width: min(44rem, 96vw); scroll-snap-type: x proximity;
}
.scenes::-webkit-scrollbar { display: none; }
.scene-chip {
  flex: 0 0 auto; scroll-snap-align: start;
  display: flex; flex-direction: column; align-items: center; gap: 0.3rem;
  padding: 0.35rem 0.4rem 0.4rem; border-radius: 0.85rem;
  background: none; border: 0; cursor: pointer; color: var(--cream);
  opacity: 0.62; transition: opacity 0.2s, transform 0.2s;
}
.scene-chip b { font-size: 0.72rem; font-weight: 600; white-space: nowrap; }
.scene-chip:hover { opacity: 0.9; }
.scene-chip.is-on { opacity: 1; }
.scene-chip.is-on .scene-chip__art { border-color: #ffd98f; box-shadow: 0 0 0 2px rgba(255,217,143,0.35); }
/* a tiny painted thumbnail per scene, so the rail reads at a glance */
.scene-chip__art {
  width: 3.6rem; height: 2.5rem; border-radius: 0.55rem;
  border: 1.5px solid rgba(246,234,216,0.28); overflow: hidden;
  box-shadow: inset 0 -8px 12px rgba(0,0,0,0.35);
}
.sc-mandir    { background: linear-gradient(180deg,#ffd9a0,#ff9e5e 52%,#8d4a2c); }
.sc-ghat      { background: linear-gradient(180deg,#ffcf94,#e8945e 46%,#7a4a63 72%,#3f2b40); }
.sc-vrindavan { background: linear-gradient(180deg,#ffd39b,#f59b63 44%,#4f7a2c); }
.sc-himalaya  { background: linear-gradient(180deg,#cfe6f2,#8fb6d1 50%,#4a5b6b); }
.sc-ratri     { background: linear-gradient(180deg,#1b2a4a,#101a30 55%,#070c18); }
@media (max-width: 767px) {
  .scenes { max-width: 100%; gap: 0.4rem; }
  .scene-chip__art { width: 3rem; height: 2.1rem; }
  .scene-chip b { font-size: 0.66rem; }
}
@media (prefers-reduced-motion: reduce) { .sky, .far-bank, .river, .temple, .tree { transition: none; } }

/* ── हिमालय ridges: depth comes from each layer being paler than the one in front ── */
.sc-only--himalaya .ridge-far { fill: var(--sil-far); opacity: 0.55; }
.sc-only--himalaya .ridge-mid { fill: var(--sil); opacity: 0.9; }
.sc-only--himalaya .snow { fill: #f2f8fc; opacity: 0.95; }

/* ── scene rail labels must survive any sky behind them ── */
.scene-chip b {
  padding: 0.1rem 0.4rem; border-radius: 999px;
  background: rgba(18, 10, 5, 0.62);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
}
.scene-chip.is-on b { background: rgba(18, 10, 5, 0.82); color: #ffd98f; }

/* ── रात्रि आरती is night by definition, whatever the clock says ──
   The [data-phase="morning"|"day"] rules force dark ink for a bright sky. This
   scene keeps that phase but paints a navy sky under it, so that ink went
   invisible. Restated here, after those rules, so source order settles it —
   equal specificity, no !important needed. हिमालय and वृंदावन keep the dark
   ink because their skies really are pale. */
[data-scene="ratri"] .quote,
[data-scene="ratri"] .tagline,
[data-scene="ratri"] .contact,
[data-scene="ratri"] .kbd-hints,
[data-scene="ratri"] .date-line,
[data-scene="ratri"] .clock-wrap,
[data-scene="ratri"] .online {
  color: var(--cream);
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.85);
}
[data-scene="ratri"] .quote { opacity: 0.95; }

/* ══════════ माहौल — the ambient mixer ══════════ */
.mixer {
  position: fixed; left: 50%; z-index: 44;
  bottom: calc(var(--player-h, 9rem) + 0.6rem);
  transform: translateX(-50%);
  width: min(94vw, 27rem); max-height: 62svh; overflow-y: auto;
  border-radius: 1.3rem; padding: 0.9rem 1rem 1.1rem;
  /* opaque on purpose: this panel is full of small text and thin sliders,
     and the hero title read straight through a translucent one */
  background: #170d07;
  border: 1px solid rgba(246, 234, 216, 0.14);
  box-shadow: 0 -16px 50px rgba(5, 2, 0, 0.75);
}
.mixer__head { display: flex; align-items: center; justify-content: space-between; }
.mixer__title { font-weight: 700; font-size: 0.95rem; }
.mixer__hint { margin-top: 0.15rem; font-size: 0.74rem; opacity: 0.65; }
.mixer__rows { margin-top: 0.7rem; display: flex; flex-direction: column; gap: 0.15rem; }

.mix-row {
  display: grid; grid-template-columns: 2.2rem 1fr 1fr auto;
  align-items: center; gap: 0.6rem;
  padding: 0.5rem 0.35rem; border-radius: 0.7rem;
  transition: background 0.2s;
}
.mix-row.on { background: rgba(255, 184, 92, 0.09); }
.mix-ic { font-size: 1.25rem; text-align: center; filter: saturate(0.9); }
.mix-row.on .mix-ic { filter: none; }
.mix-txt { min-width: 0; display: flex; flex-direction: column; }
.mix-txt b { font-size: 0.84rem; font-weight: 600; }
.mix-txt small { font-size: 0.68rem; opacity: 0.6; }
.mix-val { font-size: 0.68rem; opacity: 0.65; min-width: 2.4rem; text-align: right;
  font-family: ui-monospace, "SF Mono", Menlo, monospace; }
/* same 24px pointer target the seek and volume sliders use */
.mix-range { box-sizing: content-box; padding-block: 10px; background-clip: content-box; }
@media (pointer: coarse) { .mix-range { padding-block: 16px; } }
@media (max-width: 767px) {
  .mix-row { grid-template-columns: 1.9rem 1fr 1fr auto; gap: 0.45rem; }
  .mix-txt small { display: none; }
}

/* ══════════ the dock — the good stuff, on the scene ══════════
   The header chip row was where features went to hide. durga-puja keeps its
   dhak and its map on the scene itself, always visible. Same idea here. */
.dock {
  position: fixed; left: max(0.7rem, env(safe-area-inset-left)); top: 42%;
  z-index: 22; display: flex; flex-direction: column; gap: 0.55rem;
  transform: translateY(-50%);
}
.dock__b {
  display: flex; flex-direction: column; align-items: center; gap: 0.15rem;
  width: 3.5rem; padding: 0.5rem 0.2rem 0.4rem; border-radius: 1rem;
  background: rgba(24, 13, 8, 0.72); color: var(--cream);
  border: 1px solid rgba(246, 234, 216, 0.18);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  cursor: pointer; transition: transform 0.18s, border-color 0.18s, background 0.18s;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
}
.dock__b b { font-size: 0.6rem; font-weight: 600; letter-spacing: 0.02em; }
.dock__b:hover { transform: translateX(3px); border-color: rgba(255, 217, 143, 0.6); }
.dock__b.active { border-color: #ffd98f; background: rgba(255, 184, 92, 0.2); }
/* a first-visit pulse, so people know these are live */
.dock__b.nudge { animation: dockNudge 2.6s ease-in-out 3; }
@keyframes dockNudge {
  0%, 100% { box-shadow: 0 6px 18px rgba(0,0,0,0.45); }
  50% { box-shadow: 0 6px 18px rgba(0,0,0,0.45), 0 0 0 6px rgba(255, 184, 92, 0.18); }
}
/* state the visible case explicitly rather than relying on the initial value —
   the hidden rule plus a transition left it computing 0 after .started landed */
.dock { opacity: 0; pointer-events: none; transition: opacity 0.5s; }
body.started .dock { opacity: 1; pointer-events: auto; }
body.zen .dock { opacity: 0; pointer-events: none; }
@media (max-width: 767px) {
  /* on a phone the dock sits along the bottom, clear of the player */
  .dock {
    /* clear the scenes rail, which occupies the band directly above the player */
    left: 50%; top: auto; bottom: calc(var(--player-h, 9rem) + 5.6rem);
    transform: translateX(-50%); flex-direction: row;
  }
  .dock__b { width: 4.2rem; }
  .dock__b:hover { transform: none; }
}
@media (prefers-reduced-motion: reduce) { .dock__b.nudge { animation: none; } }
/* the scenes rail flashes when the dock points at it */
.scenes.flash .scene-chip__art { animation: railFlash 0.9s ease 2; }
@keyframes railFlash {
  50% { box-shadow: 0 0 0 4px rgba(255, 184, 92, 0.45); border-color: #ffd98f; }
}
@media (prefers-reduced-motion: reduce) { .scenes.flash .scene-chip__art { animation: none; } }

/* ══════════ panel close buttons ══════════
   These used .icon-btn, whose width is a flex basis — inside the panel headers
   they shrank to the glyph (10px measured) and read as invisible. `flex: none`
   plus a real background so they are unmistakably a button. */
.panel-x {
  flex: none;
  display: grid; place-items: center;
  width: 2.4rem; height: 2.4rem; border-radius: 50%;
  background: rgba(246, 234, 216, 0.10);
  border: 1px solid rgba(246, 234, 216, 0.22);
  color: var(--cream); font-size: 0.95rem; line-height: 1;
  cursor: pointer; transition: background 0.18s, border-color 0.18s, transform 0.15s;
}
.panel-x:hover { background: rgba(246, 234, 216, 0.2); border-color: rgba(255, 217, 143, 0.75); }
.panel-x:active { transform: scale(0.94); }
.panel-x:focus-visible { outline: 2px solid #ffd98f; outline-offset: 2px; }


/* ══════════ panel scrim + entrance ══════════
   पंचांग and माहौल both open over a moving scene. Without a scrim and without
   motion, an opened panel reads as part of the page — reported as "I click and
   nothing happens". The scrim dims the scene, catches the tap-to-close, and the
   short rise tells the eye that something arrived. */
.panel-scrim {
  position: fixed; inset: 0; z-index: 44;
  background: rgba(12, 7, 3, 0.55);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
  animation: scrimIn 0.22s ease both;
}
.panel-scrim[hidden] { display: none; }
@keyframes scrimIn { from { opacity: 0; } to { opacity: 1; } }

.panchang:not([hidden]), .mixer:not([hidden]) { animation: panelRise 0.26s cubic-bezier(0.2, 0.9, 0.3, 1) both; }
/* `translate` and `scale` are independent transform properties — they COMPOSE
   with the element's own `transform`. Animating `transform` here instead threw
   the sheet a half-height off screen, because these panels centre themselves
   with translateX(-50%) and the keyframe replaced it outright. */
@keyframes panelRise {
  from { opacity: 0; translate: 0 14px; scale: 0.98; }
  to   { opacity: 1; translate: 0 0;    scale: 1; }
}

/* ── पंचांग: somewhere to go next ── */
.panchang__acts {
  display: flex; gap: 0.5rem; margin: 0.85rem 0 0.2rem; flex-wrap: wrap;
}
.pg-act {
  flex: 1 1 auto; min-height: 46px; padding: 0.55rem 0.9rem;
  display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;
  border-radius: 999px; border: 1px solid rgba(255, 217, 143, 0.28);
  background: rgba(255, 217, 143, 0.07); color: var(--cream);
  font: inherit; font-size: 0.86rem; cursor: pointer;
  transition: background 0.18s, border-color 0.18s;
}
.pg-act:hover, .pg-act:focus-visible { background: rgba(255, 217, 143, 0.16); border-color: rgba(255, 217, 143, 0.5); }
.pg-act--go { background: rgba(255, 200, 90, 0.16); border-color: rgba(255, 200, 90, 0.42); font-weight: 600; }
.pg-act--go:hover { background: rgba(255, 200, 90, 0.26); }

@media (prefers-reduced-motion: reduce) {
  .panel-scrim, .panchang:not([hidden]), .mixer:not([hidden]) { animation: none; }
}


/* ══════════ माहौल: say when it is on ══════════
   People opened the mixer, raised a layer, closed it, and then could not work
   out why sound was still playing — the panel was the only place that knew.
   A count rides on both entry points, and the panel gets one button that
   stops everything. */
.mix-badge {
  display: inline-grid; place-items: center;
  min-width: 1.15rem; height: 1.15rem; padding: 0 0.28rem;
  border-radius: 999px; background: #ffc85a; color: #2a1508;
  font-size: 0.68rem; font-weight: 700; line-height: 1;
  box-shadow: 0 0 0 2px rgba(20, 12, 6, 0.55);
}
.mix-badge[hidden] { display: none; }
/* `.chip b { color: #ffcf7d }` outranks `.mix-badge` and repainted the count
   gold-on-gold — measured 1.06:1, i.e. invisible. Match that specificity. */
.chip .mix-badge, b.mix-badge { color: #2a1508; }
.mix-badge--dock {
  position: absolute; top: 0.28rem; right: 0.34rem; font-style: normal;
  animation: badgePulse 2.4s ease-in-out infinite;
}
#dockMixer { position: relative; }
@keyframes badgePulse {
  0%, 100% { box-shadow: 0 0 0 2px rgba(20,12,6,0.55), 0 0 0 0 rgba(255,200,90,0.55); }
  60%      { box-shadow: 0 0 0 2px rgba(20,12,6,0.55), 0 0 0 7px rgba(255,200,90,0); }
}
/* the chip is highlighted while anything plays, not only while the panel is open */
.chip.sounding { border-color: rgba(255,200,90,0.55); background: rgba(255,200,90,0.14); }

.mixer__foot {
  display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
  margin-top: 0.7rem; padding-top: 0.7rem; border-top: 1px solid rgba(246,234,216,0.12);
}
.mixer__foot:has(> [hidden]:only-child), .mixer__foot:empty { border-top: 0; }
.mixer__on { flex: 1 1 8rem; font-size: 0.76rem; opacity: 0.8; }
.mixer__on[hidden] { display: none; }
.mix-alloff {
  min-height: 40px; padding: 0.45rem 0.95rem; border-radius: 999px;
  border: 1px solid rgba(255,150,120,0.4); background: rgba(255,120,90,0.14);
  color: var(--cream); font: inherit; font-size: 0.82rem; font-weight: 600; cursor: pointer;
  transition: background 0.18s;
}
.mix-alloff:hover, .mix-alloff:focus-visible { background: rgba(255,120,90,0.26); }
.mix-alloff[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { .mix-badge--dock { animation: none; } }


/* ==== GENERATED by build-scene-palettes.mjs — do not edit by hand ==== */
/* Scene × phase. See the script for why these are mixed rather than
   overriding: a place does not have its own light. */

/* घाट — a riverfront at first light, wide open water */
[data-phase="morning"][data-scene="ghat"] {
  --sky-a: #ffe7c0; --sky-b: #f7b686; --sky-c: #888fac;
  --sil: #453647; --sil-far: #75677b; --ghat: #be9a7d;
  --river-a: #a1a5b0; --river-b: #54596c;
}
[data-phase="day"][data-scene="ghat"] {
  --sky-a: #ffe9c4; --sky-b: #f6b98a; --sky-c: #7f93b8;
  --sil: #44384b; --sil-far: #736a81; --ghat: #c19f80;
  --river-a: #8dabc5; --river-b: #3f5d7c;
}
[data-phase="evening"][data-scene="ghat"] {
  --sky-a: #735e6d; --sky-b: #b7636a; --sky-c: #cb8667;
  --sil: #36283e; --sil-far: #63506d; --ghat: #a27a6e;
  --river-a: #ae7a74; --river-b: #503856;
}
[data-phase="night"][data-scene="ghat"] {
  --sky-a: #212330; --sky-b: #2d3146; --sky-c: #313f66;
  --sil: #23233a; --sil-far: #424766; --ghat: #71696b;
  --river-a: #384b71; --river-b: #16223e;
}

/* वृंदावन — kadamba groves over the Yamuna */
[data-phase="morning"][data-scene="vrindavan"] {
  --sky-a: #ffd49c; --sky-b: #f69b63; --sky-c: #b95c3f;
  --sil: #404d20; --sil-far: #5e742e; --ghat: #a7775a;
  --river-a: #879962; --river-b: #555e3a;
}
[data-phase="day"][data-scene="vrindavan"] {
  --sky-a: #ffd39b; --sky-b: #f59b63; --sky-c: #b55a3f;
  --sil: #3f5121; --sil-far: #5b7730; --ghat: #a87a5a;
  --river-a: #719d6d; --river-b: #406344;
}
[data-phase="evening"][data-scene="vrindavan"] {
  --sky-a: #73575f; --sky-b: #b7585d; --sky-c: #dd733e;
  --sil: #333a1f; --sil-far: #525a31; --ghat: #905e53;
  --river-a: #a1744d; --river-b: #503b3e;
}
[data-phase="night"][data-scene="vrindavan"] {
  --sky-a: #21202c; --sky-b: #2d2e42; --sky-c: #373959;
  --sil: #203123; --sil-far: #344f37; --ghat: #635456;
  --river-a: #31485d; --river-b: #162332;
}

/* हिमालय — thin cold air, snow, aerial perspective */
[data-phase="morning"][data-scene="himalaya"] {
  --sky-a: #e9efee; --sky-b: #becdd7; --sky-c: #879aaf;
  --sil: #5a606c; --sil-far: #9ea4ab; --ghat: #d1cfd1;
  --river-a: #dad6d4; --river-b: #a1a7b0;
}
[data-phase="day"][data-scene="himalaya"] {
  --sky-a: #e6f1f7; --sky-b: #b7d2e4; --sky-c: #7e9fbb;
  --sil: #5b6572; --sil-far: #9faab4; --ghat: #d5d7d9;
  --river-a: #cee2ed; --river-b: #95b5c8;
}
[data-phase="evening"][data-scene="himalaya"] {
  --sky-a: #6b617e; --sky-b: #a26b89; --sky-c: #cb8b69;
  --sil: #47495a; --sil-far: #847f92; --ghat: #b0a3b0;
  --river-a: #ca9286; --river-b: #765f78;
}
[data-phase="night"][data-scene="himalaya"] {
  --sky-a: #1f2336; --sky-b: #263450; --sky-c: #314166;
  --sil: #303c50; --sil-far: #5b6c82; --ghat: #7d899d;
  --river-a: #46577a; --river-b: #29354f;
}
/* ==== end generated ==== */


/* ══════════ audit fixes — measured on a 375px phone ══════════ */

/* Scene labels were 10.6px and the quote source 11.2px. This audience skews
   older; 12px is the floor for anything anyone is expected to read. */
.scene-chip b { font-size: 0.75rem; }
.quote .src { font-size: 0.75rem; }

/* Footer links measured 168x29 and 87x29 — well under a 44px target, and they
   sit close together, so the 8px separation rule applies too. */
.contact a, .bmc-link {
  display: inline-flex; align-items: center; min-height: 44px;
  padding-inline: 0.35rem;
}
.contact { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.5rem; justify-content: center; }

/* 82x36 */
.ic-add { min-height: 44px; }

/* The weekday rows measured 330x41 — 3px short, and they are the main way in
   to a bhajan from the footer. */
.week-link { min-height: 44px; }


/* A 4px-tall slider is a 4px-tall drag target. Extend the hit area vertically
   without changing how the track looks — `content-box` + `background-clip`
   together, because border-box padding would zero the content box and the
   track would paint nothing at all (this exact pair bit the seek bar once). */
.range {
  box-sizing: content-box;
  padding-block: 20px;          /* 4px track + 40px = 44px hit area */
  background-clip: content-box;
}

/* measured 43x43 and 38x38 — one and six pixels under the minimum */
.icon-btn { min-width: 44px; min-height: 44px; }
.panel-x, .vadya__close, .wa-close { min-width: 44px; min-height: 44px; }

/* the दीया count was 9.3px — it is a number people actually read */
.diya-badge { font-size: 0.7rem; min-width: 1.3rem; height: 1.3rem; }


/* Playlist panel — the most-used surface after the player itself, and every
   control in it measured short: tabs 35px, rows 40px, the ⭐ 27x37. */
.ptab { min-height: 44px; }
.prow-meta { min-height: 44px; }
.pstar { min-width: 44px; min-height: 44px; display: inline-grid; place-items: center; padding: 0; }
.zen-play { min-width: 44px; min-height: 44px; }
.wa-block-btn { min-height: 44px; }


/* the footer links measured 138x19 — .contact a did not match them */
.contact a, .footer a:not(.week-link), .bmc-link {
  display: inline-flex; align-items: center; min-height: 44px; padding-inline: 0.3rem;
}
#shareBtn { min-width: 44px; }

.byline { margin-top: 0.35rem; font-size: 0.78rem; opacity: 0.72; }
.byline a { color: #ffd98f; text-decoration: none; border-bottom: 1px solid rgba(255,217,143,0.35);
  display: inline-flex; align-items: center; min-height: 44px; }


/* ══════════ about + FAQ ══════════
   Real reading content, below the scene so it never competes with it. */
.about { padding: 2.4rem 1rem 0.5rem; }
.about__inner {
  max-width: 40rem; margin: 0 auto;
  background: rgba(20, 12, 6, 0.55); border: 1px solid rgba(246, 234, 216, 0.12);
  border-radius: 1.2rem; padding: 1.5rem 1.3rem;
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
}
.about h2 {
  font-size: 1.05rem; font-weight: 700; color: #ffd98f;
  margin: 1.4rem 0 0.5rem; line-height: 1.4;
}
.about h2:first-child { margin-top: 0; }
.about p {
  font-size: 0.94rem; line-height: 1.75; color: var(--cream);
  opacity: 0.9; margin-bottom: 0.6rem;
}
.about b { color: #ffe6b8; font-weight: 600; }
.about a { color: #ffd98f; }
.about__en { margin-top: 1.3rem; padding-top: 1rem; border-top: 1px solid rgba(246,234,216,0.12);
  font-size: 0.86rem; opacity: 0.72; }
@media (max-width: 640px) { .about { padding-inline: 0.8rem; } .about__inner { padding: 1.2rem 1rem; } }


/* FAQ as native <details> — one divided list, no JS, keyboard-operable for free,
   and the answers stay in the DOM while collapsed so they are still indexed. */
.faq-kicker { font-size: 0.66rem; letter-spacing: 0.14em; text-transform: uppercase;
  opacity: 0.45; margin: -0.2rem 0 0.75rem; }
.faq-list {
  border: 1px solid rgba(246, 234, 216, 0.12); border-radius: 1rem;
  background: rgba(12, 7, 3, 0.35); overflow: hidden;
}
.faq-item { border-bottom: 1px solid rgba(246, 234, 216, 0.1); }
.faq-item:last-child { border-bottom: 0; }
.faq-item > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 0.7rem;
  min-height: 56px; padding: 0.85rem 1rem;
  font-size: 0.92rem; font-weight: 600; line-height: 1.5; color: #ffe6b8;
  transition: background 0.18s;
}
.faq-item > summary::-webkit-details-marker { display: none; }
.faq-item > summary::after {
  content: ""; margin-inline-start: auto; flex: none;
  width: 0.55rem; height: 0.55rem;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg); transition: transform 0.22s ease; opacity: 0.65;
}
.faq-item[open] > summary::after { transform: rotate(-135deg); }
.faq-item > summary:hover, .faq-item > summary:focus-visible { background: rgba(255, 217, 143, 0.07); }
.faq-item[open] > summary { color: #ffd98f; }
.faq-a { padding: 0 1rem 1rem; }
.faq-a p { font-size: 0.89rem; line-height: 1.75; opacity: 0.85; margin: 0; }
@media (prefers-reduced-motion: reduce) { .faq-item > summary::after { transition: none; } }

/* Page text was reading through the dock buttons, which looks broken rather
   than like depth. The container has no background of its own — the buttons do,
   so that is where the opacity belongs. */
.dock__b { background: rgba(20, 11, 6, 0.92); }


/* ══════════ zen / wallpaper — the point is nothing but the scene ══════════
   The reading content was added for crawlers and for people who want it; in
   wallpaper mode it is exactly the distraction the mode exists to remove. */
body.zen .about, body.zen .byline, body.zen .contact,
body.zen .kbd-hints, body.zen .day-banner, body.zen .scenes {
  opacity: 0 !important; pointer-events: none !important;
}
body.zen .about { display: none !important; }

/* ══════════ the player floats, it does not block ══════════
   Measured against durga-puja: theirs sits at 0.30 alpha over 1px of blur, ours
   was 0.62 over 16px — twice as opaque with sixteen times the blur, which is
   why the temple disappeared behind it. Landing between the two: light enough
   to see the scene through, solid enough that the text still passes contrast
   (verified, not assumed). The border goes warm so it reads as part of the
   scene rather than as chrome. */
.player {
  background: rgba(22, 12, 7, var(--veil, 0.62));
  backdrop-filter: blur(7px); -webkit-backdrop-filter: blur(7px);
  border-color: rgba(224, 161, 58, 0.3);
  box-shadow: 0 20px 56px -16px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(224, 161, 58, 0.06);
  animation: playerBreathe 7s ease-in-out infinite;
}
/* How light the panel can be depends entirely on what is behind it. durga-puja
   runs at 0.30 because their scene is a night pandal; मन्दिर at midday sits on a
   pale cream sky, where 0.46 measured 3.25:1 on the title — a fail. So the veil
   follows the light: nearly gone at night, heavier at noon, and never below the
   point where the text stops being readable. */
/* Tuned against measured contrast, not by eye: at 0.48 the secondary artist
   line came out 3.47:1 in the evening, and 4.44:1 at noon — both under 4.5. */
[data-phase="night"]   { --veil: 0.42; }
[data-phase="evening"] { --veil: 0.68; }
[data-phase="morning"] { --veil: 0.66; }
[data-phase="day"]     { --veil: 0.78; }

/* the panel is alive the way a lamp is, not the way a notification is */
@keyframes playerBreathe {
  0%, 100% { box-shadow: 0 20px 56px -16px rgba(0,0,0,0.7), 0 0 0 1px rgba(224,161,58,0.06); }
  50%      { box-shadow: 0 22px 64px -14px rgba(0,0,0,0.72), 0 0 0 1px rgba(224,161,58,0.22); }
}
@media (prefers-reduced-motion: reduce) { .player { animation: none; } }


/* The chip row was still fully opaque in wallpaper mode. An earlier
   `body.zen .chips { opacity: 0 !important }` matches the element and is the only
   opacity rule that does, yet computed opacity stays 1 — the older declaration is
   not being applied and the cascade does not explain why. Restating it here works
   and is verified; the original is left in place rather than risk breaking the
   other six selectors it also covers. */
body.zen .chips {
  /* `visibility` rather than opacity alone: the opacity transition on this row
     was observed still "running" 2.5s into a 0.8s fade with the value stuck at
     1, so the row never actually disappeared. visibility cannot get stranded
     mid-transition, and it still animates because it is a discrete step at the
     end of the timing function. */
  visibility: hidden; opacity: 0; pointer-events: none;
  transition: opacity 0.8s ease, visibility 0s linear 0.8s;
}
body.zen.zen-awake .chips {
  visibility: visible; opacity: 1; pointer-events: auto;
  transition: opacity 0.3s ease, visibility 0s;
}


/* the long licence explanation folds away; the CC BY attribution stays out */
.legal-more { margin-bottom: 0.6rem; }
.legal-more > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 0.4rem;
  min-height: 44px; font-size: 0.72rem; letter-spacing: 0.04em; opacity: 0.6;
}
.legal-more > summary::-webkit-details-marker { display: none; }
.legal-more > summary::after {
  content: ""; width: 0.4rem; height: 0.4rem;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg); transition: transform 0.2s;
}
.legal-more[open] > summary::after { transform: rotate(-135deg); }
.legal-more > summary:hover { opacity: 0.9; }
.legal-more[open] > summary { opacity: 0.8; margin-bottom: 0.3rem; }
@media (prefers-reduced-motion: reduce) { .legal-more > summary::after { transition: none; } }


/* ══════════ tooltips on touch ══════════
   `[data-tip]:hover` never fires on a phone — (hover: none) — so every tooltip
   on the site was desktop-only. The dock buttons carry visible labels, but the
   chip row is icon-only, so on touch a tap reveals the tip briefly. */
@media (hover: none) {
  [data-tip]:active::after,
  .top [data-tip]:active::after {
    opacity: 1; transform: translateX(-50%) translateY(0); visibility: visible;
  }
}

/* ══════════ wallpaper mode: the clock becomes the subject ══════════
   In zen the date and time were still tucked in the top-left the way they sit
   in the normal layout, competing with nothing and framing nothing. Centred and
   enlarged, they become the thing the wallpaper is *for*. */
body.zen .clock-wrap {
  /* full width rather than left:50% + translateX. A fixed box positioned at
     left:50% is only offered the remaining half of the viewport — 188px on a
     375px phone — so "9:38 PM" wrapped onto two lines and read as a mistake. */
  position: fixed; left: 0; right: 0; top: 9vh;
  text-align: center; z-index: 12; white-space: nowrap;
  /* the block is ~11rem tall once the clock is at wallpaper size; the hero has
     to be pushed clear of it or the greeting sits underneath the numbers */
  --zen-clock-h: 11rem;
  transition: all 0.9s cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* a "welcome back" line is chrome, and chrome is what this mode removes */
body.zen .greeting { opacity: 0 !important; pointer-events: none; }
body.zen .hero { padding-top: calc(9vh + 11rem) !important; }
body.zen .clock-wrap .clock, body.zen #clock {
  font-size: clamp(3.2rem, 15vw, 6rem); font-weight: 300;
  letter-spacing: 0.06em; line-height: 1;
  text-shadow: 0 4px 40px rgba(0, 0, 0, 0.55);
}
body.zen .date-line {
  display: block; margin: 0.9rem auto 0; font-size: clamp(0.9rem, 3.6vw, 1.05rem);
  opacity: 0.85; letter-spacing: 0.04em; border: 0; text-align: center;
}
@media (prefers-reduced-motion: reduce) { body.zen .clock-wrap { transition: none; } }


/* A clock pinned to the top must be sized by the viewport's HEIGHT, not its
   width. At 812x375 the width-based clamp produced a 96px clock in a 245px-tall
   block — two thirds of a landscape screen, running into the player. */
@media (max-height: 520px) {
  body.zen .clock-wrap { top: 5vh; }
  body.zen .clock-wrap .clock, body.zen #clock { font-size: clamp(1.9rem, 11vh, 3.2rem); }
  body.zen .date-line { margin-top: 0.45rem; font-size: 0.82rem; }
  body.zen .hero { padding-top: calc(5vh + 6.5rem) !important; }
}

/* a route's own credit — an icon and a couple of words, sized so it adds a
   single line to the footer rather than a paragraph */
.route-note { margin: 0.3rem 0 0.8rem; }
.route-yt {
  display: inline-flex; align-items: center; gap: 0.45rem;
  min-height: 44px; padding: 0 0.15rem;
  font-size: 0.78rem; letter-spacing: 0.02em;
  color: #ffd98f; opacity: 0.75; text-decoration: none;
  transition: opacity 0.18s;
}
.route-yt:hover, .route-yt:focus-visible { opacity: 1; }
.route-yt svg { flex: none; color: #ff0033; }


/* A fact is not a shloka and should not be dressed as one. Same slot, same
   fade, slightly plainer voice — so the शास्त्र lines keep their weight. */
.quote.is-flash {
  font-style: normal;
  font-size: clamp(0.86rem, 3.2vw, 0.98rem);
  opacity: 0.88;
}
.quote.is-flash::before {
  content: "क्या आप जानते हैं";
  display: block;
  font-size: 0.62rem; letter-spacing: 0.16em; text-transform: uppercase;
  opacity: 0.45; margin-bottom: 0.4rem;
}


/* ══════════ share this bhent ══════════
   Sat two taps deep in a menu, so it may as well not have existed. Next to the
   title it is both findable and self-explanatory: the thing beside the song
   name shares the song. */
.song-share {
  flex: none; display: grid; place-items: center;
  width: 44px; height: 44px; margin-inline-start: 0.2rem;
  border-radius: 50%; color: var(--cream); opacity: 0.6;
  background: transparent; border: 0; cursor: pointer;
  transition: opacity 0.18s, background 0.18s, transform 0.15s;
}
.song-share:hover, .song-share:focus-visible { opacity: 1; background: rgba(255, 217, 143, 0.14); }
.song-share:active { transform: scale(0.92); }
/* one slow pulse the first time somebody has been listening a while, then never
   again — a hint, not a nag */
.song-share.beckon { animation: shareBeckon 2.6s ease-in-out 3; opacity: 1; }
@keyframes shareBeckon {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 200, 90, 0); }
  45%      { box-shadow: 0 0 0 8px rgba(255, 200, 90, 0.22); }
}
@media (prefers-reduced-motion: reduce) { .song-share.beckon { animation: none; } }


/* everything hushed — the scene keeps living, it just stops making noise */
body.hushed .player { border-color: rgba(255, 140, 120, 0.4); }
body.hushed #muteBtn { color: #ff9c86; background: rgba(255, 120, 90, 0.18); }


/* the kill switch reads as a stop, not another toy */
.chip--hush { border-color: rgba(255, 150, 120, 0.32); }
.chip--hush:hover { border-color: rgba(255, 150, 120, 0.6); }
.chip--hush.active { background: rgba(255, 120, 90, 0.2); border-color: rgba(255, 140, 110, 0.65); color: #ffb4a0; }
body.hushed .chip--hush { background: rgba(255, 120, 90, 0.2); border-color: rgba(255, 140, 110, 0.65); color: #ffb4a0; }


/* ══════════ माखन मटकी — the जन्माष्टमी invitation ══════════
   Hangs at the edge of the scene only inside the festival window. It swings on
   its rope the way a real dahi-handi does, and it is the one thing on screen
   allowed to ask for a tap. */
.matki {
  position: fixed; right: clamp(0.6rem, 3vw, 2.5rem);
  /* below .top, which holds the clock and the chip row and measured 146px tall
     on a 375px phone — the मटकी was landing 49×36px inside the chips */
  top: clamp(11rem, 24vh, 14rem);
  z-index: 24; width: clamp(58px, 9vw, 92px);
  display: flex; flex-direction: column; align-items: center;
  background: none; border: 0; padding: 0; cursor: pointer;
  transform-origin: 50% 0;
  animation: matkiSway 4.2s ease-in-out infinite;
}
.matki[hidden] { display: none; }
.matki__rope {
  width: 2px; height: clamp(1.6rem, 5vh, 3rem);
  background: linear-gradient(#e0a13a, #b8791f);
  border-radius: 2px;
}
.matki__pot {
  width: 100%; height: auto;
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.45));
  transition: transform 0.2s;
}
.matki:hover .matki__pot, .matki:focus-visible .matki__pot { transform: scale(1.06); }
.matki:active .matki__pot { transform: scale(0.94); }
.matki__hint {
  margin-top: 0.3rem; font-size: 0.6rem; font-weight: 700; letter-spacing: 0.06em;
  color: #ffe6b8; background: rgba(20, 11, 6, 0.72);
  padding: 0.16rem 0.44rem; border-radius: 999px;
  border: 1px solid rgba(255, 200, 90, 0.35);
  white-space: nowrap;
}
@keyframes matkiSway {
  0%, 100% { transform: rotate(-3.2deg); }
  50%      { transform: rotate(3.2deg); }
}
/* one ornament per festival — the rest stay out of the layout entirely */
.orn { display: none; }
.matki[data-ornament="matki"]   .orn--matki,
.matki[data-ornament="diya"]    .orn--diya,
.matki[data-ornament="chunri"]  .orn--chunri,
.matki[data-ornament="trishul"] .orn--trishul,
.matki[data-ornament="modak"]   .orn--modak { display: block; }

/* the day itself — it should be impossible to miss */
body.fest-today .matki { animation: matkiSway 2.6s ease-in-out infinite, matkiGlow 2.2s ease-in-out infinite; }
@keyframes matkiGlow {
  0%, 100% { filter: drop-shadow(0 6px 14px rgba(0,0,0,0.45)); }
  50%      { filter: drop-shadow(0 6px 22px rgba(255, 200, 90, 0.55)); }
}
@media (prefers-reduced-motion: reduce) { .matki { animation: none; } }
body.zen .matki { opacity: 0; pointer-events: none; }

/* ══════════ जन्माष्टमी — the place turns over ══════════
   Deep Vrindavan night with the peacock blues and the butter golds. Layered on
   top of whatever scene is showing, so it works from anywhere. */
/* The sky colours live in festivals.json, one block per phase — they used to be
   duplicated here as a single night palette, which is why a 9am visit during
   जन्माष्टमी looked like midnight. Only what the clock cannot decide stays here. */
body.fest-on { --door-glow: 30px; }
/* जन्माष्टमी is a midnight festival, but the sun still rises on it. Lamps, stars
   and fireflies belong to the dark half of the day, not to the whole window. */
body.fest-on[data-phase="evening"],
body.fest-on[data-phase="night"] { --star-op: 1; --ff-op: 1; --win-op: 1; }
body.fest-on[data-phase="evening"] .moon,
body.fest-on[data-phase="night"] .moon { opacity: 1 !important; }
/* मोर पंख light across the top of the scene */
body.fest-on .scene::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 3;
  background:
    radial-gradient(120% 60% at 50% 0%, rgba(47, 111, 208, 0.22) 0%, transparent 60%),
    radial-gradient(90% 50% at 20% 12%, rgba(232, 178, 31, 0.16) 0%, transparent 55%),
    radial-gradient(90% 50% at 82% 8%, rgba(30, 122, 107, 0.18) 0%, transparent 55%);
  animation: festShimmer 9s ease-in-out infinite;
}
body.fest-on[data-phase="morning"] .scene::before,
body.fest-on[data-phase="day"] .scene::before { opacity: 0.45; mix-blend-mode: soft-light; }
@keyframes festShimmer { 0%, 100% { opacity: 0.85; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { body.fest-on .scene::before { animation: none; } }

/* a banner that names the day */
.fest-banner {
  position: fixed; left: 50%; transform: translateX(-50%);
  top: clamp(10.5rem, 23vh, 13rem); z-index: 25;
  display: none; align-items: center; gap: 0.5rem;
  padding: 0.42rem 1rem; border-radius: 999px;
  background: rgba(16, 20, 58, 0.82); color: #ffe6b8;
  border: 1px solid rgba(255, 200, 90, 0.4);
  font-size: clamp(0.74rem, 3vw, 0.86rem); font-weight: 700;
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  white-space: nowrap; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
}
body.fest-on .fest-banner { display: inline-flex; }
body.zen .fest-banner { opacity: 0; }
.fest-banner button {
  background: none; border: 0; color: inherit; opacity: 0.6;
  font: inherit; cursor: pointer; padding: 0 0.15rem; min-width: 32px; min-height: 32px;
}
.fest-banner button:hover { opacity: 1; }


/* ══════════ जन्माष्टमी — the scene ══════════
   Hidden until the मटकी is tapped, so the festival arrives rather than sitting
   there all year. */
.fest-only { display: none; }
body.fest-on[data-fest="janmashtami"] .fest-only--janmashtami { display: block; }
body.fest-on[data-fest="ganesh"] .fest-only--ganesh { display: block; }

/* गणेश चतुर्थी: the मंडप. Cloth and गेंदा keep their own colour like the झूला
   does — they are painted objects in the scene, not silhouettes — while the
   posts take --sil so the structure still sits in the hour's light. */
.fest-post { fill: var(--sil); }
.fest-canopy { fill: #b8342f; }
.fest-canopy-edge { stroke: #e0a13a; stroke-width: 5; }
.fest-genda { fill: #f0a02a; }
.fest-dhwaj { fill: #e0632a; }
.fest-thali { fill: #c9a227; }
.fest-modak { fill: #f2ddb0; }
.fest-durva { fill: #2f7a3e; }
/* the ध्वज flutters from its pole */
.fest-dhwaj {
  transform-box: fill-box; transform-origin: 0% 50%;
  animation: dhwajFlutter 4.4s ease-in-out infinite;
}
@keyframes dhwajFlutter {
  0%, 100% { transform: skewY(-3deg) scaleX(1); }
  50%      { transform: skewY(3deg) scaleX(0.94); }
}

.fest-kadamb ellipse { fill: #16351f; }
.fest-kadamb rect { fill: #241a12; }
.fest-jhula path[fill="#7a1338"] { filter: drop-shadow(0 6px 14px rgba(0,0,0,0.5)); }
.fest-swag circle { fill: #f2a03c; }
/* the swing moves the way a hung thing moves — from the top, not the middle */
.fest-jhula {
  transform-box: fill-box; transform-origin: 50% 0;
  animation: jhulaSway 5.4s ease-in-out infinite;
}
@keyframes jhulaSway {
  0%, 100% { transform: rotate(-1.6deg); }
  50%      { transform: rotate(1.6deg); }
}
.fest-mor { transform-box: fill-box; transform-origin: 50% 100%;
  animation: morBob 6.5s ease-in-out infinite; }
@keyframes morBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }


/* ── नींद का समय ──────────────────────────────────────────────────────────
   People fall asleep to this. Without a timer the only way to stop it is to
   wake up and stop it, which is the one thing they were trying to avoid. */
#sleepBtn { position: relative; }
#sleepBtn.is-on { color: #ffd98f; }
.sleep-left {
  position: absolute; bottom: 2px; right: 2px;
  font-size: 0.56rem; line-height: 1; padding: 0.1rem 0.18rem;
  border-radius: 4px; background: rgba(255, 217, 143, 0.22); color: #ffd98f;
}
/* .player-wrap is sticky with z-index 20, and a child cannot escape its parent's
   stacking context — so this menu was pinned under पंचांग (46), वाद्य (43) and the
   panels (42) no matter what z-index it carried. The wrap is lifted while the
   menu is open instead, and dropped again on close. */
.player-wrap.menu-open { z-index: 60; }
.sleep-menu {
  position: absolute; bottom: calc(100% + 0.5rem); right: 0; z-index: 30;
  display: flex; flex-direction: column; gap: 0.15rem; padding: 0.45rem;
  min-width: 11rem; border-radius: 0.8rem;
  background: var(--glass, rgba(26, 15, 10, 0.92));
  border: 1px solid var(--glass-border, rgba(246, 234, 216, 0.22));
  backdrop-filter: blur(10px);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
}
.sleep-menu[hidden] { display: none; }
.sleep-menu__t { font-size: 0.7rem; opacity: 0.7; margin: 0.1rem 0.35rem 0.3rem; }
.sleep-menu button {
  text-align: left; padding: 0.5rem 0.55rem; border-radius: 0.5rem;
  font-size: 0.84rem; color: inherit; background: none; border: 0;
  min-height: 44px;              /* thumb target, same rule as every other control */
}
.sleep-menu button:hover, .sleep-menu button:focus-visible { background: rgba(255, 217, 143, 0.14); }
.sleep-menu button[aria-current="true"] { color: #ffd98f; font-weight: 700; }


/* ── वीडियो देखिए ────────────────────────────────────────────────────────────
   The cover disc already IS the YouTube player, cropped to a circle — iOS
   refuses a hidden one, so the video has always been there, just 52px across
   and round. Tapping it opens the same iframe out to a proper panel, which
   costs no new control on a row that already carries six. */
.yt-stage { cursor: pointer; }

/* ── the set ───────────────────────────────────────────────────────────────
   A wooden console television, parked above the player. Casing, grilles,
   knobs and doors are all CSS — no image is loaded, which matters on the
   phones this site is built for.

   The shape follows the sets these bhajans were first heard beside: a 4:3
   picture tube in the middle, a slatted speaker grille to the left, a bakelite
   knob plate to the right, a cabinet lip along the top and two stubby legs.

   Layout is a flex row rather than the calc() arithmetic that was here before.
   The screen carries `aspect-ratio: 4/3` and the cabinet sizes itself around
   it, so there is no bezel sum to keep in step — keeping that sum in step is
   what the picture overhanging the right edge was, twice. */
/* .player carries backdrop-filter: blur(7px), and a filtered element becomes
   the containing block for its `position: fixed` descendants. The set is a
   descendant of the player — it has to be, the cover disc IS the live iframe
   and moving that node would reload it and restart playback — so its `fixed`
   was resolving against the player box rather than the viewport. On a wide
   screen that happened to land somewhere plausible; at 375px it put the
   cabinet at top: -235px, off the top of the page and under the rails.

   Dropping the blur for as long as the set is open hands the containing block
   back to the viewport. Nothing is lost visually: the cabinet covers that area
   anyway, and the rails behind it are hidden for the same duration. */
body.video-on .player.glass {
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
body.video-on .yt-stage {
  position: fixed; z-index: 61;
  left: 0; right: 0; margin-inline: auto;
  bottom: calc(var(--player-h, 11rem) + 1.1rem);
  /* Sized from the room actually available, in both axes.
     23rem was a phone-first cap: right at 375px, but only 29% of a 1280px
     window with 433px of headroom going to waste above it.

     The height term is what keeps that honest. The cabinet's height is almost
     exactly 0.65x its width (measured 0.648 — the grilles and bar are fixed, so
     the ratio settles as it grows), so dividing the free vertical space by that
     gives the widest cabinet that still clears the player. Without it, growing
     the width would simply push the set off the top of a short window — which
     it already did at 522x394 before any of this.

     The 7rem reserve is the antenna plus breathing room. Budgeting only the gap
     grew the cabinet until it sat 12px from the top of a 1024x600 window, which
     put the ears off-screen entirely — they stick up above the casing, so they
     have to be paid for in the height budget or they are the first thing lost. */
  --tv-w: clamp(18rem,
    min(92vw, calc((100vh - var(--player-h, 11rem) - 7rem) * 1.45)),
    44rem);
  width: var(--tv-w);
  height: auto;
  display: flex; align-items: stretch;
  padding: 0.7rem 0.5rem 2rem;
  border-radius: 0.9rem 0.9rem 0.45rem 0.45rem;
  /* The wood was a repeating-linear-gradient at a perfect 1px every 4px, which
     is corduroy, not grain — real timber is irregular, and the eye knows the
     difference immediately even when it cannot say why.

     This is feTurbulence with the frequency pushed hard on X and almost flat on
     Y (0.42 vs 0.012), which is what produces long vertical streaks rather than
     clouds. Same technique already carrying the screen grain, so it costs one
     more static data URI and nothing at all after first paint — no canvas, no
     rAF, which matters on a set that stays open for hours on a cheap phone.

     A faint regular stripe stays underneath at a third of its old strength:
     veneer cabinets of that era really did have a machined regularity to them,
     and losing it entirely read as driftwood. */
  /* Reference is a walnut console: broad flowing cathedral figure, warm mid
     brown, not the fine vertical noise I put here first — at 0.42 the streaks
     were thin enough to read as dirt on a small cabinet rather than as timber.
     Dropped to 0.045/0.006 for wide soft figure, at a much larger tile so it
     never tiles visibly, and the base gradient warmed toward walnut. */
  background:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='560' height='300'><filter id='w'><feTurbulence type='fractalNoise' baseFrequency='0.045 0.006' numOctaves='4' seed='11'/><feColorMatrix type='saturate' values='0'/></filter><rect width='560' height='300' filter='url(%23w)' opacity='0.5'/></svg>"),
    linear-gradient(#8a5c34, #6b4426 46%, #4a2e18);
  background-size: 560px 300px, auto;
  background-blend-mode: soft-light, normal;
  border: 1px solid rgba(0, 0, 0, 0.6);
  box-shadow:
    inset 0 1px 0 rgba(255, 220, 170, 0.3),
    inset 0 -2px 6px rgba(0, 0, 0, 0.5),
    0 22px 55px -12px rgba(5, 2, 0, 0.78);
  overflow: visible;   /* the lip and legs sit outside the casing */
}
/* ── cinema mode ────────────────────────────────────────────────────────────
   The maximise Rohit asked for, and deliberately NOT native fullscreen.

   Three reasons it is a CSS mode rather than requestFullscreen. It works on
   iOS Safari, which does not expose fullscreen on arbitrary elements at all —
   the ⛶ button is simply hidden there today, so a third of the audience has
   never had this. It keeps everything in one stacking context, so the playlist
   still opens over the set; in real fullscreen only descendants of the
   fullscreen element render, and #panel is a sibling, which is why MENU in
   fullscreen is currently a button that shows nothing. And it shows the
   television: real fullscreen here strips the grille and the cabinet and gives
   a flat black rectangle, which is the opposite of what a big TV should be.

   YouTube ships both theatre and fullscreen for the same reasons. This is the
   theatre one, which is the one that always works.

   The size cap is the only real change. The normal formula wants 887px on a
   1440x900 window and gets clamped to 44rem, leaving 320px of headroom above
   the set doing nothing. Cinema raises the cap and spends the antenna reserve
   it no longer needs to be so careful with. */
body.video-on.tv-cinema .yt-stage {
  /* 72rem capped this at 58% of a 2000px window — it stopped growing exactly
     where there was most room to grow. The vw and vh terms are the real limits;
     the cap now only exists so the cabinet cannot outgrow a very tall window. */
  /* Cinema stops budgeting around the player entirely.

     That was the real bug, and raising the cap did nothing to it: width here is
     DERIVED from height, and the height budget subtracted --player-h, which is
     measured live and includes everything the sticky wrapper carries below the
     card. So cinema only ever gained the difference between the two reserves —
     measured 576px to 664px, 88px, on a screen with room for hundreds. The set
     "expanded" by an amount nobody could see.

     Now it is centred in the viewport and sized from the whole of it, less a
     fixed 12rem that keeps the cabinet's bottom bar clear of the player card
     floating below. The player keeps working — POWER on the set closes it
     rather than pausing, so removing the card would leave a phone with no way
     to pause at all. */
  /* Measured #tvAnt at y = -8.4 on an 812x375 landscape phone: the antenna ears
     were cut off the top while 35px sat unused below the cabinet. The ears are
     48px on their own and live ABOVE the casing, which is why normal mode
     budgets 7rem here and cinema should not have dropped to 3. */
  --tv-w: clamp(18rem, min(96vw, calc((100vh - 5.5rem) * 1.45)), 130rem);
  bottom: auto;
  top: 50%;
  transform: translateY(-50%);   /* nothing else is on screen — centre it */
}
/* Everything else goes. Cinema is now the set and nothing but the set, which is
   both what was asked for and what makes it big: with the player card gone
   there is no vertical budget to work around, so the cabinet takes the whole
   window instead of the leftovers. */
/* NOT .player-wrap > * — .yt-stage is a GRANDCHILD (.player-wrap > .player >
   .yt-stage), so that selector matched .player itself and faded the television
   with everything inside it. Opacity on a parent applies to every descendant
   and a child cannot undo it, which is exactly why the set went dark. Target
   the player's own children instead, and keep the card's glass out of the way. */
body.video-on.tv-cinema .player > *:not(.yt-stage),
body.video-on.tv-cinema .top,
body.video-on.tv-cinema .hero,
body.video-on.tv-cinema .scene,
body.video-on.tv-cinema .footer,
body.video-on.tv-cinema .day-banner,
body.video-on.tv-cinema .wa-card,
body.video-on.tv-cinema .reactions,
body.video-on.tv-cinema .kbd-hints,
body.video-on.tv-cinema .dock,
body.video-on.tv-cinema .chips { opacity: 0; pointer-events: none; }
body.video-on.tv-cinema .player {
  background: none; border: none; box-shadow: none;
  backdrop-filter: none; -webkit-backdrop-filter: none;
}

/* THE reason the set stayed dark through three attempted fixes.

   .yt-stage carries z-index 61 and the scrim carries 60, so the set should sit
   in front. It does not, because the stage lives inside <main class="ui">,
   which has z-index 2 — and a positioned element with a z-index creates a
   STACKING CONTEXT. Every z-index inside main is resolved against its siblings
   within main, not against the page, so the whole of main (television included)
   composites at 2, underneath a scrim at 60. The 61 was never competing with
   the 60 at all.

   Raising main above the scrim in cinema is what actually puts the picture in
   front. Everything else inside main is already opacity 0, so nothing else
   comes with it.

   Why this survived so long: elementFromPoint at the centre of the tube kept
   returning the IFRAME, which reads as "nothing is covering it" — but the scrim
   is pointer-events:none, so hit-testing walks straight through it and can
   never see it. It was the wrong instrument for the question. */
/* ── every control gets a 44px hit area ─────────────────────────────────────
   Audited on a 375px phone and not one of the nine reached 44x44: the CH and
   VOL knobs were 15.7px boxes with roughly 25px of effective slop, MENU
   30.6x16.3, B/W 31.8x16.5 with none at all, and only 10.4px of gap between
   CH− and CH+. This site is built for older people on cheap Android phones and
   it was asking them to hit a 16px target.

   The pad is invisible and centred, so the set's proportions do not change —
   the knobs still look like knobs on a small cabinet. z-index:-1 keeps each pad
   behind its own label rather than over its neighbour, and the row knobs get a
   smaller pad so two adjacent ones meet instead of overlapping, which would
   make the outer half of each a coin toss. */
/* ⏸ only exists because cinema hides the player card. In normal mode the card
   is right there below the set with a much larger play button, so a second one
   on the cabinet is clutter on a bar that already carries B/W, ▭, ⛶ and ✕ —
   over a set that already has POWER, CH, VOL and MENU on its own panel. It was
   added unconditionally, which was wrong. */
/* Specificity, not order: .video-close sets display:flex further down the file
   at equal specificity, so a bare .tv-play rule loses to it and the button
   stayed visible. Qualified so it wins. */
body.video-on .tv-play { display: none; }
body.video-on.tv-cinema .tv-play { display: flex; }

/* NOT #tvAnt. It is already position:absolute at left:50%/bottom:100% with a
   -42px margin to centre it above the casing, and an ID selector (1,0,0) beats
   body.video-on .tv-ant (0,2,1) — so `position: relative` here overrode its
   absolute positioning and threw the antenna off the top of the set. It is
   84x48 anyway, comfortably past 44px, so it never needed a pad. The rest are
   static and safe. */
.tv-knob, #tvMenu, #tvPower, .video-close, .tv-bw-btn { position: relative; }
.tv-knob::after, #tvMenu::after, #tvPower::after,
.video-close::after, .tv-bw-btn::after {
  content: ""; position: absolute; z-index: -1;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: max(100%, 44px); height: max(100%, 44px);
}
.tv-row .tv-knob::after { width: max(100%, 38px); }

/* ── हनुमान अंश: the artwork, composed rather than redrawn ──────────────────
   Illustration belongs in an asset; CSS only frames it. The composition is NOT
   the same crop at every size — a wide illustration centre-cropped on a phone
   loses either the गदा or the face, which are the two things that identify him.
   So each tier keeps a different part of the drawing.

   Hidden unless the channel is playing, which is also the lazy-load: a
   background-image on a display:none element is never fetched, so nobody else
   pays for it. */
/* The crawlable track list on a route page. Real text, because a song title is
   exactly what someone types into a search box, and app.js is an external file
   a crawler does not read as page content. */
.route-tracks { margin: 1.1rem auto 0; max-width: 34rem; padding: 0 1rem; text-align: left; }
.route-tracks h3 {
  margin: 0 0 0.5rem; font-size: 0.78rem; font-weight: 600; letter-spacing: 0.04em;
  color: var(--gold, #ffd98f); opacity: 0.85;
}
.route-tracks ol { margin: 0; padding-left: 1.2rem; display: grid; gap: 0.3rem; }
.route-tracks li { font-size: 0.82rem; line-height: 1.4; opacity: 0.9; }
.route-tracks b { font-weight: 600; }
.route-tracks span { display: block; font-size: 0.74rem; opacity: 0.62; }

/* ── the UI has to survive the artwork ──────────────────────────────────────
   The scene normally tells the UI how to paint itself: a light morning or day
   sky gets dark text (#3d2412), which is right when the sky behind it is pale.
   The हनुमान अंश artwork is dark at every hour, so at 10am the clock, the date
   line and the listener count were dark brown on a dark picture — reported
   invisible, and they were. The chips washed out for the same reason.

   Fixed by treating this channel as permanent night for the UI only, never by
   fading the artwork. */
body.chan-hanumanansh[data-phase="morning"] .clock-wrap,
body.chan-hanumanansh[data-phase="day"] .clock-wrap,
body.chan-hanumanansh[data-phase="morning"] .date-line,
body.chan-hanumanansh[data-phase="day"] .date-line,
body.chan-hanumanansh[data-phase="morning"] .online,
body.chan-hanumanansh[data-phase="day"] .online,
body.chan-hanumanansh[data-phase="morning"] .quote,
body.chan-hanumanansh[data-phase="day"] .quote,
body.chan-hanumanansh[data-phase="morning"] .tagline,
body.chan-hanumanansh[data-phase="day"] .tagline,
body.chan-hanumanansh[data-phase="morning"] .contact,
body.chan-hanumanansh[data-phase="day"] .contact,
body.chan-hanumanansh[data-phase="morning"] .kbd-hints,
body.chan-hanumanansh[data-phase="day"] .kbd-hints {
  color: var(--cream);
  text-shadow: 0 1px 3px rgba(8, 3, 2, 0.95), 0 2px 16px rgba(8, 3, 2, 0.8);
}
/* the date line and clock sit on the busiest part of the picture, so they get
   their own ground rather than relying on a shadow alone */
body.chan-hanumanansh .clock-wrap,
body.chan-hanumanansh .date-line {
  background: linear-gradient(90deg, rgba(10, 4, 3, 0.72), rgba(10, 4, 3, 0.34) 78%, rgba(10, 4, 3, 0));
  border-radius: 0.5rem;
  padding-inline: 0.45rem;
}
/* chips are translucent by design; over line art they need more ground */
body.chan-hanumanansh .chip,
body.chan-hanumanansh .dock__b {
  background: rgba(14, 7, 4, 0.88);
  border-color: rgba(255, 217, 143, 0.42);
}

.hanuman-scene { display: none; }
body.chan-hanumanansh .hanuman-scene {
  display: block;
  position: absolute; inset: 0; pointer-events: none; z-index: 3;
  /* a controlled gradient over the art, rather than fading बजरंगबली himself:
     the UI needs contrast at top and bottom, the figure does not need dimming */
  background:
    linear-gradient(180deg, rgba(15, 5, 13, 0.55) 0%, rgba(15, 5, 13, 0.06) 26%,
                    rgba(15, 5, 13, 0.1) 58%, rgba(15, 5, 13, 0.72) 100%),
    url("/assets/scene/bajrangbali.webp");
  background-repeat: no-repeat;
  /* cover, never 100% 100%: stretching this artwork is worse than cropping it */
  background-size: cover, cover;
  background-position: center center, center center;
}

/* Tablet — from the गदा through most of the tail. */
@media (max-width: 1024px) {
  body.chan-hanumanansh .hanuman-scene {
    background-size: cover, cover;
    background-position: center center, 48% center;
  }
}

/* Phone — crop INTO him. The window is only about a quarter of the artwork's
   width here, so the x position is the whole design: measured on the real file
   (1672×941), 34% loses the face and 52% loses the गदा. 44% is the one value
   that keeps मुकुट, face, कुण्डल, माला, fist and the mace together, which is
   what the brief asked for. Shrinking the desktop composition instead would
   make him thumbnail-sized on the screen most visitors actually hold. */
@media (max-width: 768px) {
  body.chan-hanumanansh .hanuman-scene {
    background-size: cover, cover;
    background-position: center center, 44% 40%;
  }
}

/* Very narrow — cover tightens the crop on its own as the viewport narrows, so
   this only lifts the framing slightly toward the मुकुट. Verified at 320px:
   crown, face, कुण्डल, माला, fist and mace all still present. */
@media (max-width: 430px) {
  body.chan-hanumanansh .hanuman-scene {
    background-size: cover, cover;
    background-position: center center, 44% 38%;
  }
}

body.video-on.tv-cinema main.ui { z-index: 62; }

/* YouTube's theater button changes its icon to show which way it goes, and ours
   did not — the same ▭ in both states, so nothing told you that pressing it
   again returns you. One SVG with an inner bar that drops out in cinema does it.
   Not a second text glyph: those tofu on the cheap Android phones this is for. */
/* ▶ ⏸ and ⛶ were text glyphs, so every platform drew its own: colour emoji on
   one phone, a thin outline on another, and a different optical size on each.
   Rohit spotted it on the cinema bar. Drawn now, like the cinema icon, so the
   set's controls match each other on every device. ✕ stays text on purpose —
   it is a letterform, not an emoji, and renders the same everywhere. */
#tvPlay .i-pp { width: 1.05em; height: 1.05em; display: block; fill: currentColor; }
#tvPlay .i-pp__pause { display: none; }
#tvPlay.is-playing .i-pp__play { display: none; }
#tvPlay.is-playing .i-pp__pause { display: block; }
#videoFull .i-full {
  width: 1.05em; height: 1.05em; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.9;
  stroke-linecap: round; stroke-linejoin: round;
}

}

#videoCinema .i-cine { width: 1.15em; height: 1.15em; display: block; }
#videoCinema .i-cine__out { fill: none; stroke: currentColor; stroke-width: 1.6; }
#videoCinema .i-cine__in { fill: currentColor; }
body.tv-cinema #videoCinema .i-cine__in { display: none; }

/* And the page must stop scrolling underneath. The hidden elements are hidden
   with opacity, which leaves them in layout — measured scrollHeight 3261
   against a 700px window — so the whole site was still scrolling past behind a
   fixed television. */
/* The lock is driven from JS by a class on <html>, not by :has() on the body.
   :has() was tried and did not hold — measured scrollTop reaching 656px on a
   700px window with overflow:hidden supposedly applied — because the real
   scrolling element here is documentElement, and that is the element that has
   to carry the rule. */
html.tv-cinema-lock { overflow: hidden; overscroll-behavior: none; }
/* position:fixed, not overflow:hidden alone. overflow:hidden stops a user
   scrolling but not a programmatic one — measured scrollTop still reaching
   656px with it applied — and iOS Safari has never honoured it for touch
   scrolling at all, which matters here because most of this audience is on a
   phone. Pinning the body is the one technique that holds everywhere; the
   scroll position is carried in --lock-y and restored on the way out. */
html.tv-cinema-lock body {
  position: fixed;
  top: calc(var(--lock-y, 0px) * -1);
  left: 0; right: 0; width: 100%;
  overflow: hidden;
  overscroll-behavior: none;
  touch-action: none;
}
/* The room dims — it does not black out. 0.88 with a blur erased the scene
   entirely, and at night, over an already dark ghat, the whole page just read
   as black. A theatre lowers the lights; it does not paint the walls. */
body.video-on.tv-cinema::after {
  content: ""; position: fixed; inset: 0; z-index: 60;
  background: rgba(8, 4, 1, 0.45);
  animation: cinemaIn 0.32s ease both;
  pointer-events: none;
}
/* and the set gets a spill of light around it, which is what actually sells a
   lit screen in a dark room */
body.video-on.tv-cinema .yt-stage {
  box-shadow:
    inset 0 1px 0 rgba(255, 220, 170, 0.3),
    inset 0 -2px 6px rgba(0, 0, 0, 0.5),
    0 0 120px -20px rgba(255, 190, 110, 0.34),
    0 26px 70px -14px rgba(5, 2, 0, 0.85);
}
@keyframes cinemaIn { from { opacity: 0 } to { opacity: 1 } }
/* the player keeps working — it is the control surface, and a television with
   no way to pause is a worse television */
body.video-on.tv-cinema .player-wrap { z-index: 61; }
body.video-on.tv-cinema .video-bar__t::after {
  content: " · सिनेमा"; color: var(--gold, #ffd98f); opacity: 0.75;
}
@media (prefers-reduced-motion: reduce) {
  body.video-on.tv-cinema::after { animation: none; }
}

/* The top plank. In the reference it is a separate board overhanging the
   carcass on every side, with its front edge catching the light — that overhang
   and its lit edge are most of what says "furniture" rather than "box". */
body.video-on .yt-stage::before {
  content: ""; position: absolute; left: -0.35rem; right: -0.35rem; top: -0.42rem;
  height: 0.5rem; border-radius: 0.4rem 0.4rem 0 0; pointer-events: none;
  background: linear-gradient(#8a5c34, #5d3a1e);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.45);
}
/* two legs — the silhouette that says "console" rather than "monitor" */
body.video-on .yt-stage::after {
  content: ""; position: absolute; left: 14%; right: 14%; top: 100%;
  height: 0.45rem; pointer-events: none;
  background:
    linear-gradient(#3a2211, #24140a) left top / 0.45rem 100% no-repeat,
    linear-gradient(#3a2211, #24140a) right top / 0.45rem 100% no-repeat;
}

/* the picture tube. `display: contents` while closed so the 46px cover disc is
   unaffected — the iframe then resolves against .yt-stage exactly as before. */
.tv-screen { display: contents; }
body.video-on .tv-screen {
  display: block; position: relative; flex: 1 1 auto;
  aspect-ratio: 4 / 3; overflow: hidden;
  border-radius: 0.5rem; background: #070604;
  box-shadow:
    inset 0 0 0 2px rgba(0, 0, 0, 0.8), inset 0 0 24px rgba(0, 0, 0, 0.6),
    0 0 0 3px rgba(255, 225, 180, 0.09);
}
body.video-on .yt-stage iframe {
  position: absolute; left: 0; top: 0; right: auto; bottom: auto;
  width: 100% !important; height: 100% !important;
  transform: none !important;               /* the closed disc crops a circle */
  border-radius: 0.5rem; pointer-events: auto;
}

/* speaker grille left, knob plate right */
.tv-grille { display: none; }
body.video-on .tv-grille {
  display: flex; flex: 0 0 1.9rem; align-self: stretch;
  flex-direction: column; align-items: center; justify-content: center;
  gap: 0.45rem; margin: 0 0.34rem; border-radius: 0.22rem;
  /* Fluted chrome, not a dark stripe. In the reference these outer columns are
     rounded metal rods, and the specular line running down each one is what
     makes the whole cabinet read as furniture rather than a painted box — dark
     walnut is only expensive next to bright metal. Each 7px cell is one rod:
     shadow, rising tone, a hot highlight off-centre, then falling away. */
  background: repeating-linear-gradient(90deg,
    #2b2521 0px, #6f665c 2px, #d8d0c4 3px, #f6f1e8 3.5px,
    #b3aa9e 4.5px, #5d554c 6px, #2b2521 7px);
  box-shadow:
    inset 0 0 0 1px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.25),
    inset 0 -1px 0 rgba(0, 0, 0, 0.4);
}
body.video-on .tv-grille--r {
  background: linear-gradient(#d3bd97, #a98a5f 60%, #8d7048);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.35);
}
.tv-knob { display: none; }
body.video-on .tv-knob {
  display: block; width: 12px; height: 12px; border-radius: 50%; flex: none;
  background: radial-gradient(circle at 35% 30%, #6b5334, #2e2013 70%, #150e07);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.6), inset 0 -1px 1px rgba(0, 0, 0, 0.5);
}
body.video-on .tv-knob--big {
  width: 17px; height: 17px;
  background: radial-gradient(circle at 35% 30%, #f0d49a, #8a5a24 70%, #4a2e14);
}
/* the knobs are real controls: the big one is power, the two below change the
   channel. On these sets that is exactly what the right-hand plate was for. */
body.video-on button.tv-knob {
  padding: 0; border: 0; cursor: pointer; appearance: none;
}
body.video-on button.tv-knob:hover,
body.video-on button.tv-knob:focus-visible {
  outline: none; box-shadow: 0 0 0 2px rgba(255, 214, 140, 0.85), 0 1px 3px rgba(0, 0, 0, 0.6);
}

/* ── making the knobs actually pressable ────────────────────────────────────
   They were 12px across. That is a quarter of a thumb, and the only feedback
   was :active, which on a touchscreen is gone before the finger lifts — so a
   press that registered looked exactly like a press that missed.

   Three things: a hit area larger than the knob, a pressed state that outlives
   the touch, and on Android a short buzz. */
body.video-on button.tv-knob { position: relative; }
/* the tappable area, invisible and bigger than the knob it belongs to. The
   gaps below are set so two of these never overlap — an expanded target that
   overlaps its neighbour presses the wrong control, which is worse than small. */
body.video-on button.tv-knob::after {
  content: ""; position: absolute; inset: -11px -7px;
}
/* The two knobs in a row face each other, so an even expansion wastes itself
   against the neighbour. Each grows outward toward the plate edge instead, and
   only 3px inward — well inside the 10px gap, so they still never overlap. */
body.video-on .tv-chrow .tv-knob:first-child::after { inset: -11px -3px -11px -7px; }
body.video-on .tv-chrow .tv-knob:last-child::after { inset: -11px -7px -11px -3px; }
body.video-on .tv-menu-btn { position: relative; }
body.video-on .tv-menu-btn::after { content: ""; position: absolute; inset: -8px -4px; }
body.video-on .tv-bw-btn { position: relative; }
body.video-on .tv-bw-btn::after { content: ""; position: absolute; inset: -9px -6px; }

/* held for 220ms by JS, because :active does not survive the finger lifting */
body.video-on button.tv-knob:active,
body.video-on .tv-knob.knob-hit {
  transform: rotate(20deg) scale(0.88);
  box-shadow: 0 0 0 3px rgba(255, 214, 140, 0.95), 0 0 14px rgba(255, 198, 110, 0.85);
}
body.video-on .tv-menu-btn.knob-hit,
body.video-on .tv-bw-btn.knob-hit {
  color: #2b1a0c; background: #f0dcb4;
  box-shadow: 0 0 0 3px rgba(255, 214, 140, 0.95);
}

/* a phone gets bigger everything — this is the audience, not the exception */
/* A phone gets a bigger *target*, not a bigger knob. The two are separate
   things: the knob is drawn small because a 1970s knob is small and the plate
   has to stay slim enough to leave the picture its width, while the ::after
   above carries the thumb. Growing the visible knob to match the target made
   the plate bulky and pushed the controls toward the edge of the cabinet. */
@media (pointer: coarse) {
  body.video-on .tv-grille--r { flex-basis: 2.9rem; gap: 0.26rem; }
  body.video-on .tv-knob { width: 15px; height: 15px; }
  body.video-on .tv-knob--big { width: 20px; height: 20px; }
  body.video-on .tv-chrow { gap: 0.5rem; }
  body.video-on .tv-lab { font-size: 0.4rem; }
  body.video-on .tv-menu-btn { font-size: 0.38rem; padding: 0.13rem 0.3rem; }
  body.video-on .tv-bw-btn { font-size: 0.5rem; height: 16px; padding: 0 0.4rem; }
  body.video-on .tv-chrow .tv-knob { font-size: 0.4rem; }
}

/* the plate is labelled, the way the real ones were, and carries the standby
   lamp — red on these sets, and lit whenever there is power */
body.video-on .tv-grille--r { flex-basis: 2.5rem; gap: 0.22rem; padding: 0.3rem 0; }
.tv-led { display: none; }
body.video-on .tv-led {
  display: block; width: 5px; height: 5px; border-radius: 50%; flex: none;
  /* green while the set is running, red the moment POWER is pressed — the lamp
     answers the press before the picture has finished collapsing */
  background: #45f24f;
  box-shadow: 0 0 5px 1px rgba(70, 245, 80, 0.9), inset 0 0 2px rgba(200, 255, 200, 0.9);
  animation: tvLed 3.4s ease-in-out infinite;
}
/* The lamp dies down, it does not vanish. Measured before this: green, red at
   80ms, then gone at 600ms the instant video-on dropped — the set disappeared
   with its power light still lit, which is the one thing a switched-off
   television never does. It now falls red → ember → dark across the 430ms
   before the doors finish, so the light is already out when the cabinet goes.
   forwards, so it holds dark rather than snapping back on the last frame. */
body.video-on.tv-off .tv-led {
  animation: ledDown 430ms ease-out forwards;
}
@keyframes ledDown {
  0% {
    background: #ff3b2f;
    box-shadow: 0 0 6px 1px rgba(255, 70, 50, 0.95), inset 0 0 2px rgba(255, 190, 180, 0.9);
  }
  55% {
    background: #8e1b12;
    box-shadow: 0 0 3px 0 rgba(255, 70, 50, 0.5), inset 0 0 2px rgba(255, 150, 140, 0.4);
  }
  100% {
    background: #2a1008;
    box-shadow: 0 0 0 0 rgba(255, 70, 50, 0), inset 0 0 2px rgba(0, 0, 0, 0.6);
  }
}
@media (prefers-reduced-motion: reduce) {
  body.video-on.tv-off .tv-led { animation: none; background: #2a1008; box-shadow: none; }
}
@keyframes tvLed { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }
.tv-lab { display: none; }
body.video-on .tv-lab {
  display: block; flex: none;
  font-size: 0.38rem; font-weight: 700; letter-spacing: 0.06em;
  color: #3a2810; text-transform: uppercase; line-height: 1;
}
.tv-menu-btn { display: none; }
body.video-on .tv-menu-btn {
  display: block; flex: none; margin-top: 0.15rem;
  padding: 0.1rem 0.28rem; border-radius: 0.16rem; cursor: pointer;
  font-size: 0.32rem; font-weight: 700; letter-spacing: 0.06em;
  color: #f6ecd8; background: #3f2a14;
  border: 1px solid rgba(0, 0, 0, 0.45);
  box-shadow: inset 0 1px 0 rgba(255, 220, 170, 0.25);
}
body.video-on .tv-menu-btn:hover, body.video-on .tv-menu-btn:focus-visible {
  outline: none; color: #ffd98f; box-shadow: 0 0 0 2px rgba(255, 214, 140, 0.8);
}
.tv-chrow { display: none; }
body.video-on .tv-chrow { display: flex; gap: 0.28rem; flex: none; }
body.video-on .tv-chrow .tv-knob {
  display: grid; place-items: center;
  font-size: 0.34rem; color: #f0dcb4; line-height: 1;
}
@media (prefers-reduced-motion: reduce) { .tv-led { animation: none !important; } }

/* ── everything scales with the cabinet ────────────────────────────────────
   A bigger television is not a small one with thinner fittings. The grille
   slats, the knob plate, the knobs and the lettering are all a fixed fraction
   of --tv-w, so the set keeps its proportions from a 288px phone to a 704px
   desktop — the way it would if it were a real object photographed closer.

   Each carries a max() floor at its phone size: a fraction alone would shrink
   the knobs below a thumb on a small screen, and the whole point of the last
   two versions was that they be pressable. Percentages of the cabinet, not of
   the viewport, so a wide-but-short window (where the height term keeps the
   cabinet small) does not get oversized fittings. */
body.video-on .yt-stage {
  padding:
    max(0.7rem, calc(var(--tv-w) * 0.032))
    max(0.5rem, calc(var(--tv-w) * 0.023))
    max(2rem,   calc(var(--tv-w) * 0.093));
}
body.video-on .tv-grille {
  flex: 0 0 max(30px, calc(var(--tv-w) * 0.087));
  margin: 0 max(0.34rem, calc(var(--tv-w) * 0.016));
}
body.video-on .tv-grille--r {
  flex-basis: max(40px, calc(var(--tv-w) * 0.116));
  gap: max(0.26rem, calc(var(--tv-w) * 0.012));
}
body.video-on .tv-knob { width: max(15px, calc(var(--tv-w) * 0.0435)); height: max(15px, calc(var(--tv-w) * 0.0435)); }
body.video-on .tv-knob--big { width: max(20px, calc(var(--tv-w) * 0.058)); height: max(20px, calc(var(--tv-w) * 0.058)); }
body.video-on .tv-led { width: max(5px, calc(var(--tv-w) * 0.0145)); height: max(5px, calc(var(--tv-w) * 0.0145)); }
body.video-on .tv-lab { font-size: max(0.4rem, calc(var(--tv-w) * 0.0186)); }
body.video-on .tv-chrow { gap: max(0.5rem, calc(var(--tv-w) * 0.029)); }
body.video-on .tv-chrow .tv-knob { font-size: max(0.4rem, calc(var(--tv-w) * 0.0186)); }
body.video-on .tv-menu-btn {
  font-size: max(0.38rem, calc(var(--tv-w) * 0.0176));
  padding: max(0.13rem, calc(var(--tv-w) * 0.006)) max(0.3rem, calc(var(--tv-w) * 0.014));
}
body.video-on .tv-bw-btn {
  font-size: max(0.5rem, calc(var(--tv-w) * 0.0232));
  height: max(16px, calc(var(--tv-w) * 0.046));
  padding: 0 max(0.4rem, calc(var(--tv-w) * 0.019));
}
body.video-on .video-bar {
  height: max(2rem, calc(var(--tv-w) * 0.093));
  padding: 0 max(0.7rem, calc(var(--tv-w) * 0.02));
  gap: max(0.4rem, calc(var(--tv-w) * 0.017));
}
body.video-on .video-bar__t { font-size: max(0.6rem, calc(var(--tv-w) * 0.0278)); }
/* These scaled with the cabinet, which was right up to a point and absurd past
   it: 0.0754 of the set is 53px at 704px wide and 105px in cinema at 1389px —
   a control the size of a fist. Clamped, so they still grow with a small set on
   a phone and stop once they are comfortably tappable. */
body.video-on .video-close {
  width: clamp(26px, calc(var(--tv-w) * 0.0754), 44px);
  height: clamp(26px, calc(var(--tv-w) * 0.0754), 44px);
  min-width: clamp(26px, calc(var(--tv-w) * 0.0754), 44px);
  font-size: clamp(0.75rem, calc(var(--tv-w) * 0.032), 1.05rem);
}
body.video-on .tv-boot__om { font-size: max(2rem, calc(var(--tv-w) * 0.093)); }
body.video-on .tv-boot__n { font-size: max(0.6rem, calc(var(--tv-w) * 0.0174)); }
body.video-on .tv-osd, body.video-on .tv-vol { padding: max(0.26rem, calc(var(--tv-w) * 0.011)) max(0.45rem, calc(var(--tv-w) * 0.018)); }
body.video-on .tv-osd__ch, body.video-on .tv-osd__n,
body.video-on .tv-vol b, body.video-on .tv-vol em { font-size: max(0.52rem, calc(var(--tv-w) * 0.019)); }
body.video-on .tv-vol__bar { height: max(7px, calc(var(--tv-w) * 0.02)); }
body.video-on.tv-teach .tv-coach,
body.video-on.tv-fuzzy .tv-fuzz-hint { font-size: max(0.55rem, calc(var(--tv-w) * 0.0205)); }
body.video-on .tv-ant {
  width: max(84px, calc(var(--tv-w) * 0.243));
  height: max(48px, calc(var(--tv-w) * 0.139));
  margin-left: calc(max(84px, calc(var(--tv-w) * 0.243)) / -2);
}
body.video-on .tv-ant__r {
  height: max(46px, calc(var(--tv-w) * 0.133));
  width: max(3px, calc(var(--tv-w) * 0.0058));
}
body.video-on .tv-ant__base {
  width: max(16px, calc(var(--tv-w) * 0.046));
  height: max(8px, calc(var(--tv-w) * 0.023));
  margin-left: calc(max(16px, calc(var(--tv-w) * 0.046)) / -2);
}

/* ── the antenna ────────────────────────────────────────────────────────────
   Reception drifts on its own, and worse while it is raining — बारिश is a real
   state on this site, so the set answers to the weather the way it used to.
   A tap on the ears straightens it out. Tapping, not dragging: dragging the
   panel was tried and abandoned because it is fiddly with a thumb. */
.tv-ant { display: none; }
body.video-on .tv-ant {
  display: block; position: absolute; left: 50%; bottom: 100%;
  width: 84px; height: 48px; margin-left: -42px; margin-bottom: -0.2rem;
  background: none; border: 0; padding: 0; cursor: pointer; z-index: 2;
}
/* The rods are 3px of pale gold and they sit directly over the bright cream
   "मन्दिर रेडियो" wordmark — measured overlapping `.hero`. Pale on pale, they
   were invisible: DataFast recorded the antenna being nudged exactly zero times
   in the first day while every other control on the set was used. A dark edge
   drawn around the rods' own shape, not their box, makes them read against the
   title without changing a single dimension. Static, so it costs one raster. */
body.video-on .tv-ant {
  filter: drop-shadow(0 0 1.4px rgba(22, 11, 4, 0.95)) drop-shadow(0 1px 3px rgba(0, 0, 0, 0.55));
}
body.video-on .tv-ant:focus-visible { outline: 2px solid #ffd98f; outline-offset: 4px; border-radius: 6px; }
.tv-ant__r {
  position: absolute; bottom: 0; left: 50%; width: 3px; height: 46px;
  margin-left: -1.5px; border-radius: 2px; transform-origin: 50% 100%;
  background: linear-gradient(#f0dcb4, #9a7c4a);
  box-shadow: 0 0 0 0.5px rgba(32, 17, 6, 0.6), 0 0 4px rgba(0, 0, 0, 0.5);
  /* the tilt lives in transform; the wobble uses the independent `rotate`
     property so the two compose — an animation on transform would replace it */
  transition: transform 0.35s cubic-bezier(0.34, 1.4, 0.5, 1);
}
.tv-ant__r--l { transform: rotate(calc(-30deg + var(--ant, 0deg))); }
.tv-ant__r--r { transform: rotate(calc(30deg + var(--ant, 0deg))); }
.tv-ant__base {
  position: absolute; bottom: -2px; left: 50%; margin-left: -8px;
  width: 16px; height: 8px; border-radius: 4px 4px 0 0;
  background: linear-gradient(#6b4426, #3a2211);
}
body.video-on.tv-fuzzy .tv-ant__r { animation: antWobble 0.45s ease-in-out infinite alternate; }
@keyframes antWobble { from { rotate: -3.5deg; } to { rotate: 3.5deg; } }

/* the picture while the signal is off: grain, but nothing like the full
   between-stations burst — you can still see what is on */
body.video-on.tv-fuzzy .tv-static {
  opacity: 0.42; animation: tvSnow 0.42s steps(3) infinite;
}
body.video-on.tv-fuzzy .tv-static::after {
  content: ""; position: absolute; left: 0; right: 0; height: 30%;
  background: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0));
  animation: tvRoll 1.6s linear infinite;
}
/* ── the first time the set is opened ──────────────────────────────────────
   Knobs on a wooden panel do not announce themselves, and this audience will
   not go hunting. On the first open the plate rings and a line names what the
   buttons do; both stop for good at the first press. */
.tv-coach { display: none; }
body.video-on.tv-teach .tv-coach {
  display: flex; position: absolute; left: 0.4rem; right: 0.4rem; bottom: 0.4rem;
  z-index: 6; align-items: center; justify-content: center; gap: 0.3rem;
  padding: 0.3rem 0.45rem; border-radius: 0.25rem; pointer-events: none;
  font-size: 0.55rem; line-height: 1.35; text-align: center; color: #ffe9c4;
  background: rgba(16, 10, 4, 0.9); border: 1px solid rgba(255, 200, 90, 0.5);
}
/* The keyboard half of the coach. There are only 18px between the cabinet and
   the player, so a standing hint row has nowhere to live down there — that gap
   is why the old .kbd-hints row rendered clipped behind the wood. This rides
   inside the banner the set already shows instead.

   Hidden unless the device actually has a keyboard. Most of this audience is on
   a phone, where "PgUp" is not a hint, it is noise. */
/* one tip at a time, cycled from app.js */
.tv-coach__t { display: none; font-style: normal; }
.tv-coach__t.is-on { display: block; }
body.video-on.tv-teach .tv-coach { flex-direction: column; }

/* Each tip lights the control it names. A ring on the object itself, never an
   arrow over the picture — this set predates on-screen displays. */
body.video-on.tv-teach.tv-tip-ant .tv-ant,
body.video-on.tv-teach.tv-tip-bar #tvBw,
body.video-on.tv-teach.tv-tip-bar #videoCinema { animation: tipGlow 1.1s ease-in-out infinite alternate; }
@keyframes tipGlow {
  from { filter: drop-shadow(0 0 1.4px rgba(22, 11, 4, 0.95)) drop-shadow(0 0 2px rgba(255, 205, 120, 0.5)); }
  to   { filter: drop-shadow(0 0 1.4px rgba(22, 11, 4, 0.95)) drop-shadow(0 0 9px rgba(255, 205, 120, 0.95)); }
}

.tv-coach__k { display: none; }

.panel-tv {
  display: flex; flex-direction: column; gap: 0.3rem;
  margin: 0.15rem 0.15rem 0.35rem; padding: 0.55rem 0.6rem;
  border-top: 1px solid rgba(246, 234, 216, 0.14);
  font-size: 0.66rem; line-height: 1.45; opacity: 0.85;
}
.panel-tv b { font-size: 0.6rem; letter-spacing: 0.08em; opacity: 0.7; font-weight: 600; }
.panel-tv span { display: flex; gap: 0.4rem; align-items: baseline; }
.panel-tv i svg { width: 0.95em; height: 0.95em; display: block; }
.panel-tv i {
  font-style: normal; flex: none; min-width: 2.6rem; text-align: center;
  padding: 0.12rem 0.3rem; border-radius: 0.26rem; font-size: 0.58rem;
  background: rgba(246, 234, 216, 0.1); border: 1px solid rgba(246, 234, 216, 0.22);
}

/* The shortcut list that stays put. Same reasoning as the coach line: hidden
   unless the device has a keyboard, because most of this audience is on a phone
   and a list of keys they cannot press is just clutter at the end of the list. */
.panel-keys { display: none; }
@media (hover: hover) and (pointer: fine) {
  .panel-keys {
    display: flex; flex-wrap: wrap; gap: 0.3rem 0.75rem;
    margin: 0.15rem 0.15rem 0.35rem; padding: 0.55rem 0.6rem;
    font-size: 0.58rem; letter-spacing: 0.05em; opacity: 0.6;
  }
  .panel-keys:hover { opacity: 0.95; }
  .panel-keys span { display: inline-flex; align-items: center; gap: 0.28rem; white-space: nowrap; }
  .panel-keys em { flex-basis: 100%; font-style: normal; opacity: 0.75; }
  .panel-keys kbd {
    font: inherit; font-size: 0.56rem; line-height: 1;
    padding: 0.22rem 0.34rem; border-radius: 0.26rem;
    background: rgba(246, 234, 216, 0.1);
    border: 1px solid rgba(246, 234, 216, 0.22);
  }
}
@media (hover: hover) and (pointer: fine) {
  body.video-on.tv-teach .tv-coach { flex-direction: column; gap: 0.15rem; }
  .tv-coach__k {
    display: block; font-weight: 600; letter-spacing: 0.02em;
    color: #ffcf87; opacity: 0.9; font-size: 0.92em;
  }
}

body.video-on.tv-teach .tv-grille--r {
  animation: plateRing 1.5s ease-in-out infinite;
}
@keyframes plateRing {
  0%, 100% { box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.45), 0 0 0 0 rgba(255, 205, 120, 0); }
  50%      { box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.45), 0 0 0 4px rgba(255, 205, 120, 0.5); }
}
@media (prefers-reduced-motion: reduce) {
  body.video-on.tv-teach .tv-grille--r { animation: none; }
}

.tv-fuzz-hint { display: none; }
body.video-on.tv-fuzzy .tv-fuzz-hint {
  display: block; position: absolute; left: 50%; bottom: 0.5rem;
  transform: translateX(-50%); z-index: 6; pointer-events: none;
  padding: 0.2rem 0.5rem; border-radius: 999px; white-space: nowrap;
  font-size: 0.52rem; color: #ffe6b8;
  background: rgba(16, 10, 4, 0.85); border: 1px solid rgba(255, 200, 90, 0.45);
  animation: fuzzNudge 1.5s ease-in-out infinite;
}
@keyframes fuzzNudge { 0%, 100% { opacity: 0.65; } 50% { opacity: 1; } }

/* the moment it locks on */
body.video-on.tv-lock .tv-screen::after {
  content: ""; position: absolute; inset: 0; z-index: 5; pointer-events: none;
  background: #eafcff; animation: tvLock 0.4s ease-out forwards;
}
@keyframes tvLock { from { opacity: 0.22; } to { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  body.video-on.tv-teach.tv-tip-ant .tv-ant,
  body.video-on.tv-teach.tv-tip-bar #tvBw,
  body.video-on.tv-teach.tv-tip-bar #videoCinema,
  body.video-on.tv-fuzzy .tv-ant__r,
  body.video-on.tv-fuzzy .tv-static,
  body.video-on.tv-fuzzy .tv-static::after,
  body.video-on.tv-fuzzy .tv-fuzz-hint,
  body.video-on.tv-lock .tv-screen::after { animation: none; }
}

/* ── snow ───────────────────────────────────────────────────────────────────
   Changing the bhajan IS changing the channel, so it gets the burst of static
   an analogue set gave you between stations. The grain is an inline SVG
   feTurbulence data-URI — no image is fetched, which is the whole reason it
   can be used on a 2G connection. */
.tv-static { display: none; }
body.video-on .tv-static {
  display: block; position: absolute; inset: 0; z-index: 5;
  pointer-events: none; opacity: 0;
  /* deliberately not transitioned: a set snapping between stations does not
     fade into static, and a transition here also made the state untestable —
     while the tab is hidden the browser throttles it and opacity stays at 0 */
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='140' height='140' filter='url(%23n)' opacity='0.8'/></svg>");
  background-size: 170px 170px;
}
body.video-on.tv-snow .tv-static {
  opacity: 1; animation: tvSnow 0.26s steps(4) infinite;
}
@keyframes tvSnow {
  0%   { background-position: 0 0; }
  25%  { background-position: -34px 22px; }
  50%  { background-position: 26px -18px; }
  75%  { background-position: -14px -30px; }
  100% { background-position: 18px 12px; }
}
/* the hum bar rolling up the picture while it hunts for the station */
body.video-on.tv-snow .tv-static::after {
  content: ""; position: absolute; left: 0; right: 0; height: 24%;
  background: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0));
  animation: tvRoll 0.85s linear infinite;
}
@keyframes tvRoll { from { top: -26%; } to { top: 104%; } }

/* ── the tube is never clean ────────────────────────────────────────────────
   Studied from tv.codvyn.in, which reads more like a real CRT than ours did,
   and the reason is not its snow — it is that SOMETHING sits over its picture
   at all times. Ours was bare glass the moment a bhajan started, and a bare
   rectangle of video reads as a video, not as a tube.

   Four textures in one layer, all of them STATIC. No canvas, no
   requestAnimationFrame, nothing redrawn per frame: this set stays open for
   hours on a cheap Android while a bhajan plays, and a permanent rAF would
   spend its battery to be almost invisible. Gradients and one data-URI cost
   nothing after first paint.

   The grain reuses the same feTurbulence data-URI as the snow, so it adds no
   bytes at all — the browser already has it. */
body.video-on .tv-glass {
  display: block; position: absolute; inset: 0; z-index: 6;
  pointer-events: none; border-radius: inherit;
  background-image:
    /* scanlines — 3px pitch, barely there. Heavier than this and it stops
       looking like a tube and starts looking like a filter. */
    repeating-linear-gradient(rgba(0, 0, 0, 0.13) 0 1px, transparent 1px 3px),
    /* the corners a tube never lit evenly */
    radial-gradient(78% 72% at 50% 48%, transparent 38%, rgba(0, 0, 0, 0.20) 72%, rgba(0, 0, 0, 0.40) 100%),
    /* a soft sheen off the glass, top-left as the room light would fall */
    linear-gradient(147deg, rgba(255, 255, 255, 0.07) 0%, transparent 42%);
}
.tv-glass { display: none; }
/* the grain, blended rather than laid on, so it sits IN the picture */
body.video-on .tv-glass::after {
  content: ""; position: absolute; inset: 0; opacity: 0.11;
  mix-blend-mode: overlay; border-radius: inherit;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='140' height='140' filter='url(%23n)' opacity='0.8'/></svg>");
  background-size: 170px 170px;
}

/* The mains hum, as an almost-imperceptible dip in brightness. The numbers are
   the point: 0.96 at its deepest, for two frames of a four-second cycle. Any
   stronger and it reads as a fault rather than as a tube warming. Opacity only,
   so it is composited and never touches layout. */
body.video-on .tv-glass { animation: tvHum 4s ease-in-out infinite; }
@keyframes tvHum {
  0%, 100% { opacity: 1; }
  42% { opacity: 0.985; }
  44% { opacity: 0.96; }
  46% { opacity: 0.995; }
}

/* And the snow never fully leaves. A tuned-in analogue set still carried a
   whisper of it behind the picture; ours cut to nothing, which is the single
   biggest reason the tube read as clean. */
body.video-on .tv-static { opacity: 0.05; }

@media (prefers-reduced-motion: reduce) {
  body.video-on .tv-glass { animation: none; }
}

/* ── the picture collapsing when the set is switched off ────────────────────
   A CRT did not just go dark: the picture squashed to a bright line, the line
   shrank to a dot, and the dot faded. It is the exact reverse of the strike. */
.tv-crt { display: none; }
body.video-on .tv-crt {
  display: block; position: absolute; inset: 0; z-index: 7;
  pointer-events: none; opacity: 0; background: #000;
}
body.video-on.tv-off .tv-crt { opacity: 1; }
body.video-on.tv-off .tv-crt::after {
  content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 3px;
  background: #fff; box-shadow: 0 0 18px #cfefff;
  animation: tvCollapse 0.42s cubic-bezier(0.5, 0, 0.7, 1) forwards;
}
@keyframes tvCollapse {
  0%   { transform: scaleY(60); opacity: 0.9; }
  55%  { transform: scaleY(1); opacity: 1; }
  100% { transform: scaleY(1) scaleX(0.004); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  body.video-on.tv-snow .tv-static,
  body.video-on.tv-snow .tv-static::after,
  body.video-on.tv-off .tv-crt::after { animation: none; }
}

/* ── श्याम-श्वेत ────────────────────────────────────────────────────────────
   Most people over fifty first saw these bhajans — and Ramayan — on a set that
   had no colour at all. The filter goes on the iframe rather than the casing:
   a filtered ancestor becomes the containing block for fixed descendants, and
   that is the exact bug that put the whole cabinet off-screen. */
body.video-on.tv-bw .yt-stage iframe {
  filter: grayscale(1) contrast(1.12) brightness(1.04);
}
.tv-bw-btn {
  display: none; flex: none; padding: 0 0.34rem; height: 15px;
  border-radius: 0.2rem; font-size: 0.46rem; font-weight: 700; letter-spacing: 0.04em;
  background: rgba(255, 236, 205, 0.12); color: #e8d3ac;
  border: 1px solid rgba(240, 220, 180, 0.4); cursor: pointer;
}
body.video-on .tv-bw-btn { display: block; }
.tv-bw-btn[aria-pressed="true"] { background: #f0dcb4; color: #2b1a0c; border-color: #f0dcb4; }

/* ── the channel banner ─────────────────────────────────────────────────────
   Straight off a 90s set: change the channel and it tells you, in the corner,
   what you have landed on. Here it earns its place twice over — while the
   cabinet is open it covers the player, so the name of the bhajan would
   otherwise be hidden behind the woodwork. */
.tv-osd { display: none; }
body.video-on .tv-osd {
  display: flex; position: absolute; left: 0.4rem; right: 0.4rem; top: 0.4rem;
  z-index: 6; align-items: baseline; gap: 0.4rem;
  padding: 0.26rem 0.45rem; border-radius: 0.2rem;
  background: rgba(6, 20, 10, 0.82);
  border-left: 3px solid #7CFC6A;
  opacity: 0; pointer-events: none;
}
body.video-on.tv-osd-on .tv-osd { opacity: 1; }
.tv-osd__ch {
  font-size: 0.56rem; letter-spacing: 0.1em; color: #7CFC6A; flex: none;
  text-shadow: 0 0 8px rgba(124, 252, 106, 0.6);
}
.tv-osd__n {
  font-size: 0.55rem; color: #eafbe4; opacity: 0.92;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── the volume bar ─────────────────────────────────────────────────────────
   The other thing a 90s set put on screen. It reads the same slider the ↑↓
   keys and the player's own control read, so the knob is not a second source
   of truth — there is one volume, shown three ways. */
.tv-vol { display: none; }
body.video-on .tv-vol {
  display: flex; position: absolute; left: 0.4rem; right: 0.4rem; bottom: 0.4rem;
  z-index: 6; align-items: center; gap: 0.35rem;
  padding: 0.26rem 0.45rem; border-radius: 0.2rem;
  background: rgba(6, 20, 10, 0.82); border-left: 3px solid #7CFC6A;
  opacity: 0; pointer-events: none;
}
body.video-on.tv-vol-on .tv-vol { opacity: 1; }
.tv-vol b {
  font-size: 0.5rem; letter-spacing: 0.1em; color: #7CFC6A; flex: none;
  text-shadow: 0 0 8px rgba(124, 252, 106, 0.6);
}
.tv-vol__bar {
  flex: 1 1 auto; height: 7px; border-radius: 1px;
  background: rgba(124, 252, 106, 0.16);
  box-shadow: inset 0 0 0 1px rgba(124, 252, 106, 0.35);
  overflow: hidden;
}
/* stepped, the way a segmented on-screen meter was — not a smooth fill */
.tv-vol__bar s {
  display: block; height: 100%; width: 0; text-decoration: none;
  background-image: linear-gradient(90deg, #7CFC6A 0 4px, transparent 4px 6px);
  background-size: 6px 100%;
  transition: width 0.12s linear;
}
.tv-vol em {
  font-size: 0.5rem; font-style: normal; color: #eafbe4; flex: none;
  min-width: 1.4em; text-align: right;
}

/* ── the doors ──────────────────────────────────────────────────────────────
   These sets had sliding doors you shut over the screen when nobody was
   watching. Opening them is the same idea as the प्रवेश द्वार gate: you open
   something, and what is behind it is already playing.

   Default state is OPEN (translated out of the frame) and `.tv-shut` closes
   them, so the doors are never in the way if the JS never runs. */
.tv-door { display: none; }
/* z-index 8, above everything the screen paints. The shutters are the frontmost
   thing on a real cabinet — they sit in front of the glass, so nothing shown on
   the tube can be in front of them.

   At 4 they were behind .tv-crt, which is a solid black panel at 7 that tv-off
   raises for the power-off collapse. So on POWER the doors closed behind an
   opaque rectangle and the whole animation was invisible; on ✕ it only became
   visible again because ✕ stopped setting tv-off. Putting them where they
   physically belong fixes both, and lets POWER keep its collapse: the tube dies
   and the shutters come across in front of it, which is the order it happens in
   on the real object. */
body.video-on .tv-door {
  display: block; position: absolute; top: 0; bottom: 0; width: 50.6%; z-index: 8;
  /* The doors were left on the old regular 1px/5px stripe when the casing got
     real grain, so the set had corduroy shutters on a wood cabinet. Same
     feTurbulence treatment, one shade darker and a little finer — a shutter is
     a thinner panel than the carcass and reads that way. */
  background:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='260' height='180'><filter id='d'><feTurbulence type='fractalNoise' baseFrequency='0.5 0.014' numOctaves='3' seed='4'/><feColorMatrix type='saturate' values='0'/></filter><rect width='260' height='180' filter='url(%23d)' opacity='0.5'/></svg>"),
    repeating-linear-gradient(90deg, rgba(0, 0, 0, 0.10) 0 1px, transparent 1px 6px),
    linear-gradient(#6d472a, #4a2e18 55%, #33200f);
  background-size: 260px 180px, auto, auto;
  background-blend-mode: overlay, normal, normal;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.5), 0 0 18px rgba(0, 0, 0, 0.55);
  transition: transform 0.62s cubic-bezier(0.4, 0, 0.2, 1);
  will-change: transform;
}
body.video-on .tv-door--l { left: 0; transform: translateX(-101%); }
body.video-on .tv-door--r { right: 0; transform: translateX(101%); }
body.video-on.tv-shut .tv-door--l,
body.video-on.tv-shut .tv-door--r { transform: translateX(0); }
/* the brass handle down the inner edge of each door */
/* Longer than they were, and lit down one edge. In the reference the handles
   are the most recognisable thing on the front — a pair of vertical bars you
   would actually grab — and ours were a 32%-tall hairline. */
body.video-on .tv-door::after {
  content: ""; position: absolute; top: 26%; bottom: 26%; width: 4px;
  border-radius: 2px;
  background: linear-gradient(90deg, #f6e6c2, #c9a86a 45%, #7d6238);
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
}
body.video-on .tv-door--l::after { right: 3px; }
body.video-on .tv-door--r::after { left: 3px; }

/* ── the tube striking ──────────────────────────────────────────────────────
   A valve set did not show a picture the instant you turned it on: a white
   line struck across the middle and opened out. That second of warm-up is
   where the logo goes. */
.tv-boot { display: none; }
body.video-on .tv-boot {
  display: flex; position: absolute; inset: 0; z-index: 3;
  flex-direction: column; align-items: center; justify-content: center; gap: 0.2rem;
  background: radial-gradient(70% 90% at 50% 50%, #123a4a 0%, #06171f 60%, #010506 100%);
  color: #bfe9ff; pointer-events: none;
  opacity: 0; transition: opacity 0.5s ease;
}
body.video-on.tv-booting .tv-boot { opacity: 1; }
.tv-boot__om { font-size: 2rem; line-height: 1; text-shadow: 0 0 14px rgba(150, 225, 255, 0.75); }
.tv-boot__n { font-size: 0.6rem; letter-spacing: 0.3em; opacity: 0.8; }
body.video-on.tv-booting .tv-boot::before {
  content: ""; position: absolute; left: 6%; right: 6%; top: 50%;
  height: 2px; background: #eafcff; box-shadow: 0 0 16px #9fe8ff;
  animation: tvStrike 0.8s cubic-bezier(0.2, 0, 0, 1) forwards;
}
@keyframes tvStrike {
  0%   { transform: scaleX(0.02); opacity: 1; }
  50%  { transform: scaleX(1); opacity: 1; }
  100% { transform: scaleX(1) scaleY(90); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  body.video-on .tv-door { transition: none; }
  body.video-on.tv-booting .tv-boot::before { animation: none; }
}

/* ── while the set is open, everything else steps back ──────────────────────
   Not decoration. On a phone the जगह/माहौल/पंचांग rail sat directly on top of
   the casing: document.elementFromPoint over the ✕ returned #dockScenes, so
   neither the close button nor fullscreen could be tapped at all. */
body.video-on .dock,
body.video-on .chips,
body.video-on #scenes,
body.video-on .fest-banner,
body.video-on .update-bar,
body.video-on .install-card,
body.video-on .share-pop,
body.video-on .reactions,
body.video-on .matki,
body.video-on #panchang,
body.video-on #mixer,
body.video-on #vadya,
body.video-on #panel,
body.video-on #panelScrim {
  /* Not transitioned, deliberately. A fade here reads as a hesitation before
     the set opens, and while the tab is hidden the browser throttles the
     transition so these stayed fully opaque on top of the cabinet. */
  opacity: 0 !important; pointer-events: none !important;
}

/* The player's own buttons stay — they are below the cabinet, where they
   belong — but their tooltips open upward and land on the picture. */
body.video-on .player [data-tip]::after { display: none; }

/* MENU. The playlist is hidden with !important while the set is open, so
   bringing it back has to out-shout that, and sit above the cabinet.
   `transform` is forced too: .panel slides in on a transition, and a paused or
   dropped transition would otherwise leave it parked off-screen. */
body.video-on.tv-menu #panel {
  opacity: 1 !important; pointer-events: auto !important;
  z-index: 62 !important; transform: translate(-50%, 0) !important;
}

/* ── the playlist, laid out for a landscape screen ─────────────────────────
   In the set the chips wrapped to three rows and ate most of the picture
   before a single bhajan was visible — a portrait layout inside a landscape
   window. The channels now run down the left as a list and the bhajans fill
   the right, which is how a cassette booklet or a channel guide has always
   been arranged, and it is the shape the screen already has.

   Only inside the television. The radio's own panel is portrait and narrow and
   the chip row is right for it, so this is scoped to tv-menu and changes
   nothing about the page. */
@media (min-width: 620px) {
  /* :not(.max) matters. .panel.max carries its own taller max-height, and this
     selector is more specific than it — so setting max-height here silently
     overrode the expand button and made it do nothing inside the set. The grid
     still applies when expanded; only the height cap steps aside. */
  /* The grid itself now lives on .panel for every mode; only the height cap and
     the keyboard row differ inside the cabinet. */
  body.video-on.tv-menu #panel:not(.max) { max-height: min(62dvh, 30rem); }
  body.video-on.tv-menu #panel .panel-keys { display: none; }
}

/* Fullscreen: the casing becomes the screen, so grilles, lip, legs and bezel
   step out of the way rather than framing a letterboxed picture. */
body.video-on .yt-stage:fullscreen {
  width: 100%; height: 100%; padding: 0; border-radius: 0; background: #000;
  align-items: center; justify-content: center;
}
/* Fullscreen shows the PICTURE, not the cabinet — and that is deliberate after
   trying the other way.

   Keeping the cabinet here was tried and reverted: the casing is sized from
   --tv-w with padding, grilles and a control panel all derived from it, and at
   fullscreen scale those proportions stretch — a hairline grille down one side,
   a control panel taller than it is wide, and the bar overflowing the top. The
   set is drawn for the sizes it actually appears at, and fullscreen is not one
   of them.

   So the two modes now divide the way YouTube divides them, for the same
   reason. Cinema is the television, bigger. Fullscreen is the picture, with
   nothing else on screen. Neither is a worse version of the other. */
body.video-on .yt-stage:fullscreen::before,
body.video-on .yt-stage:fullscreen::after,
body.video-on .yt-stage:fullscreen .tv-grille { display: none; }
body.video-on .yt-stage:fullscreen .tv-screen {
  width: 100%; height: 100%; aspect-ratio: auto;
  border-radius: 0; box-shadow: none;
}
/* Controls fade away, the way they do on any full-screen player. Nothing should
   sit permanently over a picture somebody chose to make full-screen; the bar
   comes back on the first movement or touch and leaves again after a few idle
   seconds. The cursor goes with it, because a pointer parked over a bhajan is
   the same intrusion in miniature. */
/* Auto-hide ONLY where a pointer can reliably bring the controls back.

   On iPad this trapped people. The bar hid after 2.8s and the tap meant to
   restore it landed on the YouTube iframe, which is cross-origin and swallows
   the event — so touchstart never reached us and the controls were gone for
   good. The only way out was the browser's back button. Reported exactly that
   way, and it is the worst thing I have shipped here.

   A hover-capable device does not have this problem: mousemove fires over the
   iframe regardless, because the pointer moving is not an event the iframe can
   consume. So the fade is now gated on hover, and a touch device keeps its
   controls on screen permanently — slightly less clean, and it cannot strand
   anybody. */
@media (hover: hover) and (pointer: fine) {
  body.video-on .yt-stage:fullscreen .video-bar {
    opacity: 0; transition: opacity 0.28s ease; pointer-events: none;
  }
  body.video-on .yt-stage:fullscreen.tv-ui .video-bar {
    opacity: 1; pointer-events: auto;
  }
  body.video-on .yt-stage:fullscreen { cursor: none; }
  body.video-on .yt-stage:fullscreen.tv-ui { cursor: default; }
}
@media (prefers-reduced-motion: reduce) {
  body.video-on .yt-stage:fullscreen .video-bar { transition: none; }
}

/* the controls must not inherit a size derived from a cabinet that is no longer
   on screen — this is what produced fist-sized buttons over the picture */
body.video-on .yt-stage:fullscreen .video-close {
  width: 34px; height: 34px; min-width: 34px; font-size: 0.85rem;
}
body.video-on .yt-stage:fullscreen .tv-bw-btn { font-size: 0.7rem; padding: 0.25rem 0.5rem; }
body.video-on .yt-stage:fullscreen .video-bar__t { font-size: 0.8rem; }
body.video-on .yt-stage:fullscreen iframe { border-radius: 0; }
body.video-on .yt-stage:fullscreen .video-bar {
  background: linear-gradient(rgba(0, 0, 0, 0.85), transparent);
  top: 0; bottom: auto; height: 2.6rem;
}

/* The disc used to carry a small red play badge on its corner, saying "there
   is a video in here". It never rendered: .yt-stage clips with overflow:hidden
   and border-radius:50% — it has to, the whole TV cabinet lives inside it while
   the set is shut — so a badge pinned at right:-1px/bottom:-1px was sliced by
   the circle into a red crescent. Removed rather than re-anchored: the control
   row already carries a 📺 button that opens the set, so the affordance was a
   second way of saying the same thing, and it was saying it wrongly. */
/* ── the set ────────────────────────────────────────────────────────────────
   A wooden television, parked above the player. It is fixed there on purpose:
   dragging was fiddly with a thumb, and a panel that moves is a panel you have
   to hunt for. 4:3 because that is the shape of the sets these bhajans were
   first heard beside — the same era as the cassette player this whole site is
   built around. Casing, screen and knobs are all CSS; no image is loaded. */
.video-bar {
  display: none; position: absolute; left: 0; right: 0; bottom: 0; z-index: 6;
  align-items: center; gap: 0.4rem; padding: 0 0.7rem; height: 2rem;
}
body.video-on .video-bar { display: flex; }
.video-bar__t {
  font-size: 0.6rem; letter-spacing: 0.12em; opacity: 0.55;
  flex: 1 1 auto; text-align: center; color: #f0d9a8;
}
.video-close {
  width: 26px; height: 26px; min-width: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; flex: none;
  background: radial-gradient(circle at 35% 30%, #6b4426, #2e1a0e);
  color: #f6ead8; font-size: 0.75rem;
  border: 1px solid rgba(246, 234, 216, 0.35);
}
.video-close:hover, .video-close:focus-visible { color: #ffd98f; border-color: #ffd98f; }

}

/* ── मोर पंख drifting down ── */
.pankh { display: none; }
body.fest-on[data-fest="janmashtami"] .pankh {
  display: block; position: absolute; inset: 0;
  pointer-events: none; overflow: hidden; z-index: 4;
}
/* कृष्ण stands in front of the far-temple silhouettes, which are drawn in the
   same --sil family — at first he vanished into a dome completely, with only the
   feather showing. The near figures get a tone darker than anything behind them.
   The plain var(--sil) declaration stays as the fallback for browsers without
   color-mix, where he is merely low-contrast rather than invisible. */
/* currentColor so stroked limbs and filled masses take one tone. The CSS targets
   the group only — children keep their own fill="none" presentation attribute,
   which a rule on the element itself would have overridden. */
.fest-figure, .fest-cow { color: var(--sil); fill: currentColor; }
.fest-figure, .fest-cow { color: color-mix(in srgb, var(--sil) 62%, #04060e); }
.fest-ground { fill: var(--sil); opacity: 0.38; }

/* Where the vignette stands.
   The scene's own ground is the ghat, around y=560 — that is where the temples
   sit and where these figures have to sit to look like they are standing on
   anything. On a wide screen, and in wallpaper mode where the player is hidden,
   the whole scene is visible and y=430 left them floating among the far domes.
   On a phone the player card covers everything below about y=437, so there they
   move up only as far as the far-temple base line and let the card overlap their
   feet — which reads as standing behind something, not as hovering. */
.fest-stage { transform: translateY(130px); }
.fest-bank { fill: var(--ghat); }
/* On a phone the player card covers the scene's ground, and where it starts in
   viewBox terms depends on the viewport height — svg y 437 on an 812-tall
   screen but y 357 on a 667-tall one. A single offset cannot clear both, which
   is why the figures kept ending up behind the card with nothing under them.
   The group carries its own bank now, so it can be lifted by height and still
   be standing on something. Steps derived from feet ≈ 810 − 314600/height,
   the card being a near-constant 250px tall. */
@media (max-width: 720px) { .fest-stage { transform: none; } }
@media (max-width: 720px) and (max-height: 850px) { .fest-stage { transform: translateY(-30px); } }
@media (max-width: 720px) and (max-height: 790px) { .fest-stage { transform: translateY(-70px); } }
@media (max-width: 720px) and (max-height: 710px) { .fest-stage { transform: translateY(-105px); } }
@media (max-width: 720px) and (max-height: 660px) { .fest-stage { transform: translateY(-140px); } }
@media (max-width: 720px) and (max-height: 610px) { .fest-stage { transform: translateY(-170px); } }
@media (max-width: 720px) and (max-height: 580px) { .fest-stage { transform: translateY(-210px); } }

/* The मोर पंख on his crown sways. It sits in its own group with no transform
   attribute, because a CSS animation on transform REPLACES the attribute rather
   than composing with it — the same trap as the boat and the bell. That is also
   why .fest-mor is now wrapped in .fest-mor-place: .fest-mor already animates
   transform, so its placement has to live on a parent. */
/* Tried this as a flat shadow to match the silhouette figure. It does not work:
   what identifies a मोर पंख is the eye, and in pure silhouette the eye vanishes
   and only the ragged barbs remain — it reads as a tuft of hair. So the पंख keeps
   its colour, like the मोर a few steps away, and earns its place by being small.
   The drop shadow is what separates it from the dark head behind it. */
.fest-pankh image { filter: drop-shadow(0 1px 1.5px rgba(0, 0, 0, 0.55)); }
.fest-pankh {
  transform-box: fill-box; transform-origin: 50% 100%;
  animation: pankhSway 4.6s ease-in-out infinite;
}
@keyframes pankhSway {
  0%, 100% { transform: rotate(-5deg); }
  50%      { transform: rotate(6deg); }
}

/* the मोर lifts and fans his tail every few seconds, then lets it fall. Origin at
   the base, where the tail meets the body, so it opens outward rather than
   sliding. Inner group again — .fest-mor already animates transform for the bob,
   and two animations on one element would fight over it. */
.fest-mor__tail {
  transform-box: fill-box; transform-origin: 0% 100%;
  animation: morFan 7s ease-in-out infinite;
}
@keyframes morFan {
  0%, 34%, 100% { transform: rotate(0deg) scale(1); }
  46%, 62%      { transform: rotate(-15deg) scale(1.22); }
}

/* the पूँछ flicks. Its own group, no transform attribute — the animation would
   replace one. Origin at the top of the tail, where it joins the rump. */
.fest-cow__tail {
  transform-box: fill-box; transform-origin: 50% 0%;
  animation: cowTail 3.9s ease-in-out infinite;
}
@keyframes cowTail {
  0%, 100% { transform: rotate(-5deg); }
  50%      { transform: rotate(7deg); }
}


/* आने वाले त्योहार inside the पंचांग panel */
.pg-sub {
  margin: 0.9rem 0 0.4rem; font-size: 0.82rem; font-weight: 700;
  opacity: 0.75; letter-spacing: 0.02em;
}
.pg-fests { list-style: none; display: flex; flex-direction: column; gap: 0.3rem; }
.pg-fests li {
  display: flex; align-items: baseline; gap: 0.5rem;
  font-size: 0.84rem; padding: 0.3rem 0;
  border-bottom: 1px dotted rgba(255, 217, 143, 0.18);
}
.pg-fests li:last-child { border-bottom: 0; }
.pg-fests .f-name { flex: 1 1 auto; font-weight: 600; }
.pg-fests .f-when { opacity: 0.7; font-size: 0.78rem; white-space: nowrap; }
.pg-fests .f-away {
  font-size: 0.72rem; opacity: 0.85; white-space: nowrap;
  padding: 0.05rem 0.4rem; border-radius: 999px;
  background: rgba(255, 217, 143, 0.14);
}
.pg-fests li.is-today .f-away { background: rgba(255, 184, 92, 0.4); opacity: 1; }
/* the badge swaps to the date on hover, so it must not jump the layout */
.matki__hint--when { font-size: 0.62rem; letter-spacing: 0; }

/* the gate lintel carries गजलक्ष्मी all year; during a festival its own carving
   takes the slot. Only one is ever drawn. */
.carve--janmashtami { display: none; }
body.fest-on[data-fest="janmashtami"] .carve--aam { display: none; }
body.fest-on[data-fest="janmashtami"] .carve--janmashtami { display: inline; }

.pankh img {
  position: absolute; top: -12%; left: var(--x);
  width: clamp(30px, 6vw, 54px); height: auto; opacity: 0;
  filter: drop-shadow(0 3px 8px rgba(0, 0, 0, 0.45));
  animation: pankhFall var(--dur) linear infinite;
  animation-delay: var(--dl);
}
@keyframes pankhFall {
  0%   { transform: translate(0,0) rotate(0) scale(var(--sc));            opacity: 0; }
  8%   {                                                                  opacity: 0.9; }
  92%  {                                                                  opacity: 0.9; }
  100% { transform: translate(var(--drift), 112vh) rotate(var(--rot)) scale(var(--sc)); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .pankh img, .fest-jhula, .fest-mor, .fest-pankh,
  .fest-cow__tail, .fest-mor__tail, .fest-dhwaj { animation: none; }
}

/* the टीवी toggle in the share sheet — a mode, not a destination, so it reads
   differently from the network rows below it */
.share-tv { border-bottom: 1px solid rgba(246, 234, 216, 0.16); }
.share-tv[aria-pressed="true"], .share-tv.on { color: #ffd98f; font-weight: 700; }
.share-tv[aria-pressed="true"]::after, .share-tv.on::after { content: "✓"; margin-left: auto; color: #ffd98f; }

/* the feature page chip — gold rather than a brand colour, since it points
   inward at the site itself rather than out to another service */
.about-chip { border-color: rgba(255, 217, 143, 0.5); color: #ffd98f; }
.about-chip:hover, .about-chip:focus-visible { border-color: #ffd98f; background: rgba(255, 217, 143, 0.12); }

/* The brand chips drop their labels on a phone and survive it — an Instagram or
   Spotify logo names itself. A plain ⓘ circle does not, and this was the link
   people said they could not find, so it keeps its words at every width. */
.about-chip span { display: inline !important; }
.about-chip { width: auto; padding-inline: 0.7rem; gap: 0.35rem; }

/* ── the footer contact line was invisible ─────────────────────────────────
   Fixed once by giving .contact a colour, which was the wrong diagnosis. The
   real fault: the footer is NOT the sky. It carries its own dark panel
   (rgba(16, 8, 4, 0.82)), so the phase colouring that is right for the clock
   and the date line — dark text over a pale morning sky — is simply wrong in
   here. Measured 1.3 against the footer at midday, which is invisible.

   Every other thing in the footer is cream. .contact only had to match, and it
   has to out-rank [data-phase="..."] .contact to do it. The links inherit,
   because the phase can no longer flip the text colour underneath them. */
.footer .contact { color: var(--cream); opacity: 0.85; text-shadow: 0 1px 8px rgba(0, 0, 0, 0.6); }
.footer .contact a { color: inherit; text-decoration: underline; text-underline-offset: 2px;
  text-decoration-color: currentColor; text-decoration-thickness: 1px; }
.footer .contact a:hover, .footer .contact a:focus-visible { text-decoration-thickness: 2px; }

/* व्रत sit under the festivals in the पंचांग panel and read one step quieter —
   they recur every month, so they should not shout the way a यear-marker does. */
.pg-vrats .f-name { font-weight: 600; }
.pg-vrats li { opacity: 0.92; }
.pg-vrats li.is-today { opacity: 1; }

/* the next व्रत, riding on the date line. Small, but it is the only thing on the
   first screen that says this site knows the calendar — so it gets the gold. */
.date-next {
  display: inline-block; margin-inline-start: 0.45rem;
  font-size: 0.92em; font-weight: 700; color: #ffd98f; white-space: nowrap;
}
.date-next.is-today { color: #ffe6b8; }
@media (max-width: 380px) { .date-next { display: block; margin-inline-start: 0; } }

/* ── दही हांडी: the मटकी breaks at the moment of birth ──────────────────────
   Two beats, because one is not a break — it is a wobble. The pot shakes on the
   rope first, the way one does when somebody has just reached it, and only then
   splits: the halves tip apart and drop out of frame while the दही runs down.

   Everything animates transform and opacity so the compositor carries it; the
   pot is an inline SVG and giving it a `transform` directly would fight the
   attribute already positioning it, so the halves are clipped copies laid over
   the original rather than the original itself. */
.matki--broken .matki__pot { animation: matkiShake 0.5s ease-in-out 1; }
.matki--broken .matki__pot::after,
.matki--broken .matki__pot::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: inherit;
}
.matki--broken .matki__rope { animation: matkiSlack 1.2s ease-out 0.5s forwards; }
.matki--broken .matki__hint {
  animation: matkiCry 0.6s ease-out 0.6s both;
  background: rgba(30, 12, 48, 0.92); border-color: rgba(255, 214, 120, 0.75);
}
/* the दही: a bright spill that falls and fades, drawn rather than imaged */
.matki--broken::after {
  content: ""; position: absolute; left: 50%; top: 58%;
  width: 2.2rem; height: 2.2rem; margin-left: -1.1rem;
  border-radius: 50% 50% 45% 45%;
  background: radial-gradient(60% 55% at 50% 30%, #fffdf6 0%, #f4e9d2 55%, rgba(244,233,210,0) 100%);
  animation: dahiFall 1.5s cubic-bezier(0.45, 0, 0.75, 1) 0.5s forwards;
  pointer-events: none;
}
@keyframes matkiShake {
  0%, 100% { transform: rotate(0deg); }
  20% { transform: rotate(-7deg); }
  45% { transform: rotate(6deg); }
  70% { transform: rotate(-4deg); }
}
@keyframes matkiSlack { to { transform: scaleY(1.35); opacity: 0.45; } }
@keyframes dahiFall {
  0%   { transform: translateY(0) scale(0.5); opacity: 0; }
  25%  { transform: translateY(0.4rem) scale(1); opacity: 1; }
  100% { transform: translateY(4.5rem) scale(1.5); opacity: 0; }
}
@keyframes matkiCry {
  0%   { transform: scale(0.7); opacity: 0; }
  60%  { transform: scale(1.12); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
/* the pot itself tips off the rope and leaves */
.matki--broken .orn { animation: matkiFall 1.3s cubic-bezier(0.5, 0, 0.8, 1) 0.5s forwards; }
@keyframes matkiFall {
  0%   { transform: rotate(0) translateY(0); opacity: 1; }
  35%  { transform: rotate(28deg) translateY(0.5rem); opacity: 1; }
  100% { transform: rotate(96deg) translateY(5rem); opacity: 0; }
}

/* Reduced motion still gets the moment — it just arrives rather than performs.
   Somebody who asked for less movement did not ask to be left on a countdown
   to something that has happened. */
@media (prefers-reduced-motion: reduce) {
  .matki--broken .matki__pot,
  .matki--broken .matki__rope,
  .matki--broken .orn,
  .matki--broken::after,
  .matki--broken .matki__hint { animation: none; }
  .matki--broken .orn { opacity: 0; }
  .matki--broken::after { display: none; }
}
