/* Display typeface: Playfair Display 700, self-hosted (the CSP allows no
   font CDNs), subset for the languages the stations broadcast in. Scripts it
   lacks (Arabic, Farsi) fall through to the system serif stack. */
@font-face {
	font-family: "Playfair Display";
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url(/static/fonts/playfair-700-cyrillic.woff2) format("woff2");
	unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
	font-family: "Playfair Display";
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url(/static/fonts/playfair-700-vietnamese.woff2) format("woff2");
	unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
	font-family: "Playfair Display";
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url(/static/fonts/playfair-700-latin-ext.woff2) format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
	font-family: "Playfair Display";
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url(/static/fonts/playfair-700-latin.woff2) format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "Fira Sans";
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url(/static/fonts/firasans-700-cyrillic-ext.woff2) format("woff2");
	unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
	font-family: "Fira Sans";
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url(/static/fonts/firasans-700-cyrillic.woff2) format("woff2");
	unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
	font-family: "Fira Sans";
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url(/static/fonts/firasans-700-vietnamese.woff2) format("woff2");
	unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
	font-family: "Fira Sans";
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url(/static/fonts/firasans-700-latin-ext.woff2) format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
	font-family: "Fira Sans";
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url(/static/fonts/firasans-700-latin.woff2) format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
	font-family: "Nunito";
	font-style: normal;
	font-weight: 800;
	font-display: swap;
	src: url(/static/fonts/nunito-800-cyrillic-ext.woff2) format("woff2");
	unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
	font-family: "Nunito";
	font-style: normal;
	font-weight: 800;
	font-display: swap;
	src: url(/static/fonts/nunito-800-cyrillic.woff2) format("woff2");
	unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
	font-family: "Nunito";
	font-style: normal;
	font-weight: 800;
	font-display: swap;
	src: url(/static/fonts/nunito-800-vietnamese.woff2) format("woff2");
	unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
	font-family: "Nunito";
	font-style: normal;
	font-weight: 800;
	font-display: swap;
	src: url(/static/fonts/nunito-800-latin-ext.woff2) format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
	font-family: "Nunito";
	font-style: normal;
	font-weight: 800;
	font-display: swap;
	src: url(/static/fonts/nunito-800-latin.woff2) format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Standard-weight cuts, for stations that choose the lighter headline. */
@font-face {
	font-family: "Playfair Display";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url(/static/fonts/playfair-500-cyrillic.woff2) format("woff2");
	unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
	font-family: "Playfair Display";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url(/static/fonts/playfair-500-vietnamese.woff2) format("woff2");
	unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
	font-family: "Playfair Display";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url(/static/fonts/playfair-500-latin-ext.woff2) format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
	font-family: "Playfair Display";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url(/static/fonts/playfair-500-latin.woff2) format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
	font-family: "Fira Sans";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url(/static/fonts/firasans-500-cyrillic-ext.woff2) format("woff2");
	unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
	font-family: "Fira Sans";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url(/static/fonts/firasans-500-cyrillic.woff2) format("woff2");
	unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
	font-family: "Fira Sans";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url(/static/fonts/firasans-500-vietnamese.woff2) format("woff2");
	unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
	font-family: "Fira Sans";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url(/static/fonts/firasans-500-latin-ext.woff2) format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
	font-family: "Fira Sans";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url(/static/fonts/firasans-500-latin.woff2) format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
	font-family: "Nunito";
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url(/static/fonts/nunito-600-cyrillic-ext.woff2) format("woff2");
	unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
	font-family: "Nunito";
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url(/static/fonts/nunito-600-cyrillic.woff2) format("woff2");
	unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
	font-family: "Nunito";
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url(/static/fonts/nunito-600-vietnamese.woff2) format("woff2");
	unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
	font-family: "Nunito";
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url(/static/fonts/nunito-600-latin-ext.woff2) format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
	font-family: "Nunito";
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url(/static/fonts/nunito-600-latin.woff2) format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Uncut Sans (SIL OFL 1.1): the Fresh voice — the typeface of
   globalbibleschool.org. Latin scripts only; Cyrillic and Vietnamese fall
   through to the system stack. */
@font-face {
	font-family: "Uncut Sans";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url(/static/fonts/uncut-500.woff) format("woff");
}
@font-face {
	font-family: "Uncut Sans";
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url(/static/fonts/uncut-600.woff) format("woff");
}

/* Three headline voices, chosen per station in Appearance. Editorial is the
   default; the variable cascades through headline, page titles, posters and
   the popup, so the voice runs through the whole site. */
:root { --font-display: "Playfair Display", Georgia, "Times New Roman", serif; }
:root[data-voice="modern"] { --font-display: "Fira Sans", "Segoe UI", "Helvetica Neue", Arial, sans-serif; }
:root[data-voice="warm"] { --font-display: "Nunito", "Trebuchet MS", "Segoe UI", sans-serif; }
/* Clean: the system stack — the site's original voice, zero bytes to load. */
:root[data-voice="clean"] { --font-display: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; }
:root[data-voice="fresh"] { --font-display: "Uncut Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; }
:root[data-voice="modern"] .display { letter-spacing: -0.5px; line-height: 1.08; }
:root[data-voice="warm"] .display { letter-spacing: -0.3px; line-height: 1.1; }
:root[data-voice="clean"] .display { letter-spacing: -2px; line-height: 1.04; }
:root[data-voice="fresh"] .display { letter-spacing: -1.5px; line-height: 1.06; }

/* Headline weight, chosen per station: Bold is the classic heavy cut, Standard
   the lighter one. Each voice maps the two names onto the weights its files
   actually ship in, so the browser never fakes a weight. */
:root { --w-display: 700; }
:root[data-voice="warm"], :root[data-voice="clean"] { --w-display: 800; }
/* Uncut Sans ships no 700: its Bold is the Semibold, its Standard the
   Medium (the generic standard rule below lands on 500). */
:root[data-voice="fresh"] { --w-display: 600; }
:root[data-weight="standard"] { --w-display: 500; }
:root[data-weight="standard"][data-voice="warm"],
:root[data-weight="standard"][data-voice="clean"] { --w-display: 600; }


:root {
	--accent: #14608f;
	--accent-lift: color-mix(in srgb, var(--accent) 62%, #7ad3ff);
	--accent-glow: color-mix(in srgb, var(--accent) 45%, transparent);
	--bg: #06070b;
	--bg-lift: #0d1018;
	--ink: #f4f7fb;
	--text: #aab6c6;
	--muted: #6f7d90;
	--line: rgba(255, 255, 255, 0.09);
	--card: rgba(255, 255, 255, 0.035);
	--radius: 20px;
	--ease: cubic-bezier(0.22, 1, 0.36, 1);
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
	margin: 0;
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
	background: var(--bg);
	color: var(--text);
	font-size: 16.5px;
	line-height: 1.65;
	overflow-x: hidden;
	-webkit-font-smoothing: antialiased;
}
body.is-playing { padding-bottom: 92px; }
.container { max-width: 1180px; margin: 0 auto; padding: 0 28px; }
::selection { background: var(--accent); color: #fff; }
:focus-visible { outline: 2px solid var(--accent-lift); outline-offset: 3px; border-radius: 6px; }

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

/* ---------- Ambient background ---------- */
body::before {
	content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
	background:
		radial-gradient(900px 520px at 78% -8%, color-mix(in srgb, var(--accent) 26%, transparent), transparent 62%),
		radial-gradient(700px 420px at 8% 12%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 60%),
		linear-gradient(180deg, transparent 55%, var(--bg) 100%);
}

/* ---------- Header ---------- */
.site-header {
	position: sticky; top: 0; z-index: 60;
	/* Glass, not tint: the blur and saturation do the work, the film is a
	   whisper with a vertical grade, and an inner highlight gives the pane
	   its top edge — the same recipe as the on-air card. */
	background: linear-gradient(180deg,
		color-mix(in srgb, var(--bg) 52%, transparent) 0%,
		color-mix(in srgb, var(--bg) 34%, transparent) 100%);
	backdrop-filter: blur(24px) saturate(180%);
	-webkit-backdrop-filter: blur(24px) saturate(180%);
	border-bottom: 1px solid rgba(255, 255, 255, 0.07);
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
	transition: border-color 0.3s, background 0.3s;
}
body.scrolled .site-header { border-bottom-color: var(--line); }
.header-inner { display: flex; align-items: center; gap: 28px; min-height: 74px; }
.brand { display: flex; align-items: center; text-decoration: none; margin-right: auto; }
.brand-logo { height: 44px; width: auto; max-width: 190px; object-fit: contain; display: block; }
.brand-name { font-weight: 700; font-size: 18px; color: var(--ink); letter-spacing: -0.2px; }

.site-nav { display: flex; align-items: center; gap: 2px; }
.site-nav a {
	position: relative; text-decoration: none;
	/* Quiet until engaged: chrome whispers so the content can carry. */
	color: color-mix(in srgb, var(--text) 82%, var(--muted));
	font-weight: 500; font-size: 14px; letter-spacing: 0.1px;
	padding: 9px 15px; border-radius: 10px;
	transition: color 0.18s;
}
.site-nav a:hover, .site-nav a.active { color: var(--ink); }
.site-nav a::after {
	content: ""; position: absolute; left: 15px; right: 15px; bottom: 4px; height: 2px;
	background: var(--accent-lift); border-radius: 2px;
	transform: scaleX(0); transform-origin: left; transition: transform 0.28s var(--ease);
}
.site-nav a:hover { color: var(--ink); }
.site-nav a:hover::after { transform: scaleX(1); }

.btn-live {
	display: inline-flex; align-items: center; gap: 10px;
	border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
	background: color-mix(in srgb, var(--accent) 18%, transparent);
	color: var(--ink); font: inherit; font-weight: 700; font-size: 14.5px;
	padding: 9px 18px; border-radius: 999px; cursor: pointer; white-space: nowrap;
	transition: background 0.2s, border-color 0.2s, box-shadow 0.2s;
}
.btn-live:hover {
	background: color-mix(in srgb, var(--accent) 34%, transparent);
	box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 12%, transparent);
}

/* Animated three-bar wave inside the header button */
.wave { display: flex; align-items: flex-end; gap: 2.5px; height: 13px; }
.wave i { width: 2.5px; height: 40%; border-radius: 2px; background: var(--accent-lift); }
body.is-playing .wave i { animation: wave 0.9s ease-in-out infinite; }
.wave i:nth-child(2) { animation-delay: -0.3s; }
.wave i:nth-child(3) { animation-delay: -0.6s; }
@keyframes wave { 0%, 100% { height: 30%; } 50% { height: 100%; } }

/* ---------- Hero ---------- */
.hero { position: relative; padding: clamp(22px, 2.6vw, 44px) 0 clamp(26px, 3vw, 48px); }
/* Centred against the headline block. Top-aligning it left a hole beneath —
   the void it was meant to fix was the hero's own excess bottom padding,
   which is now gone. */
.hero-grid { display: grid; grid-template-columns: 1.15fr 0.85fr; gap: clamp(28px, 4vw, 60px); align-items: center; }

.display {
	margin: 0 0 16px; color: var(--ink);
	font-family: var(--font-display);
	font-size: clamp(40px, 5.8vw, 72px); line-height: 1.08;
	font-weight: var(--w-display); letter-spacing: -0.5px;
	text-wrap: balance;
}
.display-sub {
	margin: -10px 0 22px; font-size: clamp(19px, 2.2vw, 25px);
	color: var(--accent-lift); font-weight: 600; letter-spacing: -0.3px;
}
.hero-info {
	margin: 0 0 26px; max-width: 54ch; font-size: 18px;
	color: var(--text); white-space: pre-line;
}

.cta-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.btn-listen {
	display: inline-flex; align-items: center; gap: 12px;
	background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 50%, #0ea5e9));
	color: #fff; border: none; font: inherit; font-weight: 700; font-size: 16.5px;
	padding: 15px 28px 15px 15px; border-radius: 999px; cursor: pointer;
	box-shadow: 0 12px 38px var(--accent-glow);
	transition: transform 0.2s var(--ease), box-shadow 0.2s;
}
.btn-listen:hover { transform: translateY(-2px); box-shadow: 0 18px 50px var(--accent-glow); }
.btn-listen:active { transform: translateY(0); }
.btn-icon {
	display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%;
	background: rgba(255, 255, 255, 0.2);
}
.btn-icon svg { grid-area: 1 / 1; }
.btn-listen .ic-stop { display: none; }
body.is-playing .btn-listen .ic-play { display: none; }
body.is-playing .btn-listen .ic-stop { display: block; }

.btn-ghost {
	display: inline-flex; align-items: center; padding: 14px 22px; border-radius: 999px;
	border: 1px solid var(--line); color: var(--ink); text-decoration: none;
	font-weight: 600; font-size: 15.5px; transition: background 0.2s, border-color 0.2s;
}
.btn-ghost:hover { background: var(--card); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }

/* ---------- On-air panel ---------- */
.onair {
	/* Sized to its content, not to the column. During a live broadcast the
	   progress row and "up next" are both hidden, so left to fill the column
	   the card stretches wide with nothing in the right of it. */
	max-width: 460px; margin-left: auto; width: 100%;
	background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
	padding: 22px;
	backdrop-filter: blur(22px) saturate(170%);
	-webkit-backdrop-filter: blur(22px) saturate(170%);
	box-shadow: 0 40px 90px rgba(0, 0, 0, 0.55);
	will-change: transform; transition: transform 0.25s var(--ease), box-shadow 0.3s;
}
.onair-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 18px; }
.onair-badge {
	display: inline-flex; align-items: center; gap: 8px;
	font-size: 11.5px; font-weight: 800; letter-spacing: 1.6px; text-transform: uppercase;
	color: var(--ink); background: rgba(255, 77, 94, 0.12);
	border: 1px solid rgba(255, 77, 94, 0.35); padding: 5px 12px; border-radius: 999px;
}
.dot {
	width: 7px; height: 7px; border-radius: 50%; background: #ff4d5e; flex: none;
	animation: blip 2s ease-out infinite;
}
@keyframes blip {
	0% { box-shadow: 0 0 0 0 rgba(255, 77, 94, 0.6); }
	70% { box-shadow: 0 0 0 8px rgba(255, 77, 94, 0); }
	100% { box-shadow: 0 0 0 0 rgba(255, 77, 94, 0); }
}
.onair-show { font-size: 13px; color: var(--muted); font-weight: 600; text-align: right; }

.onair-body { display: flex; align-items: center; gap: 18px; }
.onair-meta { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.np-kicker { font-size: 10.5px; font-weight: 800; letter-spacing: 1.8px; text-transform: uppercase; color: var(--accent-lift); }
.np-title {
	font-size: 20px; font-weight: 800; color: var(--ink); line-height: 1.28; letter-spacing: -0.4px;
	display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.np-artist {
	font-size: 14.5px; color: var(--muted);
	white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Artwork tile — real art when the track has it, typographic tile when it doesn't */
.art {
	position: relative; flex: none; border-radius: 14px; overflow: hidden;
	background:
		linear-gradient(150deg,
			color-mix(in srgb, var(--accent) 55%, #000),
			color-mix(in srgb, var(--accent) 18%, var(--bg-lift)));
	display: grid; place-items: center;
	box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.art img { grid-area: 1 / 1; width: 100%; height: 100%; object-fit: cover; display: block; }
/* Logos and marks are padded rather than cropped. */
.art-contain img { object-fit: contain; padding: 14%; }
.art-lg { width: 116px; height: 116px; }
.art-sm { width: 46px; height: 46px; border-radius: 12px; }

/* Equaliser overlay on the artwork */
.eq {
	grid-area: 1 / 1; align-self: end; justify-self: start;
	display: flex; align-items: flex-end; gap: 3px; height: 20px;
	margin: 0 0 9px 9px; opacity: 0; transition: opacity 0.35s;
}
.eq.on { opacity: 1; }
.eq i {
	width: 3.5px; height: 25%; border-radius: 2px; background: #fff;
	box-shadow: 0 1px 6px rgba(0, 0, 0, 0.8);
}
.eq.on i { animation: eq 1.1s ease-in-out infinite; }
.eq i:nth-child(2) { animation-delay: -0.35s; }
.eq i:nth-child(3) { animation-delay: -0.7s; }
.eq i:nth-child(4) { animation-delay: -0.15s; }
@keyframes eq { 0%, 100% { height: 22%; } 50% { height: 100%; } }

.progress { margin-top: 20px; display: flex; align-items: center; gap: 12px; }
.progress::before {
	content: ""; flex: 1; height: 3px; border-radius: 3px;
	background: rgba(255, 255, 255, 0.1); position: relative;
}
.progress { position: relative; }
.progress-bar {
	position: absolute; left: 0; top: 50%; transform: translateY(-50%);
	height: 3px; border-radius: 3px; width: 0;
	background: linear-gradient(90deg, var(--accent), var(--accent-lift));
	transition: width 1s linear;
}
.progress-time { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; flex: none; }

.upnext {
	margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line);
	display: flex; align-items: baseline; gap: 10px; min-width: 0;
}
.upnext-label { font-size: 10.5px; font-weight: 800; letter-spacing: 1.6px; text-transform: uppercase; color: var(--muted); flex: none; }
.upnext-title { font-size: 14px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---------- Visualiser ---------- */
/* Only present while the stream is playing, and quiet about it. It is not a
   reading of the audio — the stream sends no CORS headers, so it cannot be —
   so it behaves as an indicator that sound is live rather than as decoration
   competing with the station's own photograph. */
.visualiser {
	position: absolute; left: 0; right: 0; bottom: 0; width: 100%; height: 44px;
	display: block; pointer-events: none;
	opacity: 0; transition: opacity 0.6s ease;
	-webkit-mask-image: linear-gradient(to bottom, transparent, #000 70%);
	mask-image: linear-gradient(to bottom, transparent, #000 70%);
}
body.is-playing .visualiser { opacity: 0.5; }

/* ---------- Sections ---------- */
.section { padding: clamp(56px, 8vw, 96px) 0; }
.section + .section { border-top: 1px solid var(--line); }
.section-head {
	display: flex; align-items: baseline; justify-content: space-between;
	gap: 20px; margin-bottom: 32px; flex-wrap: wrap;
}
.section-head h2 {
	margin: 0; font-size: clamp(26px, 3.4vw, 38px); font-weight: 800;
	letter-spacing: -1px; color: var(--ink);
}
.section-note { font-size: 14px; color: var(--muted); }

/* ---------- Recently played ---------- */
.track-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.track-row {
	display: flex; align-items: center; gap: 16px;
	padding: 12px 18px 12px 12px; border-radius: 16px;
	background: var(--card); border: 1px solid var(--line);
	transition: background 0.2s, border-color 0.2s, transform 0.2s var(--ease);
	animation: row-in 0.5s var(--ease) both;
}
.track-row:hover {
	background: rgba(255, 255, 255, 0.06);
	border-color: color-mix(in srgb, var(--accent) 35%, transparent);
	transform: translateX(4px);
}
@keyframes row-in { from { opacity: 0; transform: translateY(10px); } }
.track-main { min-width: 0; flex: 1; display: flex; flex-direction: column; }
.track-title {
	font-weight: 700; color: var(--ink); font-size: 15.5px;
	white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.track-artist {
	font-size: 13.5px; color: var(--muted);
	white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.track-time { font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; flex: none; }

/* ---------- Media frames ---------- */
.frame-wrap {
	border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line);
	background: #000; box-shadow: 0 40px 100px rgba(0, 0, 0, 0.6);
}
.frame-wrap iframe { display: block; width: 100%; border: 0; }
.video-wrap { aspect-ratio: 16 / 9; }
.video-wrap iframe, .video-wrap video { width: 100%; height: 100%; display: block; background: #000; }
.chat-wrap { height: 620px; background: var(--bg-lift); }
.chat-wrap iframe { height: 100%; }

/* ---------- About ---------- */
.about-grid { display: grid; grid-template-columns: 280px 1fr; gap: clamp(24px, 5vw, 64px); align-items: start; }
.about-grid h2 { margin: 0; font-size: clamp(26px, 3.4vw, 38px); font-weight: 800; letter-spacing: -1px; color: var(--ink); }
.about-text { white-space: pre-line; font-size: 17.5px; max-width: 68ch; }

/* ---------- Footer ---------- */
.site-footer { border-top: 1px solid var(--line); padding: 36px 0; }
.footer-inner { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.footer-name { font-weight: 800; color: var(--ink); }
.footer-note { color: var(--muted); font-size: 14px; }

/* ---------- Player dock ---------- */
.dock {
	position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%);
	z-index: 70; width: min(700px, calc(100vw - 32px));
	display: flex; align-items: center; gap: 15px;
	/* Same glass as the header and the on-air card: the blur is the
	   material, the film is a whisper. */
	background: linear-gradient(180deg,
		color-mix(in srgb, var(--bg-lift) 74%, transparent) 0%,
		color-mix(in srgb, var(--bg-lift) 58%, transparent) 100%);
	backdrop-filter: blur(44px) saturate(190%); -webkit-backdrop-filter: blur(44px) saturate(190%);
	border: 1px solid rgba(255, 255, 255, 0.12); border-radius: 999px;
	padding: 10px 16px 10px 10px;
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 28px 70px rgba(0, 0, 0, 0.7);
	/* Entrance lives on .arriving alone — an animation here would replay
	   the moment that class comes off. will-change keeps the bar on its
	   compositing layer permanently, so the glass renders identically
	   during the entrance and after it settles (no late "dimming" shift
	   when the animation's layer would otherwise be dropped). */
	will-change: transform;
	transition: box-shadow 0.4s ease;
}
/* The stretch clips the bar, and clipping swallows the outer glow — so the
   glow is held back during the entrance and blooms in as it lands, instead
   of snapping on when the clip is released. */
.dock.arriving { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08); }
.dock-meta { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.dock-meta .np-title { font-size: 14.5px; -webkit-line-clamp: 1; letter-spacing: 0; }
.dock-meta .np-artist { font-size: 12.5px; }
.eq-dock { position: static; margin: 0; height: 17px; opacity: 1; }
.eq-dock i { background: var(--accent-lift); box-shadow: none; }
.dock input[type="range"] { width: 88px; accent-color: var(--accent-lift); }
.dock-stop {
	flex: none; width: 40px; height: 40px; border-radius: 50%; border: none; cursor: pointer;
	color: #fff; background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 50%, #0ea5e9));
	display: grid; place-items: center; transition: transform 0.2s var(--ease);
}
.dock-stop:hover { transform: scale(1.09); }

/* ---------- Landing ---------- */
.hero-landing { padding: clamp(28px, 4vw, 56px) 0 clamp(24px, 3.5vw, 40px); }
.station-grid {
	display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
	gap: 22px; padding-bottom: 96px;
}
.station-card {
	display: flex; flex-direction: column; text-decoration: none; color: inherit;
	background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
	overflow: hidden; transition: transform 0.25s var(--ease), box-shadow 0.25s, border-color 0.25s;
}
.station-card:hover {
	transform: translateY(-5px);
	border-color: color-mix(in srgb, var(--accent) 50%, transparent);
	box-shadow: 0 26px 60px rgba(0, 0, 0, 0.55);
}
.station-card img { width: 100%; height: 140px; object-fit: contain; padding: 24px; background: rgba(255, 255, 255, 0.03); }
.station-card-placeholder {
	height: 140px; display: grid; place-items: center; padding: 24px; text-align: center;
	background: linear-gradient(150deg, color-mix(in srgb, var(--accent) 40%, #000), var(--bg-lift));
	font-weight: 800; font-size: 20px; color: var(--ink);
}
.station-card-body { padding: 18px 22px 22px; }
.station-card-name { font-weight: 800; color: var(--ink); font-size: 17px; letter-spacing: -0.3px; }
.station-card-tagline { font-size: 14px; color: var(--muted); margin-top: 3px; }

/* ---------- Error ---------- */
.error-page { padding: 140px 0; text-align: center; }
.error-page h1 { font-size: 92px; margin: 0; color: var(--ink); letter-spacing: -4px; font-weight: 800; }
.error-page p { color: var(--muted); font-size: 18px; }
.btn {
	display: inline-block; padding: 13px 28px; border-radius: 999px;
	background: var(--accent); color: #fff; text-decoration: none; font-weight: 700;
}
.muted { color: var(--muted); }

/* ---------- Header extras ---------- */
.skip-link {
	position: absolute; left: -9999px; top: 8px; z-index: 100;
	background: var(--accent); color: #fff; padding: 10px 16px; border-radius: 8px;
}
.skip-link:focus { left: 16px; }
.brand { gap: 13px; }
.site-nav a.active { color: var(--ink); }
.site-nav a.active::after { transform: scaleX(1); }
.nav-burger {
	display: none; flex-direction: column; gap: 5px; background: none; border: 0;
	padding: 10px; cursor: pointer;
}
.nav-burger span { width: 20px; height: 2px; background: var(--ink); border-radius: 2px; }
body.is-loading .site-header { opacity: 0.7; }
#app { min-height: 50vh; }

/* ---------- Page headers ---------- */
.page-head { padding: clamp(20px, 3vw, 40px) 0 clamp(12px, 1.6vw, 18px); }
.page-title {
	margin: 0 0 8px; font-size: clamp(32px, 4.6vw, 52px); font-weight: 800;
	letter-spacing: -1.5px; color: var(--ink);
}
.page-sub { margin: 0; color: var(--muted); font-size: 17px; }
.frame-note { margin: 18px 0 96px; color: var(--muted); font-size: 14px; }

/* ---------- Home layout ---------- */
.home-grid {
	display: grid; grid-template-columns: 1.3fr 0.7fr; gap: 24px;
	/* Clear of the hero band. This was zero while the hero carried a large
	   bottom padding; now that the hero ends on a defined edge the cards need
	   their own separation, or they sit against the photograph. */
	padding: clamp(24px, 2.8vw, 38px) 0 clamp(40px, 5vw, 64px); align-items: start;
}
.home-side { display: grid; gap: 24px; }
.panel {
	background: var(--card); border: 1px solid var(--line);
	border-radius: var(--radius); padding: 24px;
}
.panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; margin-bottom: 18px; }
.panel-head h2 { margin: 0; font-size: 19px; font-weight: 800; color: var(--ink); letter-spacing: -0.4px; }
.panel-link {
	display: inline-block; margin-top: 16px; color: var(--accent-lift);
	text-decoration: none; font-weight: 700; font-size: 14.5px;
}
.panel-link:hover { text-decoration: underline; }

.upnext-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
.upnext-list li { display: flex; gap: 14px; align-items: baseline; }
.upnext-time {
	font-variant-numeric: tabular-nums; font-weight: 800; color: var(--accent-lift);
	font-size: 14.5px; flex: none; min-width: 44px;
}
.upnext-meta { display: flex; flex-direction: column; min-width: 0; }
.upnext-meta b { color: var(--ink); font-size: 15px; font-weight: 700; }
.upnext-meta i { color: var(--muted); font-size: 13px; font-style: normal; }

.panel-links { display: grid; gap: 10px; padding: 12px; }
.tile {
	display: flex; align-items: center; gap: 14px; padding: 14px;
	border-radius: 14px; text-decoration: none; color: inherit;
	transition: background 0.2s, transform 0.2s var(--ease);
}
.tile:hover { background: rgba(255, 255, 255, 0.06); transform: translateX(3px); }
.tile-icon {
	display: grid; place-items: center; width: 42px; height: 42px; flex: none;
	border-radius: 12px; background: color-mix(in srgb, var(--accent) 22%, transparent);
	color: var(--accent-lift);
}
.tile-accent .tile-icon { background: rgba(255, 77, 94, 0.15); color: #ff8a95; }
.tile-text { display: flex; flex-direction: column; min-width: 0; }
.tile-text b { color: var(--ink); font-size: 15px; font-weight: 700; }
.tile-text i { color: var(--muted); font-size: 13px; font-style: normal; }

/* ---------- Schedule ---------- */
.day-tabs {
	display: flex; gap: 8px; overflow-x: auto; padding-bottom: 6px;
	margin-bottom: 26px; scrollbar-width: none;
}
.day-tabs::-webkit-scrollbar { display: none; }
.day-tab {
	flex: none; padding: 9px 18px; border-radius: 999px; cursor: pointer;
	border: 1px solid var(--line); background: transparent; color: var(--text);
	font: inherit; font-weight: 600; font-size: 14.5px;
	transition: background 0.2s, color 0.2s, border-color 0.2s;
}
.day-tab:hover { color: var(--ink); background: var(--card); }
.day-tab.active {
	background: var(--accent); border-color: var(--accent); color: #fff;
}
.day-heading { display: none; }
.slot-list { list-style: none; margin: 0; padding: 0 0 96px; display: grid; gap: 12px; }
.slot {
	display: flex; gap: 22px; align-items: flex-start; padding: 20px 22px;
	background: var(--card); border: 1px solid var(--line); border-radius: 16px;
	transition: border-color 0.25s, background 0.25s;
}
.slot.is-live {
	border-color: color-mix(in srgb, var(--accent) 60%, transparent);
	background: color-mix(in srgb, var(--accent) 12%, transparent);
	box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 25%, transparent),
		0 20px 50px rgba(0, 0, 0, 0.4);
}
.slot-time { flex: none; min-width: 74px; display: flex; flex-direction: column; }
.slot-start { font-weight: 800; color: var(--ink); font-size: 19px; font-variant-numeric: tabular-nums; letter-spacing: -0.5px; }
.slot-end { font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }
.slot-body { flex: 1; min-width: 0; }
.slot-name { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-weight: 700; color: var(--ink); font-size: 17px; }
.slot-live {
	display: inline-flex; align-items: center; gap: 7px;
	font-size: 11px; font-weight: 800; letter-spacing: 1.4px; text-transform: uppercase;
	color: #fff; background: rgba(255, 77, 94, 0.2);
	border: 1px solid rgba(255, 77, 94, 0.45); padding: 3px 10px; border-radius: 999px;
}
.slot-presenter { color: var(--accent-lift); font-size: 14.5px; font-weight: 600; margin-top: 2px; }
.slot-desc { margin: 8px 0 0; color: var(--text); font-size: 15px; max-width: 62ch; }
.slot-img { width: 84px; height: 84px; object-fit: cover; border-radius: 12px; flex: none; }

/* ---------- Shows ---------- */
.show-grid {
	display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
	gap: 22px; padding-bottom: 96px;
}
.show-card {
	background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
	overflow: hidden; transition: transform 0.25s var(--ease), border-color 0.25s, box-shadow 0.25s;
}
.show-card:hover {
	transform: translateY(-4px);
	border-color: color-mix(in srgb, var(--accent) 45%, transparent);
	box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
}
.show-media {
	height: 168px; display: grid; place-items: center; overflow: hidden;
	background: linear-gradient(150deg, color-mix(in srgb, var(--accent) 45%, #000), var(--bg-lift));
}
.show-media img { width: 100%; height: 100%; object-fit: cover; }
.show-initial { font-size: 56px; font-weight: 800; color: rgba(255, 255, 255, 0.85); }
.show-body { padding: 20px 22px 24px; }
.show-name { margin: 0; font-size: 18px; font-weight: 800; color: var(--ink); letter-spacing: -0.3px; }
.show-presenter { color: var(--accent-lift); font-weight: 600; font-size: 14.5px; margin-top: 2px; }
.show-when { color: var(--muted); font-size: 13.5px; margin-top: 6px; font-variant-numeric: tabular-nums; }
.show-desc { margin: 12px 0 0; font-size: 14.5px; color: var(--text); }

/* ---------- About & contact ---------- */
.about-layout {
	display: grid; grid-template-columns: 1.4fr 0.6fr; gap: clamp(28px, 5vw, 56px);
	padding-bottom: 96px; align-items: start;
}
.contact-card {
	background: var(--card); border: 1px solid var(--line);
	border-radius: var(--radius); padding: 26px;
}
.contact-card h2 { margin: 0 0 18px; font-size: 19px; font-weight: 800; color: var(--ink); }
.contact-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; }
.contact-list li { display: flex; flex-direction: column; gap: 2px; }
.contact-label { font-size: 11px; font-weight: 800; letter-spacing: 1.4px; text-transform: uppercase; color: var(--muted); }
.contact-list a { color: var(--ink); text-decoration: none; font-weight: 600; }
.contact-list a:hover { color: var(--accent-lift); }
.contact-value { color: var(--ink); }
.contact-social { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 22px; }
.contact-social a {
	padding: 7px 14px; border-radius: 999px; border: 1px solid var(--line);
	color: var(--text); text-decoration: none; font-size: 13.5px; font-weight: 600;
}
.contact-social a:hover { color: var(--ink); border-color: color-mix(in srgb, var(--accent) 50%, transparent); }
.btn-donate {
	display: block; margin-top: 22px; text-align: center; padding: 13px 20px;
	border-radius: 999px; background: var(--accent); color: #fff;
	text-decoration: none; font-weight: 700;
}
.btn-donate:hover { background: color-mix(in srgb, var(--accent) 82%, #fff); }

/* ---------- Footer ---------- */
.footer-grid {
	display: flex; align-items: center; justify-content: space-between;
	gap: 20px; flex-wrap: wrap;
}
.footer-social { display: flex; gap: 16px; flex-wrap: wrap; }
.footer-social a { color: var(--text); text-decoration: none; font-size: 14px; font-weight: 600; }
.footer-social a:hover { color: var(--accent-lift); }

/* ---------- Reveal ----------
   Opt-in only, and never on primary content: an element that starts at
   opacity 0 is invisible if the animation never runs. */
.reveal { opacity: 0; transform: translateY(24px); transition: opacity 0.8s var(--ease), transform 0.8s var(--ease); }
.reveal.in { opacity: 1; transform: none; }
.no-js .reveal { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
	html { scroll-behavior: auto; }
	*, *::before, *::after { animation: none !important; transition-duration: 0.01ms !important; }
	.reveal { opacity: 1; transform: none; }
}

/* ---------- Responsive ---------- */
@media (max-width: 940px) {
	.hero-grid { grid-template-columns: 1fr; }
	.about-grid { grid-template-columns: 1fr; gap: 16px; }
	.onair { max-width: 520px; }
}
@media (max-width: 940px) {
	.home-grid { grid-template-columns: 1fr; }
	.about-layout { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
	.container { padding: 0 20px; }
	.header-inner { gap: 14px; min-height: 64px; }
	.nav-burger { display: flex; }
	.site-nav {
		position: fixed; inset: 64px 0 auto; flex-direction: column; align-items: stretch;
		background: color-mix(in srgb, var(--bg-lift) 97%, transparent);
		border-bottom: 1px solid var(--line); padding: 12px 20px 20px; gap: 2px;
		transform: translateY(-8px); opacity: 0; pointer-events: none;
		transition: opacity 0.2s, transform 0.2s;
	}
	body.menu-open .site-nav { opacity: 1; transform: none; pointer-events: auto; }
	.site-nav a { padding: 13px 4px; font-size: 16px; }
	.site-nav a::after { display: none; }
	.slot { flex-direction: column; gap: 10px; }
	.slot-time { flex-direction: row; gap: 10px; align-items: baseline; }
	.slot-img { width: 100%; height: 150px; }
	.brand-logo { height: 36px; }
	.chat-wrap { height: 520px; }
	.dock input[type="range"] { display: none; }
	.art-lg { width: 92px; height: 92px; }
	.art-lg .art-letter { font-size: 36px; }
	.hero-info { font-size: 16.5px; }
}

/* ---------- Official AWR marks ---------- */
.footer-awr { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.awr-mark { height: 34px; width: auto; opacity: 0.85; }
.awr-mark-header { height: 40px; opacity: 1; }
.awr-wordmark { font-weight: 700; color: var(--text); font-size: 14px; }
.brand-fallback { opacity: 0.9; }
.station-card-fallback {
	width: 100%; height: 140px; object-fit: contain;
	padding: 30px; background: rgba(255, 255, 255, 0.03);
}

/* ================= Light theme =================
   Same structure, repainted. Station logos are usually drawn for light
   backgrounds, so this is often the truer look for a station's brand. */
:root[data-theme="light"] {
	--accent-lift: color-mix(in srgb, var(--accent) 88%, #0b2b45);
	--accent-glow: color-mix(in srgb, var(--accent) 26%, transparent);
	--bg: #f6f8fb;
	--bg-lift: #ffffff;
	--ink: #0d1726;
	--text: #46566b;
	--muted: #6d7d92;
	--line: rgba(13, 23, 38, 0.11);
	--card: #ffffff;
}
:root[data-theme="light"] body::before {
	/* The washes give the page its depth — without them light theme is a flat
	   blank sheet next to dark. But they must stay away from the hero's fade
	   line: glows near the top tinted the very colour the photo fades onto,
	   which put a visible seam at the boundary. Anchored at the bottom of the
	   viewport they are near-zero where the fade lands and bloom below it. */
	background:
		radial-gradient(900px 500px at 82% 108%, color-mix(in srgb, var(--accent) 8%, transparent), transparent 62%),
		radial-gradient(700px 420px at 10% 102%, color-mix(in srgb, var(--accent) 5%, transparent), transparent 60%);
}
/* Light glass: paler film, same mechanics; the highlight is stronger because
   glass over a light ground catches more light along its edge. */
:root[data-theme="light"] .site-header {
	background: linear-gradient(180deg,
		color-mix(in srgb, var(--bg) 58%, transparent) 0%,
		color-mix(in srgb, var(--bg) 38%, transparent) 100%);
	backdrop-filter: blur(26px) saturate(160%);
	-webkit-backdrop-filter: blur(26px) saturate(160%);
	border-bottom: 1px solid rgba(13, 23, 38, 0.08);
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.75);
}
:root[data-theme="light"] .onair,
:root[data-theme="light"] .panel,
:root[data-theme="light"] .show-card,
:root[data-theme="light"] .slot,
:root[data-theme="light"] .track-row,
:root[data-theme="light"] .contact-card,
:root[data-theme="light"] .station-card {
	box-shadow: 0 1px 2px rgba(13, 23, 38, 0.04), 0 12px 32px rgba(13, 23, 38, 0.06);
}
/* The on-air card is frosted glass: it relies on being translucent so the
   backdrop blur has something to blur. In light theme the card colour is
   solid white, which leaves the blur doing nothing and the card looking like
   a flat slab — so this one card gets a translucent white of its own. */
:root[data-theme="light"] .onair {
	/* Frosted glass, not a translucent panel. At 72% white the white did the
	   work and the blur had nothing left to show. Less white, a much wider
	   blur and a saturation boost let the colour behind bleed through — that
	   colour bleed is what reads as glass. The bright top edge is the
	   highlight a real pane catches. */
	/* A sheen across the pane rather than one flat veil: brighter at the top
	   left where light would catch it, thinner through the middle. That
	   gradient is what breaks up the colour of whatever sits behind, so a
	   blue photograph no longer tints the whole card blue. Saturation is kept
	   modest here — in dark theme a boost adds depth to a dark backdrop, but
	   over a bright photo it simply amplifies its colour. */
	/* Same principle as dark: the glass is mostly transparent and the blur
	   does the work. At 60-86% white the white did the work instead, which is
	   why it read as a flat panel rather than frosted glass. */
	background: linear-gradient(155deg,
		rgba(255, 255, 255, 0.66) 0%,
		rgba(255, 255, 255, 0.54) 52%,
		rgba(255, 255, 255, 0.60) 100%);
	backdrop-filter: blur(30px) saturate(155%);
	-webkit-backdrop-filter: blur(30px) saturate(155%);
	border-color: rgba(255, 255, 255, 0.85);
	box-shadow:
		inset 0 1px 0 rgba(255, 255, 255, 0.95),
		inset 0 -1px 0 rgba(13, 23, 38, 0.04),
		0 18px 44px rgba(13, 23, 38, 0.13);
}
:root[data-theme="light"] .dock {
	background: linear-gradient(155deg,
		rgba(255, 255, 255, 0.55) 0%,
		rgba(255, 255, 255, 0.38) 100%);
	backdrop-filter: blur(34px) saturate(118%);
	-webkit-backdrop-filter: blur(34px) saturate(118%);
	border-color: rgba(255, 255, 255, 0.85);
	box-shadow:
		inset 0 1px 0 rgba(255, 255, 255, 0.85),
		0 18px 44px rgba(13, 23, 38, 0.18);
}
:root[data-theme="light"] .frame-wrap { box-shadow: 0 20px 60px rgba(13, 23, 38, 0.14); }
:root[data-theme="light"] .track-row:hover,
:root[data-theme="light"] .tile:hover { background: color-mix(in srgb, var(--accent) 6%, #fff); }
:root[data-theme="light"] .art { background: linear-gradient(150deg, color-mix(in srgb, var(--accent) 22%, #fff), #eef2f7); }
:root[data-theme="light"] .show-media { background: linear-gradient(150deg, color-mix(in srgb, var(--accent) 20%, #fff), #eef2f7); }
:root[data-theme="light"] .show-initial { color: color-mix(in srgb, var(--accent) 55%, #fff); }
:root[data-theme="light"] .eq i { background: var(--accent); box-shadow: none; }
:root[data-theme="light"] .slot.is-live { background: color-mix(in srgb, var(--accent) 8%, #fff); }
:root[data-theme="light"] .onair-badge { background: rgba(214, 45, 62, 0.10); border-color: rgba(214, 45, 62, 0.30); color: #b3243a; }
:root[data-theme="light"] .slot-live { background: rgba(214, 45, 62, 0.12); border-color: rgba(214, 45, 62, 0.32); color: #b3243a; }
:root[data-theme="light"] .dot { background: #d62d3e; }
:root[data-theme="light"] .station-card img,
:root[data-theme="light"] .station-card-fallback { background: #f1f4f8; }
:root[data-theme="light"] .nav-burger span { background: var(--ink); }

/* Theme-aware official marks */
.awr-mark-light { display: none; }
:root[data-theme="light"] .awr-mark-dark { display: none; }
:root[data-theme="light"] .awr-mark-light { display: block; }

/* Theme toggle */
.theme-toggle {
	display: grid; place-items: center; width: 38px; height: 38px; flex: none;
	border-radius: 50%; border: 1px solid var(--line); background: transparent;
	color: var(--text); cursor: pointer; transition: color 0.2s, border-color 0.2s, background 0.2s;
}
.theme-toggle:hover { color: var(--ink); background: var(--card); border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.theme-toggle svg { grid-area: 1 / 1; }
.theme-toggle .ic-sun { display: none; }
:root[data-theme="light"] .theme-toggle .ic-sun { display: block; }
:root[data-theme="light"] .theme-toggle .ic-moon { display: none; }

.onair-right { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; min-width: 0; }
.live-source {
	font-size: 10.5px; font-weight: 800; letter-spacing: 1.4px; text-transform: uppercase;
	color: var(--accent-lift);
}

/* ---------- Right-to-left languages ---------- */
[dir="rtl"] .brand { margin-right: 0; margin-left: auto; }
[dir="rtl"] .site-nav a::after { transform-origin: right; }
[dir="rtl"] .track-row:hover, [dir="rtl"] .tile:hover { transform: translateX(-4px); }
[dir="rtl"] .progress-bar { left: auto; right: 0; }
[dir="rtl"] .eq { justify-self: end; margin: 0 0 9px 0; margin-inline-end: 9px; }
[dir="rtl"] .onair-right { align-items: flex-start; }
[dir="rtl"] .onair-show, [dir="rtl"] .live-source { text-align: left; }
[dir="rtl"] .cell-actions { text-align: left; }


/* ================= Schedule: fixed frame, scrolling list =================
   The schedule fills the viewport so the title, timezone note and day tabs
   never scroll away. The footer keeps its place in the column and takes its
   natural height — the LIST is what shrinks to make room, nothing is hidden. */
body.fixed-page {
	height: 100dvh;
	overflow: hidden;
	display: flex;
	flex-direction: column;
	padding-bottom: 0;
}
/* When the player dock is up, border-box sizing shrinks the frame by its
   height so the dock floats clear of the footer instead of over it. */
body.fixed-page.is-playing { padding-bottom: 92px; }

body.fixed-page .site-header { position: static; flex: none; }
body.fixed-page .site-footer { flex: none; padding: 20px 0; }

body.fixed-page #app { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* width:100% is required — as a flex item the container's `margin: 0 auto`
   becomes an auto cross-axis margin, which cancels stretch and shrinks it to
   its content, leaving the list narrower than the page title. */
body.fixed-page #app > .container {
	flex: 1; min-height: 0; width: 100%;
	display: flex; flex-direction: column;
}
body.fixed-page .page-head { flex: none; }
body.fixed-page .day-tabs { flex: none; margin-bottom: 16px; }

/* The only scrolling region on the page. */
body.fixed-page .day-panels {
	flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
	padding: 2px 4px 4px; margin: 0 -4px;
	scrollbar-width: thin;
}
body.fixed-page .slot-list { padding-bottom: 0; }
body.fixed-page .day-panels::-webkit-scrollbar { width: 10px; }
body.fixed-page .day-panels::-webkit-scrollbar-thumb {
	background: color-mix(in srgb, var(--ink) 18%, transparent);
	border: 3px solid transparent; background-clip: content-box; border-radius: 99px;
}
body.fixed-page .day-panels::-webkit-scrollbar-thumb:hover {
	background: color-mix(in srgb, var(--ink) 30%, transparent); background-clip: content-box;
}

/* ================= Station background image =================
   A photograph behind the hero. Legibility is not left to chance: a scrim
   built from the theme's own background colour sits over it, so the same
   image works in light and dark without the station choosing anything. */
.hero-bg {
	position: absolute; inset: 0; z-index: 0;
	background-size: cover; background-position: center;
	background-repeat: no-repeat;
	opacity: var(--bg-strength);
	filter: saturate(1.18) contrast(1.05);
	/* Fades out rather than being covered by a matching colour. Painting the
	   page colour over it never matched, because the page also carries a
	   radial accent wash — invisible in light theme, a clear step in dark.
	   Dissolving lets whatever is behind show through, so nothing has to
	   match. The scrim is inside this element and fades with it. */
	/* The dissolve completes before the hero ends, leaving clean ground
	   between the photograph's tail and the first content — in both themes,
	   so the fade's last wisps never touch the cards below. */
	-webkit-mask-image: linear-gradient(to bottom, #000 72%, transparent 93%);
	mask-image: linear-gradient(to bottom, #000 72%, transparent 93%);
}
/* The grade: hue pulled toward the station's accent, luminance untouched —
   one uploaded photo, art-directed into the brand automatically. */
.hero-bg::before {
	content: ""; position: absolute; inset: 0;
	background: linear-gradient(160deg,
		color-mix(in srgb, var(--accent) 85%, #10131c) 0%,
		color-mix(in srgb, var(--accent) 55%, #232030) 100%);
	mix-blend-mode: color;
	opacity: 0.5;
}
/* How much of the photograph shows through, chosen per station. Three steps
   rather than a free slider: every one of them keeps enough cover for the
   headline to stay legible, which a 0-100% control would not. */
/* The wash is fixed, so legibility never depends on the station's choice.
   Only the image's own opacity is adjustable, and 100% is the original look. */
/* Lightened from 94/78/55 once the light theme's later, gentler photo scrim
   showed how much picture the old numbers were burying. White text tolerates
   a clearer photo; these keep a margin over light's 58/36/14 for insurance. */
:root { --wash-1: 80%; --wash-2: 55%; --wash-3: 24%; --wash-foot: 85%; --bg-strength: 1; }

/* The wash fades into a tint carrying the station's accent, not into flat
   white or black. Fading to the page colour leaves the uncovered part of the
   hero looking empty rather than composed — the tint keeps colour across the
   whole band so it reads as one image. */
:root { --hero-tint: color-mix(in srgb, var(--bg) 91%, var(--accent)); }
:root[data-theme="light"] { --hero-tint: color-mix(in srgb, var(--bg) 88%, var(--accent)); }

.hero-bg::after {
	content: ""; position: absolute; inset: 0;
	/* Dark theme: a near-black wash. It darkens the photograph while leaving
	   it clearly visible, and white text reads over it. */
	background:
		linear-gradient(100deg,
			color-mix(in srgb, var(--hero-tint) var(--wash-1), transparent) 0%,
			color-mix(in srgb, var(--hero-tint) var(--wash-2), transparent) 45%,
			color-mix(in srgb, var(--hero-tint) var(--wash-3), transparent) 100%),
		linear-gradient(to bottom,
			transparent 42%,
			color-mix(in srgb, var(--hero-tint) var(--wash-foot), transparent) 100%);
}

/* Light theme: the same strengths would be a near-solid white sheet and the
   photograph would disappear. Dark text needs the image lifted, not covered,
   so the wash is far weaker and the picture is brightened slightly to sit
   back behind the headline. */
/* Light theme takes a different approach entirely. Station photographs are
   usually dark — a studio, a desk, a night skyline — and lightening a dark
   image to sit behind dark text only ever produces mud. So instead of washing
   it out, the picture is confined to the right of the hero and left crisp,
   while the headline keeps clean background on the left. Image beside the
   content rather than underneath it. */

/* The photograph is darkened and desaturated in light theme so it sits back
   instead of shouting. Dark theme gets this from its scrim; light has a pale
   scrim, which veils the picture without ever quietening it. */

/* Content and visualiser sit above the photograph. */
.hero-photo .hero-grid,
.hero-photo .visualiser { position: relative; z-index: 1; }
/* Behind the photograph, so the masked-away side is tinted rather than bare. */
/* Two layers: the vertical dissolve sits above the horizontal tint, so the
   whole band — photograph and tint together — resolves into the page colour
   rather than stopping at an edge. */
/* A photograph needs height to read. The hero is sized by its content, which
   on a wide screen is short enough that `cover` crops the picture to a sliver,
   so a floor is set — only when there is an image to protect. */
.hero-photo {
	min-height: clamp(360px, 32vw, 540px); display: flex; flex-direction: column; justify-content: center;
	/* The photograph runs under the frosted header: the section is pulled up
	   behind the sticky bar and padded back down by the same amount, so the
	   content sits where it did while the image reaches the top of the page.
	   The bar's translucency and blur do the rest, in both themes. */
	margin-top: -65px;
	padding-top: calc(clamp(22px, 2.6vw, 44px) + 65px);
}
.hero-photo {
	background:
		linear-gradient(180deg,
			var(--hero-tint) 0%,
			color-mix(in srgb, var(--hero-tint) 72%, transparent) 55%,
			transparent 100%);
}
/* The ambient colour wash would muddy a photograph. */

@media (max-width: 720px) {
	/* Portrait screens crop a landscape photo hard; favour the upper third
	   where the subject usually is, and lean on the scrim more heavily. */
	.hero-bg { background-position: center 30%; }
	.hero-bg::after {
		background: linear-gradient(to bottom,
			color-mix(in srgb, var(--hero-tint) 82%, transparent) 0%,
			color-mix(in srgb, var(--hero-tint) 94%, transparent) 100%);
	}

}

/* Light theme. The wash covers the text and nothing else: it is gone by the
   halfway mark, so the photograph is untouched across the whole right side
   where the microphone is. Washing the full width to make the headline legible
   was what made the picture look bleached. */
/* ---- Light theme, dark photograph ----
   Measured as too dark for near-black type (see imagelum.go). Rather than
   bleaching the picture until black text survives, the hero lights its own
   text and the photograph is left at full strength. Scoped to the hero: the
   page below, the header and the theme toggle are all untouched, and a bright
   photograph never reaches these rules. */
:root[data-theme="light"] .hero[data-photo="dark"] {
	/* The white mash: .hero-photo paints a gradient of --hero-tint UNDER the
	   photograph, and light theme's tint is near-white — so wherever the
	   bottom fade thinned the photo, white showed through from beneath. Every
	   attempt to fix the fade from above failed because the white was under
	   it. With a dark photograph the underlay takes dark theme's tint, and
	   the fade behaves exactly as it does there. */
	--hero-tint: color-mix(in srgb, #06070b 91%, var(--accent));
	/* One fade, not two: the underlay stays dark through the photo's own fade
	   zone (76-100%) and thins to TRANSPARENT at the same bottom edge. Ending
	   at a colour was wrong twice over — the page below is not flat --bg, it
	   carries the body's accent washes, so any hard-coded end colour lands
	   beside a page that doesn't match it. Transparent reveals the real page,
	   so the boundary matches by construction. */
	background: linear-gradient(180deg,
		var(--hero-tint) 0%,
		var(--hero-tint) 87%,
		transparent 100%);
	--ink: #f7fafd;
	--text: #ccd8e6;
	--muted: #a3b3c6;
	--card: rgba(255, 255, 255, 0.05);
	--line: rgba(255, 255, 255, 0.15);
	/* The eyebrows ("UŽIVO IZ STUDIJA", "SADA SLUŠATE") are accent-lift, which
	   is a dark navy in light theme — invisible here. Dark theme's lifted
	   accent is the one that belongs on a photograph. */
	--accent-lift: color-mix(in srgb, var(--accent) 62%, #7ad3ff);
}

/* Only enough scrim to keep light text off the photograph's own highlights —
   not enough to dim it. */
:root[data-theme="light"] .hero[data-photo="dark"] .hero-bg::after {
	/* The diagonal wash for the text. Nothing else. */
	background: linear-gradient(100deg,
		rgba(6, 7, 11, 0.58) 0%,
		rgba(6, 7, 11, 0.36) 45%,
		rgba(6, 7, 11, 0.14) 100%);
}

/* The landing strip. The photograph's own bottom is neutral grey, so thinning
   it over the page passes through white-mixed tones that sit beside the blue-
   tinted page — "white, then the background". This layer sits OVER the photo,
   unmasked, and ends at var(--bg) exactly: the hero's bottom edge IS the page
   colour, and the fade blends toward it instead of toward white. */
/* ---------- Light theme's coloured ground (every page) ----------
   One system, so no page looks like a different site: the ground is a
   vertical ramp in the station's own colour, sitting on its own layer above
   whatever the tone paints. It ENDS ON A REAL COLOUR, never on transparent:
   fading to transparent blends toward transparent black, which passes
   through a pale washed-out zone on the way — that pale zone was the white
   line, every time.

   The home page starts the ramp dark, because it has a photograph to hand
   over from — that dark-into-dark handover is the only fade that never
   fogs. Every other page starts at the ramp's light end: same colour, same
   system, no seam to hide. --lsc (Colour strength) drives how much accent
   survives, so the station's colour controls reach it like every other
   surface. */
/* The ramp lives on body's OWN background, not on a pseudo-element: the
   tone paints ::before and the animated glow paints ::after, so a ramp on
   either fights a rule it cannot win — that collision is why the ramp
   stayed pinned to the viewport and appeared to slide down the page while
   scrolling. body's background is free in light theme, and it scrolls with
   the document by nature. */
:root[data-theme="light"] body {
	background-color: var(--bg);
	background-image: linear-gradient(180deg,
		color-mix(in srgb, color-mix(in srgb, var(--accent) 26%, #c3ccdb) var(--lsc, 100%), #d6dce6) 0,
		var(--bg) 620px);
	background-repeat: no-repeat;
	background-size: 100% 620px;
	background-attachment: scroll;
}
:root[data-theme="light"] body:has(.hero[data-photo="dark"]) {
	/* The dark stage is contained to the photograph's own zone: it holds
	   through the hero, hands over across the seam, and by the time content
	   starts it is the same light-tinted ground the other pages carry. */
	background-image: linear-gradient(180deg,
		color-mix(in srgb, color-mix(in srgb, var(--accent) 62%, #0a0f18) var(--lsc, 100%), #141a24) 0,
		color-mix(in srgb, color-mix(in srgb, var(--accent) 62%, #0a0f18) var(--lsc, 100%), #141a24) 380px,
		color-mix(in srgb, color-mix(in srgb, var(--accent) 26%, #c3ccdb) var(--lsc, 100%), #d6dce6) 560px,
		var(--bg) 900px);
	background-size: 100% 900px;
}

/* The card and the outlined button take dark glass here, the same way they do
   in dark theme — they are sitting on the same kind of backdrop. */
/* Dark theme's card is 3.5% white because the page behind it is reliably dark.
   Here the backdrop is a photograph, and only the text region on the left was
   measured — the card sits on the right, where the picture may be bright. So it
   carries its own darkness instead of borrowing it, exactly as the light card
   carries its own white. */
:root[data-theme="light"] .hero[data-photo="dark"] .onair {
	background: linear-gradient(155deg,
		rgba(8, 14, 26, 0.54) 0%,
		rgba(8, 14, 26, 0.40) 52%,
		rgba(8, 14, 26, 0.48) 100%);
	border-color: rgba(255, 255, 255, 0.20);
	backdrop-filter: blur(26px) saturate(150%);
	-webkit-backdrop-filter: blur(26px) saturate(150%);
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 24px 56px rgba(0, 0, 0, 0.34);
}
:root[data-theme="light"] .hero[data-photo="dark"] .onair-badge {
	background: rgba(255, 77, 94, 0.14);
	border-color: rgba(255, 77, 94, 0.36);
	color: #ff929f;
}
:root[data-theme="light"] .hero[data-photo="dark"] .btn-ghost {
	background: rgba(8, 14, 26, 0.34);
	border-color: rgba(255, 255, 255, 0.24);
	backdrop-filter: blur(16px) saturate(140%);
	-webkit-backdrop-filter: blur(16px) saturate(140%);
}
:root[data-theme="light"] .hero[data-photo="dark"] .btn-ghost:hover {
	background: rgba(255, 255, 255, 0.16);
	border-color: rgba(255, 255, 255, 0.4);
}

/* The hero's secondary text sits on the photograph, not on the page, so the
   page's mid-greys disappear into it — the headline survived only because it is
   near-black. Darkened to match, inside the hero and nowhere else. */
:root[data-theme="light"] .hero {
	--text: #16283e;
	--muted: #2b3d52;
}

/* The outlined button is a hairline with no fill — fine on a flat page, but on
   the photograph there is nothing to see. It takes the same glass as the on-air
   card so it still reads as the quieter of the two buttons. */
:root[data-theme="light"] .hero .btn-ghost {
	background: rgba(255, 255, 255, 0.60);
	border-color: rgba(13, 23, 38, 0.18);
	backdrop-filter: blur(16px) saturate(140%);
	-webkit-backdrop-filter: blur(16px) saturate(140%);
}
:root[data-theme="light"] .hero .btn-ghost:hover {
	background: rgba(255, 255, 255, 0.82);
	border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

:root[data-theme="light"] .hero-bg::after {
	/* Strengths come from the photograph itself — see imagelum.go. The values
	   here are the fallback for when it cannot be measured, and they sit at the
	   heavy end deliberately: failing towards readable text is the safe way to
	   fail, failing towards a clear picture is not. */
	background: linear-gradient(100deg,
		color-mix(in srgb, var(--hero-tint) var(--w1, 93%), transparent) 0%,
		color-mix(in srgb, var(--hero-tint) var(--w2, 88%), transparent) 40%,
		color-mix(in srgb, var(--hero-tint) var(--w3, 42%), transparent) 56%,
		transparent 70%);
}

/* ================= Page glow presets =================
   data-glow on <html>, chosen per station in Appearance. "soft" is the
   original look and needs no rules — the base body::before IS soft. Each
   preset defines its dark form and its light form; light keeps its glows
   away from mid-viewport, where the home hero fades onto the page. */
:root[data-glow="vivid"] body::before {
	background:
		radial-gradient(1000px 560px at 78% -8%, color-mix(in srgb, var(--accent) 42%, transparent), transparent 64%),
		radial-gradient(760px 460px at 6% 14%, color-mix(in srgb, var(--accent) 24%, transparent), transparent 62%),
		radial-gradient(900px 420px at 55% 108%, color-mix(in srgb, var(--accent) 18%, transparent), transparent 62%),
		linear-gradient(180deg, transparent 55%, var(--bg) 100%);
}
:root[data-glow="vivid"][data-theme="light"] body::before {
	background:
		radial-gradient(900px 500px at 82% 108%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 62%),
		radial-gradient(700px 420px at 10% 102%, color-mix(in srgb, var(--accent) 10%, transparent), transparent 60%);
}
:root[data-glow="horizon"] body::before {
	background: linear-gradient(180deg,
		color-mix(in srgb, var(--accent) 30%, transparent) 0%,
		color-mix(in srgb, var(--accent) 10%, transparent) 26%,
		transparent 46%);
}
:root[data-glow="horizon"][data-theme="light"] body::before {
	background: linear-gradient(180deg,
		color-mix(in srgb, var(--accent) 16%, transparent) 0%,
		color-mix(in srgb, var(--accent) 5%, transparent) 24%,
		transparent 42%);
}
:root[data-glow="plain"] body::before,
:root[data-glow="plain"][data-theme="light"] body::before {
	background: none;
}

/* ================= Chat: the floating companion =================
   Bubble bottom-right on every page; the open panel replaces it. Both sit
   above the audio dock when it is showing. On Watch and Chat pages the page
   itself carries the chat, so scripting hides the pair there. */
.chat-fab {
	position: fixed; right: 18px; bottom: 18px; z-index: 56;
	width: 54px; height: 54px; border-radius: 50%; border: none; cursor: pointer;
	display: grid; place-items: center;
	background: var(--accent); color: #fff;
	box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
	transition: transform 0.15s, box-shadow 0.15s;
}
.chat-fab:hover { transform: translateY(-2px); box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4); }
.chat-panel {
	position: fixed; right: 18px; bottom: 18px; z-index: 57;
	width: min(380px, calc(100vw - 24px)); height: min(600px, 74vh);
	display: flex; flex-direction: column;
	border: 1px solid var(--line); border-radius: 16px; overflow: hidden;
	/* The same frosted glass as the player bar: a translucent film over a
	   heavy backdrop blur, so the page glows softly through the panel.
	   Whatever parts of the embedded chat are translucent sit on this
	   glass; its opaque parts paint themselves. */
	background: linear-gradient(180deg,
		color-mix(in srgb, var(--bg-lift) 74%, transparent) 0%,
		color-mix(in srgb, var(--bg-lift) 58%, transparent) 100%);
	backdrop-filter: blur(44px) saturate(190%);
	-webkit-backdrop-filter: blur(44px) saturate(190%);
	box-shadow: 0 24px 64px rgba(0, 0, 0, 0.45);
}
.chat-panel iframe { background: transparent; }
.chat-panel-head {
	display: flex; align-items: center; justify-content: space-between;
	padding: 10px 10px 10px 16px; font-weight: 700; font-size: 14px; color: var(--ink);
	border-bottom: 1px solid var(--line);
}
.chat-panel-close {
	border: none; background: none; color: var(--muted); cursor: pointer;
	font-size: 15px; padding: 4px 10px; border-radius: 8px;
}
.chat-panel-close:hover { background: var(--card); color: var(--ink); }
.chat-panel iframe { flex: 1; width: 100%; border: 0; }

/* Clear the audio dock when it is up. */
.dock:not([hidden]) ~ .chat-fab { bottom: 88px; }
.dock:not([hidden]) ~ .chat-panel { bottom: 88px; }

@media (max-width: 720px) {
	.chat-panel { inset: 0; width: auto; height: auto; border-radius: 0; }
	.dock:not([hidden]) ~ .chat-panel { inset: 0 0 72px 0; }
}

/* ================= Page tone (dark theme's ground) =================
   The glow presets decide where the accent sits; the tone decides what the
   ground beneath is. Dark theme only — light's near-white ground and its
   text contrast were settled at cost, and stay settled. Guarded with
   :not([data-theme="light"]) so a tone can never leak into light theme. */
:root[data-tone="deep"]:not([data-theme="light"]) {
	--bg: color-mix(in srgb, #05060b 80%, var(--accent));
	--bg-lift: color-mix(in srgb, #0b101b 78%, var(--accent));
}
:root[data-tone="gradient"]:not([data-theme="light"]) body {
	background:
		linear-gradient(180deg,
			color-mix(in srgb, var(--accent) 30%, #04050a) 0%,
			color-mix(in srgb, var(--accent) 10%, #04050a) 34%,
			var(--bg) 68%) fixed,
		var(--bg);
}

/* Curated grounds. Colour IS the ground here — not black with a glow. The
   station's colour controls drive these too, same meanings as in light:
   --lx1/--lx2 position the blooms, --lspf scales them, --ltc fades the top
   bloom, --lsc dims all bloom colour. The base gradient stays put, so zeroing
   everything leaves a moody ground rather than a void. Dark theme only. */
:root[data-tone="dusk"]:not([data-theme="light"]) body {
	background:
		radial-gradient(calc(max(1300px, 85vw) * var(--lspf, 1)) calc(max(800px, 60vh) * var(--lspf, 1)) at var(--lx1, 15%) -10%,
			color-mix(in srgb, color-mix(in srgb, rgba(58, 92, 190, 0.75) var(--ltc, 100%), transparent) var(--lsc, 100%), transparent), transparent 74%),
		radial-gradient(calc(max(1200px, 85vw) * var(--lspf, 1)) calc(max(760px, 60vh) * var(--lspf, 1)) at var(--lx2, 90%) 99%,
			color-mix(in srgb, rgba(196, 52, 128, 0.6) var(--lsc, 100%), transparent), transparent 76%),
		linear-gradient(150deg,
			color-mix(in srgb, #131b3c var(--lsc, 100%), #090a12) 0%,
			color-mix(in srgb, #221040 var(--lsc, 100%), #090a12) 55%,
			color-mix(in srgb, #2c0e2e var(--lsc, 100%), #090a12) 100%);
	background-attachment: fixed;
}
:root[data-tone="aurora"]:not([data-theme="light"]) body {
	background:
		radial-gradient(calc(max(1200px, 85vw) * var(--lspf, 1)) calc(max(740px, 60vh) * var(--lspf, 1)) at var(--lx1, 18%) -8%,
			color-mix(in srgb, color-mix(in srgb, rgba(16, 190, 160, 0.55) var(--ltc, 100%), transparent) var(--lsc, 100%), transparent), transparent 72%),
		radial-gradient(calc(max(1300px, 85vw) * var(--lspf, 1)) calc(max(800px, 60vh) * var(--lspf, 1)) at var(--lx2, 85%) 100%,
			color-mix(in srgb, rgba(126, 60, 220, 0.6) var(--lsc, 100%), transparent), transparent 76%),
		linear-gradient(160deg,
			color-mix(in srgb, #07262a var(--lsc, 100%), #090a12) 0%,
			color-mix(in srgb, #101433 var(--lsc, 100%), #090a12) 60%,
			color-mix(in srgb, #200f38 var(--lsc, 100%), #090a12) 100%);
	background-attachment: fixed;
}
:root[data-tone="ocean"]:not([data-theme="light"]) body {
	background:
		radial-gradient(calc(max(1400px, 85vw) * var(--lspf, 1)) calc(max(860px, 60vh) * var(--lspf, 1)) at var(--lx1, 78%) -12%,
			color-mix(in srgb, color-mix(in srgb, rgba(38, 118, 220, 0.65) var(--ltc, 100%), transparent) var(--lsc, 100%), transparent), transparent 74%),
		radial-gradient(calc(max(1000px, 85vw) * var(--lspf, 1)) calc(max(620px, 60vh) * var(--lspf, 1)) at var(--lx2, 6%) 101%,
			color-mix(in srgb, rgba(22, 180, 205, 0.45) var(--lsc, 100%), transparent), transparent 72%),
		linear-gradient(170deg,
			color-mix(in srgb, #0a2447 var(--lsc, 100%), #090a12) 0%,
			color-mix(in srgb, #0a1a38 var(--lsc, 100%), #090a12) 55%,
			color-mix(in srgb, #071228 var(--lsc, 100%), #090a12) 100%);
	background-attachment: fixed;
}
:root[data-tone="ember"]:not([data-theme="light"]) body {
	background:
		radial-gradient(calc(max(1000px, 85vw) * var(--lspf, 1)) calc(max(620px, 60vh) * var(--lspf, 1)) at var(--lx1, 12%) -8%,
			color-mix(in srgb, color-mix(in srgb, rgba(150, 34, 60, 0.6) var(--ltc, 100%), transparent) var(--lsc, 100%), transparent), transparent 70%),
		radial-gradient(calc(max(1200px, 85vw) * var(--lspf, 1)) calc(max(740px, 60vh) * var(--lspf, 1)) at var(--lx2, 82%) 100%,
			color-mix(in srgb, rgba(225, 58, 92, 0.55) var(--lsc, 100%), transparent), transparent 74%),
		linear-gradient(155deg,
			color-mix(in srgb, #2c0a14 var(--lsc, 100%), #090a12) 0%,
			color-mix(in srgb, #200812 var(--lsc, 100%), #090a12) 55%,
			color-mix(in srgb, #160610 var(--lsc, 100%), #090a12) 100%);
	background-attachment: fixed;
}
/* ================= Light theme's version of the tones =================
   Two forms per tone. The default covers the whole page — bloom at the top
   right, bloom at the bottom left, the classic wallpaper diagonal. The home
   page alone keeps colour anchored low, because up there the hero photograph
   fades onto the page and colour at that boundary is the seam war all over
   again. body:has() picks the form live as in-place navigation changes the
   page. Strength comes from the slider via --lwo on the layer's opacity. */

/* -- full-page forms (every page but home) -- */
:root[data-tone="deep"][data-theme="light"] body::before,
:root[data-tone="gradient"][data-theme="light"] body::before {
	background:
		radial-gradient(calc(1100px * var(--lspf, 1)) calc(640px * var(--lspf, 1)) at var(--lx1, 86%) -4%, color-mix(in srgb, color-mix(in srgb, var(--accent) 42%, transparent) var(--ltc, 100%), transparent), transparent 68%),
		radial-gradient(calc(1200px * var(--lspf, 1)) calc(700px * var(--lspf, 1)) at var(--lx2, 8%) 104%, color-mix(in srgb, var(--accent) 30%, transparent), transparent 68%);
}
:root[data-tone="dusk"][data-theme="light"] body::before {
	background:
		radial-gradient(calc(1100px * var(--lspf, 1)) calc(640px * var(--lspf, 1)) at var(--lx1, 14%) -6%, color-mix(in srgb, rgba(64, 96, 200, 0.5) var(--ltc, 100%), transparent), transparent 66%),
		radial-gradient(calc(1200px * var(--lspf, 1)) calc(700px * var(--lspf, 1)) at var(--lx2, 90%) 104%, rgba(200, 66, 138, 0.42), transparent 68%);
}
:root[data-tone="aurora"][data-theme="light"] body::before {
	background:
		radial-gradient(calc(1100px * var(--lspf, 1)) calc(640px * var(--lspf, 1)) at var(--lx1, 16%) -6%, color-mix(in srgb, rgba(18, 170, 144, 0.45) var(--ltc, 100%), transparent), transparent 64%),
		radial-gradient(calc(1200px * var(--lspf, 1)) calc(700px * var(--lspf, 1)) at var(--lx2, 86%) 106%, rgba(126, 70, 210, 0.42), transparent 68%);
}
:root[data-tone="ocean"][data-theme="light"] body::before {
	background:
		radial-gradient(calc(1200px * var(--lspf, 1)) calc(700px * var(--lspf, 1)) at var(--lx1, 84%) -6%, color-mix(in srgb, rgba(38, 112, 212, 0.48) var(--ltc, 100%), transparent), transparent 66%),
		radial-gradient(calc(1100px * var(--lspf, 1)) calc(640px * var(--lspf, 1)) at var(--lx2, 6%) 102%, rgba(24, 168, 196, 0.36), transparent 64%);
}
:root[data-tone="ember"][data-theme="light"] body::before {
	background:
		radial-gradient(calc(1100px * var(--lspf, 1)) calc(640px * var(--lspf, 1)) at var(--lx1, 14%) -6%, color-mix(in srgb, rgba(170, 46, 72, 0.38) var(--ltc, 100%), transparent), transparent 64%),
		radial-gradient(calc(1100px * var(--lspf, 1)) calc(640px * var(--lspf, 1)) at var(--lx2, 84%) 104%, rgba(212, 62, 96, 0.42), transparent 66%);
}
:root[data-tone="custom"][data-theme="light"] body::before {
	background:
		radial-gradient(calc(1100px * var(--lspf, 1)) calc(640px * var(--lspf, 1)) at var(--lx1, 14%) -6%,
			color-mix(in srgb, color-mix(in srgb, color-mix(in srgb, var(--tone-a, #3a5cbe) 65%, #26314a) 55%, transparent) var(--ltc, 100%), transparent), transparent 66%),
		radial-gradient(calc(1200px * var(--lspf, 1)) calc(700px * var(--lspf, 1)) at var(--lx2, 88%) 104%,
			color-mix(in srgb, color-mix(in srgb, var(--tone-b, #c43480) 65%, #4a2638) 48%, transparent), transparent 68%);
}

/* -- home page: colour stays low, clear of the hero fade -- */
:root[data-tone="deep"][data-theme="light"] body:has(#app[data-page=""])::before,
:root[data-tone="gradient"][data-theme="light"] body:has(#app[data-page=""])::before {
	background:
		radial-gradient(calc(1200px * var(--lspf, 1)) calc(700px * var(--lspf, 1)) at 80% 102%, color-mix(in srgb, var(--accent) 48%, transparent), transparent 70%),
		radial-gradient(calc(1000px * var(--lspf, 1)) calc(600px * var(--lspf, 1)) at 6% 100%, color-mix(in srgb, var(--accent) 30%, transparent), transparent 66%);
}
:root[data-tone="dusk"][data-theme="light"] body:has(#app[data-page=""])::before {
	background:
		radial-gradient(calc(1200px * var(--lspf, 1)) calc(700px * var(--lspf, 1)) at 12% 102%, rgba(64, 96, 200, 0.55), transparent 68%),
		radial-gradient(calc(1100px * var(--lspf, 1)) calc(640px * var(--lspf, 1)) at 88% 100%, rgba(200, 66, 138, 0.45), transparent 68%);
}
:root[data-tone="aurora"][data-theme="light"] body:has(#app[data-page=""])::before {
	background:
		radial-gradient(calc(1200px * var(--lspf, 1)) calc(700px * var(--lspf, 1)) at 14% 102%, rgba(18, 170, 144, 0.48), transparent 66%),
		radial-gradient(calc(1150px * var(--lspf, 1)) calc(660px * var(--lspf, 1)) at 86% 100%, rgba(126, 70, 210, 0.45), transparent 68%);
}
:root[data-tone="ocean"][data-theme="light"] body:has(#app[data-page=""])::before {
	background:
		radial-gradient(calc(1300px * var(--lspf, 1)) calc(760px * var(--lspf, 1)) at 78% 102%, rgba(38, 112, 212, 0.52), transparent 68%),
		radial-gradient(calc(1000px * var(--lspf, 1)) calc(600px * var(--lspf, 1)) at 8% 100%, rgba(24, 168, 196, 0.38), transparent 64%);
}
:root[data-tone="ember"][data-theme="light"] body:has(#app[data-page=""])::before {
	background:
		radial-gradient(calc(1200px * var(--lspf, 1)) calc(700px * var(--lspf, 1)) at 82% 102%, rgba(212, 62, 96, 0.45), transparent 68%),
		radial-gradient(calc(1000px * var(--lspf, 1)) calc(600px * var(--lspf, 1)) at 10% 100%, rgba(170, 46, 72, 0.35), transparent 64%);
}
:root[data-tone="custom"][data-theme="light"] body:has(#app[data-page=""])::before {
	background:
		radial-gradient(calc(1200px * var(--lspf, 1)) calc(700px * var(--lspf, 1)) at 12% 102%,
			color-mix(in srgb, color-mix(in srgb, var(--tone-a, #3a5cbe) 65%, #26314a) 55%, transparent), transparent 68%),
		radial-gradient(calc(1100px * var(--lspf, 1)) calc(640px * var(--lspf, 1)) at 88% 100%,
			color-mix(in srgb, color-mix(in srgb, var(--tone-b, #c43480) 65%, #4a2638) 48%, transparent), transparent 68%);
}

/* The slider's reach. Wash layers scale by opacity; the tinted ground reads
   its mix percentages straight from the emitted variables. */
:root[data-theme="light"] body::before { opacity: var(--lwo, 0.66); }
:root[data-lightbg="tinted"][data-theme="light"] {
	--bg: color-mix(in srgb, var(--lt-hue) var(--ltp, 8%), #f7f9fc);
	--bg-lift: color-mix(in srgb, var(--lt-hue) var(--ltp-lift, 4%), #ffffff);
}
/* A custom colour must be deepened before tinting paper with it — a white or
   pastel pick otherwise dissolves into grey. */
:root[data-tone="custom"] { --lt-hue: color-mix(in srgb, var(--tone-a, #3a5cbe) 70%, #2c3a5c); }

/* Custom ground, dark form: the station's two colours as the whole ground.
   (Restored — an earlier edit truncated this rule; Custom in dark theme fell
   back to plain black without anyone choosing that.) */
:root[data-tone="custom"]:not([data-theme="light"]) body {
	background:
		radial-gradient(calc(max(1300px, 85vw) * var(--lspf, 1)) calc(max(800px, 60vh) * var(--lspf, 1)) at var(--lx1, 15%) -10%,
			color-mix(in srgb, color-mix(in srgb, color-mix(in srgb, var(--tone-a, #3a5cbe) 60%, transparent) var(--ltc, 100%), transparent) var(--lsc, 100%), transparent), transparent 74%),
		radial-gradient(calc(max(1200px, 85vw) * var(--lspf, 1)) calc(max(760px, 60vh) * var(--lspf, 1)) at var(--lx2, 90%) 99%,
			color-mix(in srgb, color-mix(in srgb, var(--tone-b, #c43480) 50%, transparent) var(--lsc, 100%), transparent), transparent 76%),
		linear-gradient(150deg,
			color-mix(in srgb, color-mix(in srgb, var(--tone-a, #3a5cbe) 30%, #06070d) var(--lsc, 100%), #090a12) 0%,
			color-mix(in srgb, color-mix(in srgb, var(--tone-b, #c43480) 26%, #06070d) var(--lsc, 100%), #090a12) 100%);
	background-attachment: fixed;
}

/* The pre-tone ambient layer (accent radials + a bottom fade to --bg) sits
   above the body's background. Over a tone ground its bottom fade paints
   near-black across the lower half of the viewport and buries the bottom
   bloom. The grounds are complete compositions — the legacy layer stands
   down wherever one is active. Black and Deep keep it: they have no ground
   of their own and its glow is their look. */
:root[data-tone="gradient"]:not([data-theme="light"]) body::before,
:root[data-tone="dusk"]:not([data-theme="light"]) body::before,
:root[data-tone="aurora"]:not([data-theme="light"]) body::before,
:root[data-tone="ocean"]:not([data-theme="light"]) body::before,
:root[data-tone="ember"]:not([data-theme="light"]) body::before,
:root[data-tone="custom"]:not([data-theme="light"]) body::before {
	background: none;
}

/* Text on glass floats over unknowable content, so it carries its own
   separation: near-full colour plus a soft halo — the same trick OS media
   overlays use. Scoped to the dock; the on-air card sits on a controlled
   backdrop and keeps its quieter palette. */
.dock .np-title {
	color: var(--ink);
	text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6), 0 2px 12px rgba(0, 0, 0, 0.45);
}
.dock .np-artist {
	color: color-mix(in srgb, var(--ink) 85%, var(--muted));
	text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55), 0 2px 10px rgba(0, 0, 0, 0.4);
}
:root[data-theme="light"] .dock .np-title,
:root[data-theme="light"] .dock .np-artist {
	text-shadow: 0 1px 3px rgba(255, 255, 255, 0.95), 0 2px 14px rgba(255, 255, 255, 0.8);
}

/* Light theme, home page, before any scroll: the glass header floats over the
   dark photograph, so light-theme greys would sink into it. The header's
   palette flips light for exactly that state and reverts the moment the
   visitor scrolls (body.scrolled, 12px) and the photo leaves the glass.
   Everything inside inherits — brand, nav, burger, toggle. */
:root[data-theme="light"] body.over-photo:has(.hero[data-photo="dark"]) .site-header {
	--ink: #f4f7fb;
	--text: #c8d5e4;
	--muted: #9fb0c4;
	--line: rgba(255, 255, 255, 0.18);
	/* The nav underline and live-pill accents are navy in light theme —
	   invisible on the photo. They take dark theme's lifted cyan here. */
	--accent-lift: color-mix(in srgb, var(--accent) 55%, #9adcff);
}
.site-header .brand-name,
.site-header .site-nav a,
.site-header .theme-toggle {
	transition: color 0.3s;
}

/* ================= Show details popup =================
   Cards and schedule rows show a taster — the clamp is purely visual, the
   full text stays in the element, and clicking opens it in a dialog. One
   dialog, both pages, the site's glass material. */
.show-desc { display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
.slot-desc { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.show-card, .slot[data-slot] { cursor: pointer; }
.slot[data-slot]:hover .slot-name, .show-card:hover .show-name { color: var(--accent-lift); }

.show-dialog {
	border: 1px solid var(--line); border-radius: 18px; padding: 0;
	width: min(560px, calc(100vw - 32px)); max-height: min(82vh, 740px);
	background: var(--bg-lift); color: var(--text);
	box-shadow: 0 40px 90px rgba(0, 0, 0, 0.5);
	overflow: hidden;
}
/* Flex layout ONLY while open. An unconditional display on a <dialog>
   overrides the browser's closed-state display:none — the dialog then
   lingers on the page as a ghost after close(), backdrop gone, and reflows
   to the top of the document. That ghost was every "won't close" report. */
.show-dialog[open] {
	/* The image is a fixed pane; only the text below it scrolls. */
	display: flex; flex-direction: column;
}
.show-dialog::backdrop { background: rgba(4, 6, 10, 0.55); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
.sd-close {
	/* The modal dialog is the containing block, so this pins to its corner
	   regardless of what scrolls inside. */
	position: absolute; top: 12px; right: 12px;
	width: 34px; height: 34px; border-radius: 50%; border: none; cursor: pointer;
	background: rgba(0, 0, 0, 0.35); color: #fff; font-size: 15px; z-index: 1;
}
.sd-close:hover { background: rgba(0, 0, 0, 0.55); }
.sd-media { flex: none; }
.sd-media img {
	display: block; width: 100%; height: min(300px, 38vh);
	/* Crop favours the top of the frame — a portrait keeps its face, and
	   square show artwork is unaffected. */
	object-fit: cover; object-position: 50% 18%;
}
.sd-media[hidden] { display: none; }
.sd-body { padding: 20px 24px 26px; overflow: auto; flex: 1; min-height: 0; }
.sd-name { margin: 0 0 4px; font-size: 22px; color: var(--ink); letter-spacing: -0.3px; }
.sd-presenter { color: var(--accent-lift); font-weight: 600; margin-bottom: 2px; }
.sd-when { color: var(--muted); font-size: 14px; font-variant-numeric: tabular-nums; margin-bottom: 14px; }
.sd-desc { margin: 0; white-space: pre-line; line-height: 1.65; }

/* ================= The living hero =================
   The photo drifts almost imperceptibly and the words arrive in sequence —
   the grammar of a broadcast promo, made from what the station already
   provided. Keyframes own the start state, so with animations disabled the
   content simply stands there fully visible. */
@keyframes hero-drift {
	from { transform: scale(1.05) translate(0, 0); }
	to { transform: scale(1.17) translate(-2.2%, -2.4%); }
}
/* Negative delays start every ambient motion mid-cycle: the page loads
   already moving, rather than waking up after a still first act. */
.hero-bg { animation: hero-drift 22s ease-in-out -9s infinite alternate; will-change: transform; }

@keyframes rise-in {
	from { opacity: 0; transform: translateY(16px); }
	to { opacity: 1; transform: none; }
}
.hero-copy .display { animation: rise-in 0.8s cubic-bezier(0.2, 0.7, 0.2, 1) 0.05s backwards; }
.hero-copy .hero-info { animation: rise-in 0.8s cubic-bezier(0.2, 0.7, 0.2, 1) 0.22s backwards; }
.hero-copy .cta-row { animation: rise-in 0.8s cubic-bezier(0.2, 0.7, 0.2, 1) 0.38s backwards; }
.hero .onair { animation: rise-in 0.9s cubic-bezier(0.2, 0.7, 0.2, 1) 0.3s backwards; }

@media (prefers-reduced-motion: reduce) {
	.hero-bg, .hero-copy .display, .hero-copy .hero-info, .hero-copy .cta-row, .hero .onair {
		animation: none;
	}
}

/* While audio plays, the dock and the header carry the controls — the
   hero's big invitation stands down instead of becoming a third Stop, and
   eases back the moment playback ends. Space is kept so the hero never
   reflows; the button hands its role to the dock. */
.cta-row [data-player-toggle] {
	max-width: 340px; overflow: hidden; white-space: nowrap;
	/* Returning: the space opens first, then the button fades back in. */
	transition: opacity 0.3s ease 0.24s, transform 0.32s var(--ease) 0.24s, filter 0.3s ease 0.24s,
		max-width 0.34s var(--ease), padding 0.34s var(--ease), visibility 0s linear 0.24s;
}
body.is-playing .cta-row [data-player-toggle],
body.is-connecting .cta-row [data-player-toggle] {
	opacity: 0; transform: scale(1.18); filter: blur(6px);
	visibility: hidden; pointer-events: none;
	/* Departing: poof first, then the vacated space closes so any
	   neighbouring button glides across into place. */
	max-width: 0; padding-left: 0; padding-right: 0;
	transition: opacity 0.3s ease, transform 0.32s var(--ease), filter 0.3s ease,
		max-width 0.36s var(--ease) 0.26s, padding 0.36s var(--ease) 0.26s, visibility 0s linear 0.32s;
}

/* The dock's entrance, triggered once per appearance: it rises and
   stretches out of a centred glass lozenge while its controls fade in. */
.dock.arriving { animation: dock-stretch 0.5s cubic-bezier(0.32, 0.72, 0.28, 1); }
/* No opacity in these keyframes: fading a backdrop-filter element changes
   how its glass composites, and the blur would visibly re-engage when the
   animation ends. Clip and rise carry the entrance alone. */
@keyframes dock-stretch {
	from { clip-path: inset(0 31% 0 31% round 999px); transform: translate(-50%, 18px); }
	to { clip-path: inset(0 0 0 0 round 999px); transform: translate(-50%, 0); }
}
/* The exit is the entrance reversed and a little brisker — an exit that
   matches its entrance in length feels sluggish. The outer glow goes first
   (it is what clipping cannot carry), then the bar contracts into the
   lozenge and settles down. */
.dock.leaving {
	animation: dock-contract 0.34s cubic-bezier(0.4, 0, 0.7, 0.3) forwards;
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
	pointer-events: none;
}
@keyframes dock-contract {
	from { clip-path: inset(0 0 0 0 round 999px); transform: translate(-50%, 0); }
	to { clip-path: inset(0 31% 0 31% round 999px); transform: translate(-50%, 18px); }
}

/* ================= Site templates =================
   A template is a look the whole site wears, chosen per station. Each is a
   bundle of declarative rules over the SAME markup — never a second copy of
   the page — so every feature keeps working in every template.

   Editorial is the baseline and needs no rules: it IS the current design.
   The others push type scale, edge shape, density and how much the
   photography leads. Each degrades on its own: a station with no pictures
   still gets a composed page, because nothing here depends on an image
   existing. */

/* -- Magazine: programmes first, denser cards, imagery smaller -- */
:root[data-template="magazine"] .display {
	font-size: clamp(26px, 2.8vw, 38px); line-height: 1.15; letter-spacing: -0.6px;
}
/* Content-led: the hero is a band, not a stage, so programmes and podcasts
   start near the top of the page. */
:root[data-template="magazine"] .hero-photo { min-height: clamp(220px, 17vw, 300px); }
:root[data-template="magazine"] .hero {
	padding-top: clamp(26px, 2.4vw, 40px);
	padding-bottom: clamp(20px, 2vw, 32px);
}
/* A short hero centres its content, which on this template would put the
   headline behind the sticky header — so the photo band reserves the
   header's height before centring. */
:root[data-template="magazine"] .hero-photo { padding-top: 72px; }
:root[data-template="minimal"] .hero-photo { padding-top: 56px; }
:root[data-template="magazine"] .display-sub { font-size: clamp(15px, 1.4vw, 17px); margin-bottom: 14px; }
:root[data-template="magazine"] .hero-copy .hero-info { font-size: 14px; }
:root[data-template="magazine"] .panel { padding: 16px 18px; }
:root[data-template="magazine"] .panel-head h2 { font-size: 16px; }
:root[data-template="magazine"] .home-grid { gap: 16px; }
:root[data-template="magazine"] .track-list li { padding: 8px 10px; }
:root[data-template="magazine"] .show-grid {
	grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 16px;
}
:root[data-template="magazine"] .show-media { height: auto; width: 100%; aspect-ratio: 16 / 10; }
:root[data-template="magazine"] .show-media .show-name { font-size: 17px; }
:root[data-template="magazine"] .show-body { padding: 12px 14px 14px; }
:root[data-template="magazine"] .show-desc { -webkit-line-clamp: 2; font-size: 13px; }
:root[data-template="magazine"] .episode { padding: 10px 12px; gap: 12px; }
:root[data-template="magazine"] .episode-art { width: 64px; height: 64px; }
:root[data-template="magazine"] .episode-title { font-size: 15px; }
:root[data-template="magazine"] .pod-card { flex-basis: 128px; }
:root[data-template="magazine"] .pod-art { width: 128px; height: 128px; }
:root[data-template="magazine"] .slot { padding: 12px 14px; }

/* -- Minimal: quiet, typographic, imagery sparing -- */
:root[data-template="minimal"] .display {
	font-size: clamp(30px, 3.4vw, 44px); line-height: 1.2; letter-spacing: 0;
	font-weight: 500;
}
/* Quiet: the photograph is a whisper behind the words, the cards lose their
   fills, and the page breathes instead of stacking panels. */
:root[data-template="minimal"] .hero-photo { min-height: clamp(240px, 18vw, 320px); }
:root[data-template="minimal"] .hero-bg { filter: saturate(0.2) contrast(0.98); opacity: 0.5; }
:root[data-template="minimal"] .container { max-width: 1060px; }
:root[data-template="minimal"] .panel { padding: 4px 0 18px; }
:root[data-template="minimal"] .panel-head { border-bottom: 1px solid var(--line); padding-bottom: 8px; }
:root[data-template="minimal"] .track-list li,
:root[data-template="minimal"] .upnext-list li {
	background: transparent; border: 0; border-bottom: 1px solid var(--line); border-radius: 0;
}
:root[data-template="minimal"] .home-grid { gap: 34px; }
:root[data-template="minimal"] .tile { background: transparent; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; }
:root[data-template="minimal"] .show-media { height: auto; width: 100%; aspect-ratio: 21 / 9; }
:root[data-template="minimal"] .show-media .show-name { font-size: 18px; }
:root[data-template="minimal"] .pod-card { flex-basis: 112px; }
:root[data-template="minimal"] .pod-art { width: 112px; height: 112px; }
:root[data-template="minimal"] .episode-art { width: 56px; height: 56px; }
:root[data-template="minimal"] .panel,
:root[data-template="minimal"] .show-card,
:root[data-template="minimal"] .episode,
:root[data-template="minimal"] .slot {
	background: transparent; border-color: var(--line); box-shadow: none;
}
:root[data-template="minimal"] .panel-head h2 { font-size: 15px; letter-spacing: 0.2px; }

/* ---------- Podcasts: home rail and episodes page ---------- */
.pod-rail-panel { margin-bottom: 26px; }
.pod-rail {
	display: flex; gap: 14px; overflow-x: auto; padding: 4px 2px 10px;
	scrollbar-width: thin;
}
.pod-card { flex: 0 0 152px; cursor: pointer; }
.pod-art {
	display: block; width: 152px; height: 152px; border-radius: 14px;
	object-fit: cover; background: var(--card);
	box-shadow: 0 10px 26px rgba(0, 0, 0, 0.35);
	transition: transform 0.25s var(--ease), box-shadow 0.25s;
}
.pod-card:hover .pod-art { transform: translateY(-3px); box-shadow: 0 16px 34px rgba(0, 0, 0, 0.45); }
.pod-card.playing .pod-art { outline: 2px solid var(--accent-lift); outline-offset: 2px; }
.pod-art-empty { display: grid; place-items: center; font-size: 40px; }
.pod-title {
	display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
	margin-top: 9px; font-size: 13.5px; line-height: 1.35; color: var(--ink); font-weight: 600;
}
.pod-meta { display: block; margin-top: 3px; font-size: 12px; color: var(--muted); font-style: normal; }

.episode-list { display: flex; flex-direction: column; gap: 14px; padding-bottom: 40px; }
.episode {
	display: flex; align-items: center; gap: 16px; cursor: pointer;
	background: var(--card); border: 1px solid var(--line); border-radius: 16px;
	padding: 14px 16px;
	transition: border-color 0.2s, transform 0.2s var(--ease);
}
.episode:hover { border-color: color-mix(in srgb, var(--accent) 45%, transparent); transform: translateY(-1px); }
.episode.playing { border-color: var(--accent-lift); }
.episode-art {
	flex: none; width: 84px; height: 84px; border-radius: 12px; object-fit: cover;
	background: var(--hover);
}
.episode-art-empty { display: grid; place-items: center; font-size: 30px; }
.episode-body { min-width: 0; flex: 1; }
.episode-title { margin: 0 0 4px; font-size: 16.5px; color: var(--ink); }
.episode-meta { font-size: 12.5px; color: var(--muted); margin-bottom: 6px; }
.episode-desc {
	margin: 0; font-size: 13.5px; color: var(--text); line-height: 1.5;
	display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.episode-play {
	flex: none; width: 44px; height: 44px; border-radius: 50%; border: none; cursor: pointer;
	color: #fff; background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 50%, #0ea5e9));
	display: grid; place-items: center; transition: transform 0.2s var(--ease);
}
.episode-play:hover { transform: scale(1.08); }
.episode-play .ic-stop { display: none; }
.episode.playing .episode-play .ic-play { display: none; }
.episode.playing .episode-play .ic-stop { display: block; }
@media (max-width: 560px) {
	.episode { align-items: flex-start; }
	.episode-art { width: 64px; height: 64px; }
}

/* Page titles join the display voice. */
.page-title { font-family: var(--font-display); font-weight: var(--w-display); letter-spacing: -0.3px; }
.sd-name { font-family: var(--font-display); font-weight: var(--w-display); }
/* A programme airing several times lists them one per line in the popup. */
.sd-when { white-space: pre-line; }

/* ================= Shows as posters =================
   The name the station typed, composed over the image they uploaded — the
   template does what a designer would. */
.show-media { position: relative; }
.show-media::after {
	content: ""; position: absolute; inset: 40% 0 0 0;
	background: linear-gradient(to bottom, transparent, rgba(5, 8, 14, 0.82));
}
.show-media .show-name {
	position: absolute; left: 16px; right: 16px; bottom: 12px; z-index: 1;
	margin: 0; color: #fff;
	font-family: var(--font-display); font-weight: var(--w-display);
	font-size: 22px; line-height: 1.2; letter-spacing: -0.2px;
	text-shadow: 0 1px 8px rgba(0, 0, 0, 0.55);
}

/* The on-air card carries a slow breathing glow in the station's accent —
   alive, like the studio light it represents. */
@keyframes onair-breathe {
	from { box-shadow: 0 24px 56px rgba(0, 0, 0, 0.35), 0 0 0px color-mix(in srgb, var(--accent) 0%, transparent); }
	to { box-shadow: 0 24px 56px rgba(0, 0, 0, 0.35), 0 0 44px color-mix(in srgb, var(--accent) 38%, transparent); }
}
.hero .onair { animation: rise-in 0.9s cubic-bezier(0.2, 0.7, 0.2, 1) 0.3s backwards, onair-breathe 4s ease-in-out 1.3s infinite alternate; }


@media (prefers-reduced-motion: reduce) {
	.hero .onair { animation: none; }
}

/* ================= Living, not static =================
   Three continuous motions, all slow enough to feel rather than watch:
   a light sweep travelling through the headline, the ambient glow drifting
   like aurora, and the photo's colour grade breathing. All stand down under
   prefers-reduced-motion. */

/* The headline carries a travelling sheen — ink, a passing lift of the
   accent, ink again. Uses the palette variables, so it adapts to theme and
   to the dark-photo text flip automatically. */
.hero-copy .display {
	background: linear-gradient(110deg,
		var(--ink) 0%, var(--ink) 32%,
		color-mix(in srgb, var(--accent-lift) 70%, var(--ink)) 50%,
		var(--ink) 68%, var(--ink) 100%);
	background-size: 260% 100%;
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
	/* One unbroken glide: the band is off the glyphs at both ends of the
	   cycle, so the loop point never shows and the sheen never parks. The
	   negative delay means it is already mid-glide on load. */
	animation: rise-in 0.8s cubic-bezier(0.2, 0.7, 0.2, 1) 0.05s backwards,
		headline-sheen 9s linear -3s infinite;
}
@keyframes headline-sheen {
	0% { background-position: 130% 0; }
	100% { background-position: -40% 0; }
}

/* The ambient glow layer drifts — aurora, not wallpaper. */
body::before {
	animation: aurora-drift 30s ease-in-out -14s infinite alternate;
	will-change: transform;
}
@keyframes aurora-drift {
	from { transform: translate(0, 0) scale(1); }
	to { transform: translate(1.8%, -1.4%) scale(1.07); }
}

/* "Animate the background" ticked in Appearance: the ambient layers move
   enough to be seen — the glow wanders, swells and breathes in saturation,
   and an uploaded background's blurred wash drifts with it. Plain stays
   plain. Reduced-motion visitors are covered by the global kill switch. */
:root[data-animate="1"]:not([data-glow="plain"]) body::before {
	animation: aurora-lively 14s ease-in-out -6s infinite alternate;
	will-change: transform, filter;
}
@keyframes aurora-lively {
	from { transform: translate(-5%, 3%) scale(1.18) rotate(-2.2deg); filter: saturate(1) hue-rotate(-10deg) brightness(1); }
	to { transform: translate(5.5%, -3.5%) scale(1.42) rotate(2.6deg); filter: saturate(1.7) hue-rotate(14deg) brightness(1.22); }
}
/* Dark-theme tone presets paint their gradient on body itself and blank
   ::before, which left the drift animating an empty layer. The moving light
   therefore gets a layer of its own: two accent glows on ::after that wander
   over whatever ground the tone painted. Oversized so the drift never shows
   an edge. */
/* body normally shares the root stacking context, where negative-z layers
   paint BENEATH body's own background — and the dark tones paint an opaque
   gradient on body, burying the glow. Isolating body reorders the paint:
   tone gradient first, then the glow layers, then the content. */
:root[data-animate="1"] body { isolation: isolate; }
:root[data-animate="1"]:not([data-glow="plain"]) body::after {
	content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
	/* A gradient that breathes, not an object that travels: two soft glows
	   anchored where the station placed its light (the same --lx variables
	   the tone uses), and the whole field slowly swells, brightens, leans a
	   few degrees in hue, and settles back. Nothing crosses the page —
	   the light lives where it is. */
	background:
		radial-gradient(56vw 44vh at var(--lx1, 78%) 6%, color-mix(in srgb, var(--accent-lift) 38%, transparent), transparent 70%),
		radial-gradient(66vw 54vh at var(--lx2, 14%) 98%, color-mix(in srgb, var(--accent) 42%, transparent), transparent 72%);
	mix-blend-mode: screen;
	animation: glow-breathe 9s ease-in-out -4s infinite alternate;
	will-change: opacity, transform, filter;
}
:root[data-animate="1"][data-theme="light"]:not([data-glow="plain"]) body::after { opacity: 0.55; }
@keyframes glow-breathe {
	from { opacity: 0.45; transform: scale(1); filter: hue-rotate(-6deg) brightness(1); }
	to { opacity: 1; transform: scale(1.1); filter: hue-rotate(8deg) brightness(1.14); }
}
}
}
/* The wash keyframes restate the full filter chain: animating a filter
   replaces the whole list, so the blur must ride along in both frames. */
}

/* The photo's grade breathes: colour concentration easing in and out. */
.hero-bg::before { animation: grade-breathe 14s ease-in-out -6s infinite alternate; }
@keyframes grade-breathe {
	from { opacity: 0.42; }
	to { opacity: 0.6; }
}

@media (prefers-reduced-motion: reduce) {
	.hero-copy .display { animation: none; background: none; color: var(--ink); }
	body::before, .hero-bg::before { animation: none; }
}
