.player {
	position: relative;
	width: 100%;
	background: #000;
	border: 1px solid var(--line);
	border-radius: var(--radius);
	overflow: hidden;
	/* No drop shadow: it darkened the ambilight bleeding out from behind the player. */
	box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02);
}

.playerScreen {
	position: relative;
	width: 100%;
	aspect-ratio: 16 / 9;
	background: #000;
}

.playerVideo {
	width: 100%;
	height: 100%;
	object-fit: cover;
	background: #000;
	display: block;
}
.playerVideo::-webkit-media-controls { display: none !important; }

.liveBadge {
	position: absolute;
	top: 16px;
	left: 16px;
	z-index: 3;
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 6px 11px;
	border-radius: 4px;
	background: rgba(8, 8, 10, 0.6);
	border: 1px solid rgba(255, 255, 255, 0.08);
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 2px;
	text-transform: uppercase;
}
.liveDot { width: 9px; height: 9px; border-radius: 50%; background: var(--red); animation: livePulse 1.8s infinite; }

@keyframes livePulse {
	0% { box-shadow: 0 0 0 0 var(--red-glow); }
	70% { box-shadow: 0 0 0 9px rgba(212, 32, 47, 0); }
	100% { box-shadow: 0 0 0 0 rgba(212, 32, 47, 0); }
}

/* Now-playing overlay: hidden on the page (info lives in the side panels), shown over the video only in
   fullscreen. Top-left, and everything sizes with clamp(..vw..) so it scales smoothly from a phone in
   portrait up to a big-screen TV (the fixed-px version was unreadably small on a TV). It carries the same
   info as the side panels (band, song, country, album, genre, tags) plus the Follow/Save buttons, so the
   viewer never needs to exit fullscreen. The Overlay control-bar button still toggles it. */
.nowOverlay {
	display: none;
	position: absolute;
	left: clamp(8px, 1vw, 18px);
	bottom: calc(var(--barH, 64px) + clamp(12px, 1vw, 18px));
	z-index: 3;
	flex-direction: column;
	gap: clamp(8px, 0.8vw, 18px);
	width: clamp(224px, 18vw, 480px);
	max-width: calc(100% - 24px);
	max-height: calc(100% - var(--barH, 64px) - 30px);
	overflow-y: auto;
	padding: clamp(12px, 1vw, 22px) clamp(14px, 1.1vw, 26px);
	/* No border, no blur. Dark on the left for legibility, held solid most of the way then faded to fully
	   transparent at the right edge (so the right just dissolves into the video while the text stays readable). */
	background: linear-gradient(to right, rgba(10, 10, 12, 0.66) 0%, rgba(10, 10, 12, 0.66) 90%, rgba(10, 10, 12, 0) 100%);
	border-radius: 6px;
	transition: opacity 0.25s, transform 0.25s;
	scrollbar-width: thin;
}
.nowOverlay::-webkit-scrollbar { width: 6px; }
.nowOverlay::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 3px; }
.player.isFullscreen .nowOverlay { display: flex; }
/* Overlay OFF (the default): the song card is part of the auto-hiding UI - it shows with the control bar on
   mouse activity and fades out with it. */
.player.isFullscreen.controlsHidden:not(.overlayPinned) .nowOverlay { opacity: 0; transform: translateY(8px); pointer-events: none; }
/* Overlay ON (pinned via the Overlay button): stays visible; when the bar auto-hides it just settles down
   into the freed space (~30px above the bottom, matching the corner ad). */
.player.isFullscreen.controlsHidden.overlayPinned .nowOverlay { transform: translateY(calc(var(--barH, 64px) - 18px)); }

.ovPhoto { position: relative; border-radius: 5px; overflow: hidden; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5); }
.ovPhotoImg { width: 100%; height: 100%; object-fit: cover; display: block; }
.ovPhotoFallback { position: absolute; inset: 0; background: linear-gradient(135deg, #1a1a1f, #0d0d10); }
/* Band image on its own line at the top: shown WHOLE at its natural size (not cropped), left-aligned,
   capped tall and at the card width, so logos and photos take up space correctly. */
.ovBandPhoto { width: 100%; max-height: 200px; }
.ovBandPhoto .ovPhotoImg { width: auto; height: auto; max-width: 100%; max-height: 200px; object-fit: contain; }
.ovBandPhoto.isLogo { background: transparent; box-shadow: none; }
.ovAlbumPhoto { flex: none; width: clamp(46px, 4vw, 100px); height: clamp(46px, 4vw, 100px); }
.ovTitle { line-height: 1.18; }
.ovBand { display: block; font-size: clamp(16px, 1.35vw, 32px); font-weight: 700; color: var(--silver-1); }
.ovSong { display: block; margin-top: 3px; font-size: clamp(14px, 1.15vw, 27px); font-weight: 600; color: var(--fg); }
.ovSongLen { display: block; margin-top: 2px; font-size: clamp(11px, 0.8vw, 16px); color: var(--fg-dim); font-variant-numeric: tabular-nums; }
.ovSongLen:empty { display: none; }
.ovCountry { display: flex; align-items: center; gap: 7px; font-size: clamp(11px, 0.9vw, 19px); color: var(--fg-dim); }

.ovAlbum { display: flex; gap: clamp(10px, 0.9vw, 20px); align-items: center; }
.ovAlbumText { min-width: 0; }
.ovAlbumLabel { font-size: clamp(9px, 0.62vw, 13px); letter-spacing: 1.6px; text-transform: uppercase; color: var(--fg-mute); }
.ovAlbumName { font-size: clamp(13px, 1.02vw, 22px); color: var(--silver-1); margin-top: 2px; }
.ovAlbumDate { font-size: clamp(11px, 0.85vw, 18px); color: var(--fg-dim); margin-top: 2px; }

.ovRow { display: flex; flex-wrap: wrap; }
.ovChips { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }
.nowOverlay .gChip, .nowOverlay .gTag { font-size: clamp(10px, 0.78vw, 16px); }

.ovActions { display: flex; flex-wrap: wrap; gap: clamp(8px, 0.7vw, 16px); margin-top: clamp(2px, 0.2vw, 6px); }
.ovBtn { font-size: clamp(12px, 0.9vw, 18px); }
.ovBtn .btnIcon { width: clamp(16px, 1.1vw, 22px); height: auto; }

.loader {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	z-index: 2;
	color: var(--fg-dim);
	font-size: 13px;
	letter-spacing: 3px;
	text-transform: uppercase;
}
.loader.isHidden { display: none; }

/* Daily rollover toast (player.js showRestartToast): a small auto-dismissing pill near the bottom of the
   player at the ~6pm NZST restart. Absolute inside .player so it also shows in fullscreen. */
.streamToast {
	position: absolute; left: 50%; bottom: calc(var(--barH, 64px) + 16px); transform: translateX(-50%) translateY(12px);
	z-index: 12; max-width: calc(100% - 32px); padding: 10px 18px; border-radius: 999px;
	background: rgba(13, 13, 15, 0.94); border: 1px solid var(--line); color: var(--silver-1);
	font-size: 13px; font-weight: 600; letter-spacing: 0.3px; white-space: nowrap;
	box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5);
	opacity: 0; pointer-events: none; transition: opacity .3s ease, transform .3s ease;
}
.streamToast.isShown { opacity: 1; transform: translateX(-50%) translateY(0); }

/* Big unmute prompt over the video (autoplay is muted). Shown only while muted and playing. */
.unmuteOverlay {
	display: none;
	position: absolute;
	inset: 0;
	z-index: 3;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 16px;
	border: none;
	cursor: pointer;
	color: #fff;
	font-family: inherit;
	background: radial-gradient(circle at 50% 45%, rgba(0, 0, 0, 0.32), rgba(0, 0, 0, 0.62));
}
.player.isMuted:not(.isLoading) .unmuteOverlay { display: flex; }
.unmuteCircle {
	width: 84px;
	height: 84px;
	border-radius: 50%;
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--red);
	box-shadow: 0 0 0 10px rgba(212, 32, 47, 0.18), 0 10px 34px rgba(0, 0, 0, 0.55);
	transition: transform 0.15s, box-shadow 0.15s;
}
.unmuteOverlay:hover .unmuteCircle { transform: scale(1.06); box-shadow: 0 0 0 14px rgba(212, 32, 47, 0.22), 0 10px 34px rgba(0, 0, 0, 0.55); }
.unmuteCircle svg { width: 40px; height: 40px; fill: #fff; }
.unmuteLabel { font-size: 13px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase; text-shadow: 0 1px 8px rgba(0, 0, 0, 0.8); }

.controlBar {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 4;
	display: flex;
	align-items: center;
	gap: 12px;
	padding: 26px 16px 13px;
	background: linear-gradient(180deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.85));
	transition: transform 0.25s ease, opacity 0.25s ease;
}

.ctrl {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	background: none;
	border: none;
	cursor: pointer;
	color: var(--fg-dim);
	font-family: inherit;
	font-size: 12px;
	font-weight: 600;
	letter-spacing: 1.5px;
	text-transform: uppercase;
	padding: 6px;
	transition: color 0.15s;
}
.ctrl:hover { color: var(--fg); }
.ctrl svg { width: 20px; height: 20px; display: block; fill: currentColor; }
.ctrlIcon { padding: 6px 4px; }

.ctrlSpacer { flex: 1; }

/* Selectors must out-specify ".ctrl svg { display: block }", or the hidden icon of each
   pair leaks through and both show at once. */
.ctrl .iconPause { display: none; }
.player.isPlaying .ctrl .iconPlay { display: none; }
.player.isPlaying .ctrl .iconPause { display: block; }

.ctrl .iconMute { display: none; }
.player.isMuted .ctrl .iconVol { display: none; }
.player.isMuted .ctrl .iconMute { display: block; }

/* Volume as a filled "level" bar. The ACTIVE portion (0 -> current level) is red with a soft glow; the
   PASSIVE remainder is dark. player.js keeps --vol in sync (0% when muted), so muting empties the bar and
   the .isMuted rule below greys the fill + knob = a clear silent state. The knob is a white bead ringed in
   the active colour that swells on hover/focus for a tactile feel. */
.volSlider {
	-webkit-appearance: none;
	appearance: none;
	width: 96px;
	height: 6px;
	border-radius: 999px;
	cursor: pointer;
	outline: none;
	background: linear-gradient(to right,
		var(--volFill, var(--red)) var(--vol, 100%), var(--volTrack, #33333b) var(--vol, 100%));
	transition: background-color .2s;
}
.volSlider::-webkit-slider-thumb {
	-webkit-appearance: none;
	appearance: none;
	width: 14px;
	height: 14px;
	border-radius: 50%;
	background: #fff;
	border: 2px solid var(--volFill, var(--red));
	box-shadow: 0 0 7px var(--volGlow, var(--red-glow));
	transition: transform .12s ease, border-color .2s, box-shadow .2s;
}
.volSlider::-moz-range-thumb {
	width: 14px;
	height: 14px;
	border-radius: 50%;
	background: #fff;
	border: 2px solid var(--volFill, var(--red));
	box-shadow: 0 0 7px var(--volGlow, var(--red-glow));
}
/* Firefox paints its own progress fill + track, so match them to the active colour / dark remainder. */
.volSlider::-moz-range-progress { height: 6px; border-radius: 999px; background: var(--volFill, var(--red)); }
.volSlider::-moz-range-track { height: 6px; border-radius: 999px; background: var(--volTrack, #33333b); }
.volSlider:hover::-webkit-slider-thumb, .volSlider:focus-visible::-webkit-slider-thumb { transform: scale(1.22); }
.volSlider:hover::-moz-range-thumb, .volSlider:focus-visible::-moz-range-thumb { transform: scale(1.22); }
/* Muted (or level 0) = passive: no red, no glow. The bar is empty (--vol:0%) and the knob greys out. */
.player.isMuted .volSlider { --volFill: #6a6a74; --volGlow: transparent; }

.ctrlLive { color: var(--red); }
.ctrlLive .liveTinyDot {
	width: 8px;
	height: 8px;
	border-radius: 50%;
	background: var(--red);
	animation: livePulse 1.8s infinite;
}

.ctrl.isActive { color: var(--fg); }

/* The overlay control only makes sense in fullscreen (on the page the info is in the panels). */
#overlayBtn { display: none; }
.player.isFullscreen #overlayBtn { display: inline-flex; }

/* The report flag opens the site modal, which cannot render inside the fullscreen element, and a lean-back
   fullscreen viewer does not need it, so hide it in fullscreen (admin rotation/delete buttons stay). */
.player.isFullscreen [data-report-open] { display: none; }

/* Fill the screen cleanly in fullscreen (drop the framed-module look). */
.player.isFullscreen { border: none; border-radius: 0; box-shadow: none; }
.player.isFullscreen .playerScreen { height: 100%; aspect-ratio: auto; }

/* Idle auto-hide (set by player.js): slide the control bar away and hide the cursor. The now-playing
   overlay sits top-left so it is clear of the bar and stays put; only the corner ad (bottom-right, in
   site.css) pins just above the bar via --barH and slides down with it. */
.player.controlsHidden .controlBar { transform: translateY(100%); opacity: 0; pointer-events: none; }
.player.controlsHidden { cursor: none; }

/* ---------- Watch layout: band | player | now playing ---------- */
.watchLayout {
	position: relative;   /* hosts #playerGlow behind the columns */
	display: grid;
	grid-template-columns: 300px minmax(0, 1fr) 300px;
	grid-template-areas:
		"band player now"
		"band seo    now";
	gap: 20px;
	align-items: start;
}
/* Everything stacks above the stage lighting; the panels' translucent backgrounds let it shine through. */
.bandColumn, .playerColumn, .nowColumn, .seoColumn { position: relative; z-index: 1; }
/* The band + now panels span BOTH rows, so the SEO row's top pins to the player column's bottom (i.e.
   directly under the chat) rather than under whichever side panel is tallest. align-items:start keeps each
   panel at its content height at the top of its tall cell, so they never stretch to fill. */
/* The left column stacks the band box and the frequency box (each its own card). */
.bandColumn { grid-area: band; display: flex; flex-direction: column; gap: 20px; min-width: 0; }
/* The right column stacks the now-playing box and the schedule box (each its own card). */
.nowColumn { grid-area: now; display: flex; flex-direction: column; gap: 20px; min-width: 0; }
.seoColumn { grid-area: seo; }

/* Center column = the player + a bar below it (a separate strip; holds the watcher count + future buttons). */
.playerColumn { grid-area: player; min-width: 0; position: relative; }

/* Ambilight (first child of .watchLayout, z-index under every column, container sized to the player's
   box by player.js). The glow IS the video: player.js paints the current frame (letterbox bars cropped)
   into this tiny canvas every ~120ms, and CSS stretches it ~30px past the player on every side and blurs
   it heavily - the footage's own light leaking from underneath, per-pixel, corners included (the
   YouTube-ambient-mode technique). screen blending keeps black invisible (a dark scene simply emits no
   light, and the dark-box GPU glitch stays impossible); saturate() lifts the colours so the glow reads
   vivid rather than washed out. player.js drives only opacity (music pulse + fade-in). */
.playerGlow { position: absolute; z-index: 0; pointer-events: none; }
.pgCanvas {
	position: absolute; left: -10px; top: -10px; width: calc(100% + 20px); height: calc(100% + 20px);
	opacity: 0; mix-blend-mode: screen; filter: blur(70px) saturate(1.7); border-radius: 22px; will-change: opacity;
}
@media (prefers-reduced-motion: reduce) { .playerGlow { display: none; } }
.playerBar {
	display: flex;
	align-items: center;
	gap: 14px;
	margin-top: 12px;
	padding: 10px 14px;
	background: rgba(20, 20, 23, 0.78);   /* translucent so the stage light shines through */
	border: 1px solid var(--line);
	border-radius: var(--radius);
}
.viewerBadge { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase; color: var(--fg); }
.viewerBadge .viewerIcon { height: 18px; width: auto; display: block; flex: none; }
.viewerBadge #viewerCount { color: var(--red); margin-top: -2px; }
.viewerBadge.isRevealed { outline: 1px dashed var(--red); outline-offset: 4px; border-radius: 3px; }   /* operator Ctrl-tap peek: below the public threshold, only they see it */
/* Player-bar: the viewer badge stays at the left; Genres + Request group together at the right. The base
   .freqBtn has margin-left:auto, so the FIRST button pushes the group right and the rest sit beside it. */
.playerBar .freqBtn { margin-left: 0; }
.playerBar .freqBtn:first-of-type { margin-left: auto; }

.sidePanel {
	background: rgba(20, 20, 23, 0.78);   /* --bg-elev with a little translucency so the stage light shines through */
	border: 1px solid var(--line);
	border-radius: var(--radius);
	padding: 18px 18px 20px;
}

.panelKicker { display: flex; align-items: center; gap: 8px; font-size: 11px; letter-spacing: 2.5px; text-transform: uppercase; color: var(--red); margin-bottom: 12px; }
.panelKicker .eqIcon { width: 15px; height: 15px; fill: var(--red); }

.bandName { font-size: 23px; font-weight: 700; letter-spacing: 0.5px; color: var(--silver-1); line-height: 1.15; margin-bottom: 14px; }
.bandName a { color: inherit; text-decoration: none; }            /* link to the band profile, stays white */
.bandName a:hover { text-decoration: underline; }

.panelPhoto { position: relative; width: 100%; border-radius: 5px; overflow: hidden; border: 1px solid var(--line-soft); background: var(--bg-elev-2); }
.panelPhoto img { width: 100%; height: 100%; display: block; }
/* Band card shows the logo/photo full width at natural height - no crop, no max-height. Album art sizes
   freely too (no square aspect-ratio, no object-fit crop). */
.bandPhoto img { height: auto; object-fit: contain; }
/* When the band card shows the logo (not the photo), sit it in a padded dark box; a photo stays full-bleed. */
.bandPhoto.isLogo { background: #0d0d0d; padding: 5px; }
.photoFallback { position: absolute; inset: 0; background: repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.025) 0 10px, rgba(255, 255, 255, 0) 10px 20px), var(--bg-elev-2); }

.factList { margin-top: 16px; }
.factRow { display: flex; align-items: center; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--line-soft); }
.factRow:last-child { border-bottom: none; }
.factKey { font-size: 11px; letter-spacing: 1px; text-transform: uppercase; color: var(--fg-dim); }
.factVal { margin-left: auto; text-align: right; font-size: 13px; color: var(--fg); }
/* Genre family + tag chips in the band card use the exact /admin/Genres .gChip/.gTag style (in site.css),
   flowing right-aligned in the fact value. */
.factChipRow { line-height: 2; }
.factChipRow .gChip, .factChipRow .gTag { margin: 2px 0 2px 5px; }   /* flush to the right edge like the other fact values */

/* Follow + Profile (band card) and YouTube + Archives (album) are left-aligned rows of the standard
   content-width panelBtn - the same button as Save Track, never stretched. */
.panelActions, .panelLinks { display: flex; flex-wrap: wrap; gap: 10px; }
.panelActions { margin-top: 14px; }
.panelLinks { margin-top: 16px; gap: 10px 15px; }   /* the band's social buttons wrap in loose rows */
.panelActions .panelBtn { margin-top: 0; }

/* Compact text-only buttons for the band's own links (Bandcamp, SoundCloud, ...): smaller and lighter
   than .panelBtn, no icon padding, they just wrap. */
.socialBtn {
	display: inline-flex;
	align-items: center;
	padding: 5px 10px;
	font: inherit;
	font-size: 11px;
	font-weight: 500;
	letter-spacing: 0.6px;
	text-transform: uppercase;
	text-decoration: none;
	color: var(--fg);
	background: var(--bg-elev-2);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	transition: color 0.15s, border-color 0.15s;
}
.socialBtn:hover { border-color: var(--red); color: #fff; }
.linkYt svg { fill: var(--red); }
/* (.linkMa retired 2026-07-03 with the Archives button; band social links use the plain .panelBtn.) */

/* Follow band / save track buttons (member library). Filled red-tint when active. */
.panelBtn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 10px;
	margin-top: 14px;
	padding: 9px 15px;
	font: inherit;
	font-size: 12px;
	font-weight: 600;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fg);
	background: var(--bg-elev-2);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	cursor: pointer;
	transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.panelBtn:hover { border-color: var(--red); color: #fff; }
.panelBtn.isActive { background: rgba(212, 32, 47, 0.16); border-color: var(--red); color: #fff; }
.panelBtn.isActive:hover { color: #fff; }
/* Buttons with a leading icon get less left padding so the icon optically aligns; text-only buttons keep even 15px. */
.panelBtn:has(.btnIcon) { padding-left: 10px; }
.panelBtn .btnIcon { height: 16px; width: auto; display: block; flex: none; }
.panelBtn .btnText { margin-top: -2px; }
/* Icon-only panel button (e.g. the band-page refresh/rescrape button): no text, equal padding both sides. */
.panelBtn.iconBtn { padding: 9px 10px; }
.panelBtn.iconBtn svg { width: 16px; height: 16px; display: block; flex: none; fill: currentColor; }

/* Country fact row: flag + name (kept right-aligned with the other facts). */
.countryVal { display: inline-flex; align-items: center; gap: 8px; }
.countryVal .flag { width: 22px; height: 16px; flex: none; object-fit: cover; border-radius: 2px; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); }

/* ---------- Live chat (sits under the player bar) ---------- */
.chatPanel { margin-top: 16px; display: flex; flex-direction: column; background: rgba(20, 20, 23, 0.78); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
/* Docked in the left band column (moved from under the player 2026-07-02): the column's flex gap does
   the spacing, and the side column carries a taller log. Undocked keeps its own floating size. */
.bandColumn .chatPanel { margin-top: 0; }
.bandColumn .chatPanel:not(.isUndocked) .chatLog { height: 300px; }
/* Collapsed (default): show only the header bar, like the player bar under the video. */
.chatPanel.isCollapsed .chatLog, .chatPanel.isCollapsed .chatForm { display: none; }
.chatPanel.isCollapsed .chatHead { border-bottom: none; }
/* New-message attention pulse on the collapsed chat: a soft red glow + a one-pass shine sweep across the
   header. Subtle, and throttled to once a minute in chat.js. Only fires while collapsed (expanded/undocked
   already shows the message). */
@keyframes chatGlow {
	0%   { box-shadow: 0 0 0 0 rgba(236, 27, 39, 0); border-color: var(--line); }
	22%  { box-shadow: 0 0 16px 1px rgba(236, 27, 39, .5); border-color: var(--red); }
	100% { box-shadow: 0 0 0 0 rgba(236, 27, 39, 0); border-color: var(--line); }
}
@keyframes chatShine {
	0%   { transform: translateX(-130%); opacity: 0; }
	12%  { opacity: 1; }
	100% { transform: translateX(130%); opacity: 0; }
}
.chatPanel.chatPulse { position: relative; animation: chatGlow 1.9s ease-out; }   /* mentions pulse expanded too; plain messages only pulse collapsed (JS gate) */
.chatPanel.chatPulse::after {
	content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 45%; pointer-events: none;
	background: linear-gradient(105deg, transparent, rgba(255, 255, 255, .10), transparent);
	transform: translateX(-130%); animation: chatShine 1.4s ease-out;
}
/* A channel tagged in chat glimmers its scroller card: same glow + shine, on the thing pointed at. */
.chanCard.cardPulse { position: relative; overflow: hidden; animation: chatGlow 1.9s ease-out 1; }
.chanCard.cardPulse::after {
	content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 45%; pointer-events: none;
	background: linear-gradient(105deg, transparent, rgba(255, 255, 255, .10), transparent);
	transform: translateX(-130%); animation: chatShine 1.4s ease-out;
}
@media (prefers-reduced-motion: reduce) {
	.chatPanel.chatPulse { animation: none; }
	.chatPanel.chatPulse::after { display: none; }
	.chanCard.cardPulse { animation: none; }
	.chanCard.cardPulse::after { display: none; }
}
/* Undocked: a free-floating window the viewer can drag by its header and resize. No backdrop, so the
   rest of the page stays usable. Always expanded; the collapse control is hidden while floating. */
.chatPanel.isUndocked { position: fixed; z-index: 210; margin: 0; width: 360px; height: 460px; min-width: 280px; min-height: 240px; max-width: 96vw; max-height: 92vh; overflow: hidden; box-shadow: 0 24px 70px rgba(0, 0, 0, 0.6); background: var(--bg-elev); }   /* floats over content: stay opaque; the .chatResize grip resizes (native resize retired: it cannot start from the dock) */
/* The floating window's z-index only counts inside its column's stacking context (.bandColumn is
   z-index 1 for the ambilight), so sibling columns painted OVER it: lift the whole column while the
   chat floats. 90 stays under the site modal (100) and the dialog overlays (1000). */
.bandColumn:has(.chatPanel.isUndocked) { z-index: 90; }
.chatPanel.isUndocked .chatLog { height: auto; flex: 1 1 auto; }
.chatPanel.isUndocked .chatCollapse { display: none; }
/* Touch / mobile "undock": a docked, full-screen chat instead of the floating draggable window (chat.js
   toggleFull). No positioning maths, no drag, no resize - it just fills the screen and the undock button
   toggles it. Sits above page chrome but below the site modal (2000). */
.chatPanel.isChatFull { position: fixed; inset: 0; z-index: 200; margin: 0; width: auto; height: auto; max-width: none; max-height: none; border: none; border-radius: 0; background: var(--bg); }
.chatPanel.isChatFull .chatLog { height: auto; flex: 1 1 auto; }
.chatPanel.isChatFull .chatCollapse, .chatPanel.isChatFull .chatResize { display: none; }
.chatPanel.isChatFull .chatHead { cursor: default; }
body.chatFullOpen { overflow: hidden; }   /* lock the page behind the fullscreen chat */
/* On touch/coarse-pointer devices the header is not a drag handle and the resize grip is gone (the undock
   button toggles fullscreen); restore normal cursor + touch scrolling on the header. */
@media (pointer: coarse) {
	.chatPanel:not(.isCollapsed) .chatHead { cursor: default; touch-action: auto; }
	.chatPanel .chatResize { display: none; }
}
/* The resize grip (docked AND floating): grab it on the docked panel and it lifts into the floating
   window at exactly its place, mid-gesture. Diagonal ticks, quiet until the panel is hovered. */
.chatPanel { position: relative; }
.chatPanel .chatResize { position: absolute; right: 2px; bottom: 2px; width: 16px; height: 16px; z-index: 5; cursor: nwse-resize; opacity: 0.35; transition: opacity 0.15s; touch-action: none;
	background: linear-gradient(135deg, transparent 0 55%, var(--fg-mute) 55% 60%, transparent 60% 70%, var(--fg-mute) 70% 75%, transparent 75% 85%, var(--fg-mute) 85% 90%, transparent 90%); }
.chatPanel:hover .chatResize { opacity: 0.85; }
.chatPanel.isCollapsed .chatResize { display: none; }
/* The header is a handle everywhere it can start a drag. */
.chatPanel:not(.isCollapsed) .chatHead { cursor: grab; touch-action: none; }
.chatPanel:not(.isCollapsed) .chatHead:active { cursor: grabbing; }
.chatHead { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; border-bottom: 1px solid var(--line-soft); }
.chatTitle { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase; color: var(--silver-1); }
.chatStatus { font-size: 11px; color: var(--fg-mute); letter-spacing: 0.5px; }
.chatHeadRight { display: inline-flex; align-items: center; gap: 8px; }
.chatIcon { display: inline-flex; align-items: center; justify-content: center; padding: 4px; background: none; border: none; color: var(--fg-mute); cursor: pointer; border-radius: 4px; transition: color 0.15s, background 0.15s; }
.chatIcon:hover { color: var(--fg); background: var(--bg-elev-2); }
.chatIcon svg { width: 17px; height: 17px; display: block; }
/* Collapse chevron points down when collapsed (click to open), flips up when expanded (click to close). */
.chatCollapse svg { transition: transform 0.15s; }
.chatPanel:not(.isCollapsed) .chatCollapse svg { transform: rotate(180deg); }
.chatLog { height: 320px; overflow-y: auto; padding: 12px 14px; display: flex; flex-direction: column; gap: 12px; }
.chatMsg { display: flex; gap: 10px; align-items: flex-start; }
.chatAvatar { width: 25px; height: 25px; flex: none; margin-top: 1px; border-radius: 50%; object-fit: contain; background: var(--bg-elev-2); box-shadow: 0 0 0 1px var(--line-soft); }
.chatAvatar.isCover { object-fit: cover; }
.chatBubble { min-width: 0; }
.chatMeta { display: flex; align-items: baseline; gap: 8px; margin-bottom: 2px; }
.chatName { font-size: 12.5px; font-weight: 700; color: var(--silver-2); }
.chatTime { font-size: 10.5px; color: var(--fg-mute); }
/* The poster's channel ("- black channel"): click to tune over and watch together (channels.js switch). */
.chatChan { font-size: 11px; color: var(--red-bright); text-decoration: none; white-space: nowrap; cursor: pointer; }
.chatChan:hover { text-decoration: underline; }
.chatBody { font-size: 13.5px; line-height: 1.4; color: var(--fg); overflow-wrap: anywhere; }
.chatBody a { color: var(--red-bright); text-decoration: underline; }
/* Inline YouTube embed: a thumbnail facade that swaps to the player on click (see chat.js addMessage). */
.chatYt { position: relative; display: block; width: 100%; max-width: 340px; aspect-ratio: 16 / 9; margin: 6px 0 2px; border-radius: 8px; overflow: hidden; background: #000; cursor: pointer; border: 1px solid var(--line); }
.chatYtThumb { width: 100%; height: 100%; object-fit: cover; display: block; }
.chatYtPlay { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 46px; height: 32px; border-radius: 7px; background: rgba(0, 0, 0, .6); transition: background .15s; }
.chatYt:hover .chatYtPlay { background: #cc1a1a; }
.chatYtPlay::after { content: ''; position: absolute; left: 52%; top: 50%; transform: translate(-50%, -50%); border-style: solid; border-width: 7px 0 7px 12px; border-color: transparent transparent transparent #fff; }
.chatYtFrame { width: 100%; height: 100%; border: 0; display: block; }
/* MetalTV bot: visually distinct (round logo badge, red name, muted italic line). */
.chatMsg.isBot .chatAvatar { background: #000; }
.chatMsg.isBot .chatName { color: var(--red-bright); }
.chatMsg.isBot .chatBody { color: var(--fg-dim); font-style: italic; }
.chatForm { display: flex; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--line-soft); }
.chatInput { flex: 1; min-width: 0; padding: 9px 12px; font: inherit; font-size: 13px; color: var(--fg); background: var(--bg-elev-2); border: 1px solid var(--line); border-radius: var(--radius); }
.chatInput::placeholder { color: var(--fg-mute); }
.chatInput:focus { outline: none; border-color: var(--red); }
.chatSend { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 40px; padding: 0; background: var(--bg-elev-2); border: 1px solid var(--line); border-radius: var(--radius); cursor: pointer; transition: border-color 0.15s, background 0.15s; }
.chatSend:hover { border-color: var(--red); background: rgba(212, 32, 47, 0.12); }
.chatSend img { height: 13px; width: auto; display: block; opacity: 0.35; transition: opacity 0.15s; }
.chatForm.hasText .chatSend img { opacity: 1; }

.nowField { margin-bottom: 16px; }
.fieldLabel { font-size: 11px; letter-spacing: 2.5px; text-transform: uppercase; color: var(--red); margin-bottom: 12px; }
.albumName { font-size: 19px; font-weight: 700; color: var(--silver-1); line-height: 1.2; }
.albumType { font-size: 12px; color: var(--fg-dim); margin-top: 2px; }
.songName { display: flex; align-items: center; gap: 10px; font-size: 17px; font-weight: 600; color: var(--fg); }
.songLength { font-size: 13px; color: var(--fg-mute); margin-top: 5px; letter-spacing: 0.3px; font-variant-numeric: tabular-nums; }
.releaseDate { font-size: 16px; color: var(--fg); margin-top: 10px; }
.nowPanel .albumArt { margin-bottom: 14px; }

/* ---------- Loading skeleton (2026-07-09): the band + song panel VALUES shimmer until the first
   /api/nowplaying response lands, so the panels read as loading instead of a scaffold of dashes.
   player.js drops .isTuning in renderNowPlaying (first response, song OR idle), so the shimmer only
   ever covers the initial gap. Text values only: the photo boxes collapse when empty (absolute
   fallback), so there is nothing to skeleton there. Deliberately does NOT touch the player. ---------- */
@keyframes mtvShimmer { to { background-position: -160% 0; } }

.bandPanel.isTuning .factVal,
.nowPanel.isTuning #songName,
.nowPanel.isTuning .albumName {
	color: transparent !important;
	position: relative;
}
.bandPanel.isTuning .factVal .flag { visibility: hidden; }
.nowPanel.isTuning .eqBars { display: none; }

.bandPanel.isTuning .factVal::after,
.nowPanel.isTuning #songName::after,
.nowPanel.isTuning .albumName::after {
	content: "";
	position: absolute; top: 50%; transform: translateY(-50%);
	height: 0.72em; border-radius: 4px;
	background: linear-gradient(90deg, rgba(255,255,255,0.05) 25%, rgba(255,255,255,0.13) 40%, rgba(255,255,255,0.05) 60%);
	background-size: 160% 100%;
	animation: mtvShimmer 1.25s ease-in-out infinite;
}
/* Band facts are right-aligned, so anchor the bar to the right; vary the widths so it reads as content. */
.bandPanel.isTuning .factVal::after { right: 0; width: 96px; }
.bandPanel.isTuning .factRow:nth-child(2n) .factVal::after { width: 68px; }
.bandPanel.isTuning .factRow:nth-child(3n) .factVal::after { width: 116px; }
/* Song + album are left-aligned. */
.nowPanel.isTuning #songName::after { left: 0; width: 150px; }
.nowPanel.isTuning .albumName::after { left: 0; width: 104px; }

/* Promotion: a subtle PROMOTED tag under the song, plus the CTA button row (CTA left of Save Track). */
.promotedTag { display: inline-block; margin-top: 8px; font-size: 11px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase; color: var(--red); }
.songActions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; }
.songActions .panelBtn { margin-top: 0; }
.schedPromoted { display: inline-block; margin-left: 6px; font-size: 9px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: var(--red); vertical-align: middle; }

/* ---------- Schedule panel: upcoming + recently played, tabbed (real-time, animated) ---------- */
/* Tabs are left-aligned, content-width (never stretched to 100%); the active one underlines in red. */
.schedTabs { display: flex; gap: 2px; margin-bottom: 14px; border-bottom: 1px solid var(--line-soft); }
.schedTab {
	display: inline-flex; align-items: center; gap: 8px;
	padding: 8px 14px 10px; margin-bottom: -1px;
	background: none; border: none; border-bottom: 2px solid transparent;
	font: inherit; font-size: 11px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase;
	color: var(--fg-dim); cursor: pointer; transition: color 0.15s, border-color 0.15s;
}
.schedTab svg { width: 15px; height: 15px; fill: currentColor; flex: none; }
.schedTab:hover { color: var(--fg); }
.schedTab.isActive { color: var(--silver-1); border-bottom-color: var(--red); }

.schedList { display: flex; flex-direction: column; gap: 10px; }
.schedItem {
	position: relative;   /* anchors the top-right Save heart on History rows */
	display: grid; grid-template-columns: 60px 1fr; gap: 12px; align-items: center;
	padding: 9px; background: var(--bg-elev-2); border: 1px solid var(--line-soft);
	border-radius: 6px; cursor: pointer; will-change: transform; opacity: 1;
	transition: opacity 0.35s ease, transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.15s, background 0.15s;
}
.schedItem:hover { border-color: var(--red); background: var(--bg-elev); }
.schedItem.isEnter { opacity: 0; transform: translateY(8px); }   /* newly added rows fade/slide in */
.schedArt { position: relative; width: 60px; height: 60px; flex: none; border-radius: 4px; overflow: hidden; background: var(--bg-elev); }
.schedArt img { width: 100%; height: 100%; object-fit: cover; display: block; }
.schedBody { min-width: 0; }
.schedBand { font-size: 14px; font-weight: 700; color: var(--silver-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.schedSong { font-size: 12.5px; color: var(--fg-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; }
/* Save Track on a History row: an icon-only heart pinned top-right (like the band page). Faint until the row
   or button is hovered, full opacity once saved. */
.schedSave { position: absolute; top: 6px; right: 6px; z-index: 2; display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; padding: 0; border: none; background: none; cursor: pointer; opacity: 0.4; transition: opacity 0.15s, transform 0.1s; }
.schedItem:hover .schedSave, .schedSave:hover, .schedSave:focus-visible { opacity: 1; }
.schedSave.isSaved { opacity: 1; }
.schedSave:active { transform: scale(0.88); }
.schedSaveIcon { width: 16px; height: 16px; display: block; }
/* Reserve room so a long band/song name truncates before the corner heart instead of running under it. */
#schedHistory .schedBand, #schedHistory .schedSong { padding-right: 26px; }
.schedChips { margin-top: 4px; line-height: 1.9; }
/* Promoted row CTA button: sits under the song name, above the genres. Content-width, left aligned. */
.schedCta { margin: 6px 0 2px; font-size: 11px; padding: 6px 11px; }
.schedTime { display: inline-flex; align-items: center; gap: 6px; margin-top: 5px; font-size: 11px; letter-spacing: 0.5px; color: var(--fg-mute); }
.schedTime svg { width: 12px; height: 12px; fill: #fff; flex: none; }
.schedEmpty { font-size: 12.5px; color: var(--fg-mute); padding: 4px 2px; }

/* Animated equaliser next to the now-playing song. */
.eqBars { display: inline-flex; align-items: flex-end; gap: 2px; height: 14px; }
.eqBars i { width: 3px; background: var(--red); transform-origin: bottom; animation: eq 0.9s ease-in-out infinite; }
.eqBars i:nth-child(1) { height: 40%; animation-delay: -0.2s; }
.eqBars i:nth-child(2) { height: 100%; animation-delay: -0.5s; }
.eqBars i:nth-child(3) { height: 60%; animation-delay: -0.1s; }
.eqBars i:nth-child(4) { height: 80%; animation-delay: -0.35s; }
@keyframes eq { 0%, 100% { transform: scaleY(0.4); } 50% { transform: scaleY(1); } }
/* Live mode: player.js drives the bars from the real audio (Web Audio analyser); the fake loop stops and
   all four bars share one base height so they rest even along the bottom (scaleY does the rest). */
.eqBars.isLive i { animation: none; height: 100%; }

/* ---------- Channel switcher (the bar under the chat + the fullscreen copy) ----------
   Cards read Title + Tagline from the Core channel catalogue (one source). channels.js drives arrows,
   drag-to-scroll and keeps the active card in view; switching is in-page (hls source swap). */
.chanBar { margin-top: 16px; }
.chanScroller { position: relative; }
.chanTrack { display: flex; gap: 10px; overflow-x: auto; scrollbar-width: none; flex: 1 1 auto; min-width: 0; padding: 2px; }
.chanTrack::-webkit-scrollbar { display: none; }
.chanCard {
	/* Flex-fill: on a wide screen the 7 cards grow to fill the row evenly (no arrows, no scrollbar); on a
	   narrow screen they hold min-width and the row scrolls. Height 100px, no fixed width. */
	flex: 1 1 150px; min-width: 150px; height: 100px; display: flex; flex-direction: column; justify-content: center;
	gap: 4px; padding: 12px 16px; background: rgba(20, 20, 23, 0.78); border: 1px solid var(--line);
	border-radius: var(--radius); cursor: pointer; user-select: none; -webkit-user-drag: none;
}
.chanCard:hover { border-color: var(--red); }
.chanCard.isActive { border-color: var(--red); background: linear-gradient(180deg, rgba(90, 20, 24, 0.5), rgba(20, 20, 23, 0.85)); }
.chanCardName { font-size: 17px; font-weight: 700; color: var(--silver-1); letter-spacing: 0.3px; }
.chanCardTag { font-size: 12px; color: var(--fg-dim); line-height: 1.35; }
.chanCardLive { display: none; align-items: center; gap: 6px; font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--red-bright); }
.chanCard.isActive .chanCardLive { display: inline-flex; }

/* What is on air on that channel (channels.js 30s poll). One line, never wraps: when the text does not
   fit it scrolls as a marquee (the text is duplicated so the loop is seamless). */
.chanCardNow { display: block; overflow: hidden; white-space: nowrap; font-size: 11px; color: var(--silver-2, #b9b9c0); margin-top: 1px; }
.chanCardNow .chanNowText { display: inline-block; }
/* Marquee (overflowing lines): ONE track holds both copies and slides -50% = exactly one copy + gap, so the
   loop is seamless and cannot drift (two independently-animated copies used to drift apart on sub-pixels). */
.chanCardNow.isMarquee .chanNowTrack { display: inline-block; white-space: nowrap; animation: chanMarquee var(--marqueeDur, 12s) linear infinite; }
.chanCardNow.isMarquee .chanNowText { padding-right: 36px; }
.chanCard:hover .chanCardNow .chanNowTrack { animation-play-state: paused; }
@keyframes chanMarquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.chanLiveDot { width: 7px; height: 7px; border-radius: 50%; background: var(--red-bright); box-shadow: 0 0 8px var(--red-glow); }
/* Arrows OVERLAY the track edges (absolute, out of flow) instead of sitting beside it as flex siblings.
   That keeps the track a CONSTANT width whether an arrow is shown or hidden, so the far end is a fixed
   target: scrolling fully right reliably reveals the last card and clears the right arrow. (As flex
   siblings, hiding an arrow widened the track and the "end" kept shifting ~36px, stranding the arrow.) */
.chanArrow { position: absolute; top: 50%; transform: translateY(-50%); z-index: 3; width: 32px; height: 46px; border: 1px solid var(--line); background: rgba(13, 13, 15, 0.92); color: var(--fg-dim); border-radius: var(--radius); cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.55); }
.chanArrowL { left: 0; }
.chanArrowR { right: 0; }
.chanArrow svg { width: 16px; height: 16px; }
.chanArrow:hover { color: var(--fg); border-color: var(--red); }

/* ---------- New Bands strip (watch pages, under the channel bar): fresh signups as image-topped tiles.
   Reuses the .chanScroller shell (arrows + drag via channels.js); these rules cover the panel + tiles. ---------- */
.newBandsPanel { margin-top: 30px; }
.newBandsTitle { font-weight: 700; font-size: 22px; margin: 10px 0; color: var(--silver-1); }
.newBandCard { flex: none; display: flex; flex-direction: column; width: 191.5px; background: rgba(20, 20, 23, 0.78); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; text-decoration: none; color: var(--fg); transition: border-color 0.15s; }
.newBandCard:hover { border-color: var(--red); }
.newBandImg { display: block; box-sizing: border-box; width: 100%; height: 130px; flex: none; padding: 8px; background: #0d0d0d; }
.newBandImg img { width: 100%; height: 100%; object-fit: contain; display: block; }
.newBandBody { display: flex; flex-direction: column; gap: 4px; padding: 9px 11px 11px; min-width: 0; }
.newBandName { display: block; font-weight: 700; font-size: 15px; margin-bottom: 5px; color: var(--silver-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.newBandMeta { display: block; margin-bottom: 10px; font-size: 11px; color: var(--fg-mute); }
.newBandGenres { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 1px; }
.newBandGenre { display: inline-block; font-size: 9.5px; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase; padding: 2px 6px; border-radius: 3px; background: rgba(212, 32, 47, 0.22); color: #fff; }
/* "On air now" / "up next {time} + {channel}" pill under the meta line, filled by newbands.js. A click
   tunes the reader to that channel (no hover movement per house rules: only colour shifts on hover). */
.newBandAiring { display: inline-flex; align-items: center; gap: 5px; align-self: flex-start; max-width: 100%; margin: 0 0 10px; padding: 3px 9px 3px 7px; border-radius: 999px; background: rgba(255, 255, 255, 0.06); border: 1px solid var(--line); font-size: 11px; font-weight: 600; color: var(--silver-1); cursor: pointer; transition: border-color 0.15s, color 0.15s, background 0.15s; }
.newBandAiring:hover { border-color: var(--red); color: #fff; background: rgba(255, 255, 255, 0.09); }
.newBandAiring .nbClock { width: 12px; height: 12px; flex: none; opacity: 0.85; }
.newBandAiring .nbAiringText { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.newBandAiring .nbAiringChan { color: var(--fg-mute); font-weight: 600; }
.newBandAiring.isLive { background: rgba(212, 32, 47, 0.16); border-color: rgba(212, 32, 47, 0.5); color: #fff; }
.newBandAiring.isLive:hover { background: rgba(212, 32, 47, 0.24); border-color: var(--red); }
.newBandAiring.isLive .nbAiringChan { color: rgba(255, 255, 255, 0.78); }
.nbLiveDot { width: 8px; height: 8px; flex: none; border-radius: 50%; background: var(--red); box-shadow: 0 0 0 0 rgba(212, 32, 47, 0.6); animation: nbLivePulse 1.8s ease-out infinite; }
@keyframes nbLivePulse { 0% { box-shadow: 0 0 0 0 rgba(212, 32, 47, 0.55); } 70% { box-shadow: 0 0 0 6px rgba(212, 32, 47, 0); } 100% { box-shadow: 0 0 0 0 rgba(212, 32, 47, 0); } }

/* New panel tabs (New Bands / New Songs) - reuse the Upcoming/History (schedTab) look, no icons. */
.newTabs { display: flex; gap: 2px; margin: 6px 0 14px; border-bottom: 1px solid var(--line-soft); }
.newTab {
	display: inline-flex; align-items: center;
	padding: 8px 14px 10px; margin-bottom: -1px;
	background: none; border: none; border-bottom: 2px solid transparent;
	font: inherit; font-size: 11px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase;
	color: var(--fg-dim); cursor: pointer; transition: color 0.15s, border-color 0.15s;
}
.newTab:hover { color: var(--fg); }
.newTab.isActive { color: var(--silver-1); border-bottom-color: var(--red); }
.newEmpty { padding: 14px 2px; color: var(--fg-mute); font-size: 13px; }

/* New Songs: a plain 3-per-row grid (no slider) of compact "art + Band - Song" rows. */
.newSongsGrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.newSongCard { display: flex; align-items: center; gap: 10px; min-height: 50px; padding: 6px; background: rgba(20, 20, 23, 0.78); border: 1px solid var(--line); border-radius: var(--radius); text-decoration: none; color: var(--fg); overflow: hidden; transition: border-color 0.15s; }
.newSongCard:hover { border-color: var(--red); }
.newSongArt { flex: none; width: 46px; height: 46px; border-radius: 4px; overflow: hidden; background: #0d0d0d; }
.newSongArt img { width: 100%; height: 100%; object-fit: cover; display: block; }
.newSongBody { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.newSongTitle { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.newSongBand { color: var(--silver-1); font-weight: 700; }
.newSongDash { color: var(--fg-mute); }
.newSongName { color: var(--fg-dim); }
.newSongMeta { font-size: 10.5px; color: var(--fg-mute); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.newSongCard .newBandAiring { margin: 3px 0 0; font-size: 10.5px; padding: 2px 8px 2px 6px; }
/* The same airing pill, reused inline in a band-profile track row (band-airing.js). */
.bpTrack .bpTrackAiring { margin: 0; align-self: center; flex: none; }
@media (max-width: 900px) { .newSongsGrid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .newSongsGrid { grid-template-columns: 1fr; } }

/* Volume readout flashed by the remote: just a big number in the corner (no box), over the video. */
.volOverlay { position: absolute; top: 16px; right: 24px; z-index: 40; color: #fff; text-shadow: 0 2px 16px rgba(0, 0, 0, .85), 0 0 4px rgba(0, 0, 0, .9); opacity: 0; transform: translateY(-6px); pointer-events: none; transition: opacity .18s, transform .18s; }
.volOverlay.show { opacity: 1; transform: translateY(0); }
.volOvNum { font-family: Oswald, sans-serif; font-weight: 700; font-size: 68px; line-height: 1; font-variant-numeric: tabular-nums; }
.volOvMute { font-family: Oswald, sans-serif; font-weight: 700; font-size: 34px; letter-spacing: 3px; }

/* The Channels dialog rows (site modal + the fullscreen sheet). */
.chanList { display: flex; flex-direction: column; gap: 8px; }
.chanRow { display: flex; align-items: center; gap: 12px; padding: 12px 14px; background: var(--bg-elev-2); border: 1px solid var(--line-soft); border-radius: var(--radius); }
.chanRow:hover { border-color: var(--red); }
.chanRow.isActive { border-color: var(--red); background: #241316; }
.chanDot { width: 9px; height: 9px; border-radius: 50%; background: var(--line); flex: none; }
.chanRow.isActive .chanDot { background: var(--red-bright); box-shadow: 0 0 8px var(--red-glow); }
.chanBody { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.chanName { font-weight: 700; color: var(--silver-1); font-size: 14.5px; }
.chanTag { font-size: 12px; color: var(--fg-mute); }
.chanNow { margin-left: auto; font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--red-bright); flex: none; }

/* Fullscreen channel switcher: bottom-right (opposite the band overlay), slides away with the control
   bar like the corner ad. Desktop shows a compact scroller; small screens swap it for a Channels button
   toggling a stacked sheet (the site modal cannot render inside the fullscreen element). Note: shares
   the corner with the fullscreen ad slot, which is currently disabled (promote.js CORNER_AD_ENABLED). */
.fsChannels { display: none; }
.player.isFullscreen .fsChannels {
	display: block; position: absolute; right: clamp(8px, 1vw, 18px); bottom: calc(var(--barH, 64px) + 14px);
	z-index: 6; max-width: min(46vw, 740px); transition: transform 0.25s, opacity 0.25s;
}
.player.controlsHidden .fsChannels { transform: translateY(calc(var(--barH, 64px) - 18px)); opacity: 0; pointer-events: none; }
.fsChannels .chanCard { flex: 0 0 auto; width: 170px; height: 88px; padding: 10px 12px; background: rgba(12, 12, 14, 0.72); }
.fsChannels .chanCardName { font-size: 14px; }
.fsChannels .chanCardTag { font-size: 10.5px; }
.fsChannels .chanCardNow { display: none; }   /* the compact fullscreen cards have no room for it */
.fsChanBtn { display: none; }
.fsChanSheet {
	position: absolute; right: 0; bottom: 46px; width: min(320px, 80vw); max-height: min(60vh, 480px);
	overflow-y: auto; background: rgba(12, 12, 14, 0.95); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px;
}
.fsChanSheet .modalTitle { font-size: 15px; margin: 0 0 10px; }
.fsChanSheet .modalRule, .fsChanSheet .modalSub { display: none; }
@media (max-width: 900px) {
	.player.isFullscreen .fsChanScroller { display: none; }
	.player.isFullscreen .fsChanBtn { display: inline-flex; }
}

/* ---------- Crawlable about + FAQ (under the player, middle column only, for SEO) ---------- */
.seoColumn { min-width: 0; }
.seoTitle { font-size: 24px; font-weight: 700; color: var(--silver-1); margin: 0 0 14px; letter-spacing: 0.3px; }
.seoIntro p { line-height: 1.7; font-size: 14px; color: var(--fg-dim); margin: 0 0 12px; }
.seoIntro a, .seoLink { color: var(--red-bright); text-decoration: none; }
.seoIntro a:hover, .seoLink:hover { text-decoration: underline; }
.seoLink { background: none; border: none; padding: 0; font: inherit; cursor: pointer; }

.faqSection { margin-top: 30px; }
/* The SEO intro + FAQ sit below the fold on the watch page. content-visibility lets the browser skip their
   layout + paint until they scroll near, so scrolling the player area stays light on weak GPUs. `auto` caches
   the real height after first view, so the scrollbar does not jump on later skips. */
.seoIntro, .faqSection { content-visibility: auto; contain-intrinsic-size: auto 400px; }
.seoHeading { font-size: 13px; text-transform: uppercase; letter-spacing: 2px; color: var(--red); margin: 0 0 14px; padding-bottom: 8px; border-bottom: 1px solid var(--line-soft); }
.faqItem { padding: 14px 0; border-bottom: 1px solid var(--line-soft); }
.faqItem:last-child { border-bottom: none; }
.faqQ { font-size: 15px; font-weight: 600; color: var(--silver-1); margin: 0 0 6px; }
.faqA { font-size: 14px; line-height: 1.65; color: var(--fg-dim); margin: 0; }

/* Tablet: player full width on top, the two side panels side by side beneath it, SEO full width last. */
@media (max-width: 1350px) {
	.watchLayout {
		grid-template-columns: 1fr 1fr;
		grid-template-areas:
			"player player"
			"band   now"
			"seo    seo";
		gap: 18px;
	}
}

/* Phone: a single column - player, then band, then now playing, then the SEO text last. */
@media (max-width: 991px) {
	.watchLayout {
		grid-template-columns: 1fr;
		grid-template-areas:
			"player"
			"band"
			"now"
			"seo";
		gap: 16px;
	}
}

@media (max-width: 640px) {
	.ctrlLabel { display: none; }
	.liveBadge { top: 8px; left: 8px; gap: 5px; padding: 3px 7px; font-size: 9px; letter-spacing: 1px; }
	.volSlider { display: none; }                 /* the device handles volume; the mute toggle remains */
	.controlBar { gap: 6px; padding: 24px 10px 11px; }
	.seoTitle { font-size: 21px; }
	/* Player bar: shrink the buttons so [count] + Genres + Request + flag fit; wrap as a fallback on tiny screens. */
	.playerBar { gap: 8px; padding: 8px 10px; flex-wrap: wrap; }
	.playerBar .freqBtn { padding: 6px 9px; font-size: 11px; gap: 6px; letter-spacing: 0.5px; }
	.playerBar .reportBtn { padding: 6px 8px; }
	.playerBar .freqBtn svg { width: 14px; height: 14px; }
	.viewerBadge { font-size: 11px; }
}

/* Compact fullscreen overlay on phones - portrait (narrow width) OR landscape (short height): the logo
   banner + Follow/Save would overflow the limited space, so drop them, sit flush left, square corners. */
@media (max-width: 640px), (max-height: 500px) {
	.nowOverlay { left: 0; bottom: 50px; gap: 8px; padding: 12px 14px; border-radius: 0; }
	.nowOverlay .ovBandPhoto { display: none; }
	.nowOverlay .ovActions { display: none; }
}

/* ---- Frequency: the live room genre mix steering the channel, its own box under the band box (Phase 4) ---- */
.freqBars { display: flex; flex-direction: column; gap: 10px; }
.freqRow { display: grid; grid-template-columns: 92px 1fr 38px; align-items: center; gap: 10px; will-change: transform; }
.freqName { font-size: 12px; color: var(--silver-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.freqTrack { height: 8px; background: var(--bg-elev-2); border-radius: 4px; overflow: hidden; }
.freqFill {
	display: block; height: 100%; width: 0;
	background: linear-gradient(90deg, #7d1019, var(--red-bright));
	border-radius: 4px; transition: width .6s cubic-bezier(.4, 0, .2, 1);
}
.freqRow.isMine .freqName { color: #fff; }
.freqRow.isMine .freqFill { box-shadow: 0 0 9px var(--red-glow); }
.freqPct { font-size: 11px; color: var(--fg-mute); text-align: right; font-variant-numeric: tabular-nums; }
.freqMeta { margin: 13px 0 0; font-size: 11px; color: var(--fg-mute); line-height: 1.45; }

/* "?" help link in the Active Genres kicker, pushed to the right edge of the row. */
.freqHelp {
	margin-left: auto; flex: none; width: 16px; height: 16px; border-radius: 50%;
	display: inline-flex; align-items: center; justify-content: center;
	font-family: Verdana, sans-serif; font-size: 11px; font-weight: 700; line-height: 1; letter-spacing: 0;
	color: var(--fg-mute); background: var(--bg-elev-2); text-decoration: none;
	transition: color .15s, background .15s;
}
.freqHelp:hover { color: #fff; background: var(--red); }

/* Empty state: no genres active in the room, so explain it and offer a Genres button (same .freqBtn as the
   one under the video, but left-aligned here instead of pushed to the right of the kicker row). */
.freqEmpty p { margin: 0 0 14px; font-size: 12px; color: var(--silver-2); line-height: 1.5; }
.freqEmpty .freqBtn { margin-left: 0; }

/* Join nudge: a one-off glow + shine on the Genres button under the video (same look as the chat's
   new-message glow + shine), fired ~10s after join by freq.js when the viewer has no picks yet. A single
   glow, not a repeating pulse. */
.freqBtn.freqPulse { position: relative; overflow: hidden; animation: chatGlow 1.9s ease-out 1; }
.freqBtn.freqPulse::after {
	content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 45%; pointer-events: none;
	background: linear-gradient(105deg, transparent, rgba(255, 255, 255, .12), transparent);
	transform: translateX(-130%); animation: chatShine 1.4s ease-out;
}
@media (prefers-reduced-motion: reduce) {
	.freqBtn.freqPulse { animation: none; }
	.freqBtn.freqPulse::after { display: none; }
}

/* ---------- Hangout (/hangout): a full-height social room reusing the player + chat + band/song panels.
   TWO parts, split by a drag-to-resize divider (hangout.js): LEFT = live chat + a "who's here" roster;
   RIGHT = the player + channel list on top, with the band + song info sleek beneath. Everything here is
   scoped under .hangout* so the shared band/song/player partials render normally on the home page. ------ */
.stage.stageHangout { max-width: none; padding: 0; }
/* App-style single screen: no page scroll on desktop; a slim footer bar sits under the room. */
@media (min-width: 1001px) { body.hangoutPage { overflow: hidden; } }
.hangoutFooterBar { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; padding: 7px 16px; margin: 0; background: rgba(12, 12, 14, 0.9); border-top: 1px solid var(--line); }
.hangoutFooterBar .hfCopy { font-size: 11px; color: var(--fg-dim); white-space: nowrap; }
.hangoutFooterBar .hfLinks { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.hangoutFooterBar a { color: var(--fg-dim); text-decoration: none; text-transform: uppercase; letter-spacing: .5px; font-size: 10.5px; }
.hangoutFooterBar a:hover { color: var(--fg); }
.hangoutFooterBar .footerRecaptcha { color: var(--fg-mute); text-transform: none; letter-spacing: 0; font-size: 10px; }
.hangoutLayout {
	display: grid;
	grid-template-columns: var(--hangoutChatW, 40vw) 10px minmax(0, 1fr);
	gap: 0;
	padding: 14px;
	box-sizing: border-box;
	height: calc(100vh - 104px);   /* hangout.js fits this exactly (minus the footer); pre-JS fallback */
	overflow: hidden;             /* columns scroll INSIDE; the chat never outgrows the screen */
	align-items: stretch;
}

/* Left half: the chat fills its column and the log scrolls inside it (never past the viewport). */
.hangoutChatCol { display: flex; flex-direction: column; min-height: 0; min-width: 0; padding-right: 14px; }
.hangoutChatCol .chatPanel { flex: 1 1 auto; min-height: 0; margin: 0; }
.hangoutChatCol .chatBodyRow { flex: 1 1 auto; min-height: 0; display: flex; }
.hangoutChatCol .chatMain { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.hangoutChatCol .chatMain .chatLog { flex: 1 1 auto; height: auto; min-height: 0; }
.hangoutChatCol .chatPanel.isCollapsed .chatBodyRow { display: none; }

/* The "who's here now" roster: the chat's LEFT column (source order puts it first). Resizable via the
   in-chat handle; it lives inside .chatPanel so it undocks WITH the chat when the panel floats. */
.chatRoster { flex: none; width: var(--chatUsersW, 150px); min-height: 0; overflow-y: auto; background: rgba(0, 0, 0, 0.16); }   /* no border: the resize handle's line is the single divider */
.chatRosterHead { position: sticky; top: 0; padding: 11px 12px 9px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; color: var(--fg-dim); background: rgba(18, 18, 21, 0.96); border-bottom: 1px solid var(--line-soft); }
.chatRosterCount { color: var(--red-bright); margin-left: 3px; }
.chatUserList { list-style: none; margin: 0; padding: 6px; display: flex; flex-direction: column; gap: 1px; }
.chatUser { display: flex; align-items: center; gap: 9px; padding: 5px 7px; border-radius: 7px; font-size: 13px; color: var(--fg); cursor: default; }
.chatUser:hover { background: rgba(255, 255, 255, 0.04); }
.chatUser img { width: 26px; height: 26px; border-radius: 7px; object-fit: contain; background: #000; flex: none; }
.chatUser img.isCover { object-fit: cover; }
.chatUserName { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chatUser.isMe .chatUserName { font-weight: 600; }   /* your own row stands out in the list */
.chatEmpty { padding: 10px 12px; color: var(--fg-dim); font-size: 12px; }

/* Drag handles: the in-chat one (user list vs messages) and the main one (chat vs player) share ONE look -
   a thin full-height line with a centred grip pill that lights red on hover/drag. */
.hangoutDivider, .chatUsersResize { position: relative; flex: none; align-self: stretch; cursor: col-resize; }
.chatUsersResize { width: 10px; }
.hangoutDivider::before, .chatUsersResize::before { content: ""; position: absolute; top: 0; bottom: 0; left: 50%; width: 1px; transform: translateX(-50%); background: var(--line); }
.hangoutDivider::after, .chatUsersResize::after { content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 4px; height: 40px; border-radius: 3px; background: var(--silver-2); opacity: .5; transition: opacity .15s, background .15s; }
.hangoutDivider:hover::after, .hangoutDivider:focus-visible::after, .hangoutDivider.isDragging::after,
.chatUsersResize:hover::after, .chatUsersResize:focus-visible::after, .chatUsersResize.isDragging::after { opacity: 1; background: var(--red-bright); }
.hangoutDivider:focus-visible, .chatUsersResize:focus-visible { outline: none; }

/* Chat message rows (concept art): avatar, name + time, body beneath, subtle hover highlight. */
.hangoutChatCol .chatMsg { display: flex; gap: 10px; align-items: flex-start; padding: 6px 10px; border-radius: 8px; }
.hangoutChatCol .chatMsg:hover { background: rgba(255, 255, 255, 0.03); }
.hangoutChatCol .chatAvatar { width: 34px; height: 34px; border-radius: 8px; object-fit: cover; background: #000; flex: none; margin-top: 2px; }
.hangoutChatCol .chatBubble { flex: 1 1 auto; min-width: 0; }
.hangoutChatCol .chatMeta { display: flex; align-items: baseline; gap: 8px; margin-bottom: 1px; }
.hangoutChatCol .chatName { font-weight: 600; font-size: 13px; color: #ececef; }
.hangoutChatCol .chatTime { font-size: 11px; color: var(--fg-dim); margin-left: auto; flex: none; }
.hangoutChatCol .chatBody { font-size: 13.5px; line-height: 1.45; color: rgba(228, 228, 233, 0.9); overflow-wrap: anywhere; }

/* Collapsed + docked: the whole left half becomes a slim vertical rail (title + expand/undock buttons);
   the divider hides and the player fills the freed width. Click the chevron to expand back. */
.hangoutLayout:has(.chatPanel.isCollapsed:not(.isUndocked)) { grid-template-columns: 46px 0 minmax(0, 1fr); }
.hangoutLayout:has(.chatPanel.isCollapsed:not(.isUndocked)) .hangoutDivider { display: none; }
.hangoutChatCol .chatPanel.isCollapsed .chatHead { flex-direction: column; align-items: center; gap: 14px; height: 100%; padding: 12px 0; }
.hangoutChatCol .chatPanel.isCollapsed .chatTitle { writing-mode: vertical-rl; transform: rotate(180deg); font-size: 12px; letter-spacing: 1px; opacity: .75; white-space: nowrap; }
.hangoutChatCol .chatPanel.isCollapsed .chatStatus { display: none; }
.hangoutChatCol .chatPanel.isCollapsed .chatHeadRight { flex-direction: column; gap: 10px; }

/* Undocked: the chat floats free; the left half + divider collapse to nothing so the player becomes one
   big window. The floating panel is position:fixed (a DOM descendant of the now-zero-width column, so it
   still renders over the page). A collapsed float shrinks to its title bar. */
.hangoutLayout:has(.chatPanel.isUndocked) { grid-template-columns: 0 0 minmax(0, 1fr); }
.hangoutLayout:has(.chatPanel.isUndocked) .hangoutChatCol { padding: 0; }
.hangoutLayout:has(.chatPanel.isUndocked) .hangoutDivider { display: none; }
.chatPanel.isUndocked.isCollapsed { height: auto !important; }
/* Collapse is available in drag mode too (the base hides it when floating; the hangout re-shows it). */
.hangoutChatCol .chatPanel.isUndocked .chatCollapse { display: inline-flex; }

body.hangoutResizing { cursor: col-resize; user-select: none; }

/* Right half: player + channels on top, band + song beneath. Scrolls internally if it ever overflows so
   the page itself never grows. */
.hangoutMainCol { display: flex; flex-direction: column; gap: 14px; min-width: 0; min-height: 0; padding-left: 14px; overflow: hidden; }
/* The player fills the space left after the channel bar + info strip. hangout.js measures that leftover
   height and sets --hangoutPlayerMaxW (= it, in 16:9), so the player is the largest 16:9 that fits, centred,
   and both "parts" of the page stay visible with no scrolling. */
.hangoutStage { flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center; }
.hangoutStage .player { width: 100%; max-width: var(--hangoutPlayerMaxW, 60vw); margin: 0 auto; }
.hangoutChanBar { flex: none; margin-top: 0; }

/* The now-playing card beneath the player: album art (left); song + band + country + genres + duration with
   Follow / Profile / Save together (middle); the band's social links (right). ONE slim panel. */
.hangoutInfo { flex: none; }
.hangoutInfoPanel { margin: 0; padding: 0; }
.hangoutInfoPanel .hiInner { display: flex; align-items: center; gap: 16px; padding: 12px 16px; }
.hangoutInfoPanel .hiArt { flex: none; width: 86px; height: 86px; margin: 0; }
.hangoutInfoPanel .hiArt img { width: 86px; height: 86px; border-radius: 6px; object-fit: cover; display: block; }
.hangoutInfoPanel .hiArt .photoFallback { width: 86px; height: 86px; min-height: 0; border-radius: 6px; }
.hangoutInfoPanel .hiText { flex: 1 1 auto; min-width: 0; }
.hangoutInfoPanel .hiSong { display: flex; align-items: baseline; gap: 8px; font-size: 18px; font-weight: 700; color: #fff; }
.hangoutInfoPanel .hiSong #songName { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hangoutInfoPanel .hiDur { font-size: 12px; font-weight: 400; color: var(--fg-dim); flex: none; }
.hangoutInfoPanel .hiBand { margin: 2px 0 0; font-size: 14px; font-weight: 600; }
.hangoutInfoPanel .hiBand a { color: var(--fg-dim); text-decoration: none; }
.hangoutInfoPanel .hiBand a:hover { color: #fff; }
.hangoutInfoPanel .hiMeta { display: flex; align-items: center; gap: 8px 12px; flex-wrap: wrap; margin-top: 6px; }
.hangoutInfoPanel .hiCountry { font-size: 12.5px; color: var(--fg-dim); display: inline-flex; align-items: center; gap: 6px; }
.hangoutInfoPanel .hiActions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.hangoutInfoPanel .hiLinks { flex: none; max-width: 42%; display: flex; flex-wrap: wrap; gap: 6px; align-content: center; justify-content: flex-end; }
.hangoutInfoPanel .hiKept { display: none !important; }

/* Bottom bar: full-width control strip above the footer. Settings + voice on the left; status on the right. */
.hangoutBar { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex: none; padding: 8px 16px; background: rgba(16, 16, 19, 0.92); border-top: 1px solid var(--line); }
.hbGroup { display: flex; align-items: center; gap: 12px; min-width: 0; }
.hbSep { width: 1px; align-self: stretch; background: var(--line); margin: 2px 0; }
.hbBtn { display: inline-flex; align-items: center; gap: 7px; background: var(--bg-elev-2); border: 1px solid var(--line); color: var(--fg); border-radius: 6px; padding: 7px 12px; font: inherit; font-size: 13px; cursor: pointer; transition: border-color .15s, color .15s; }
.hbBtn:hover:not(:disabled) { border-color: var(--red); color: #fff; }
.hbBtn:disabled { opacity: .5; cursor: default; }
.hbBtn svg { width: 16px; height: 16px; flex: none; }
.hbIconBtn { padding: 7px; }
.hbSettings { font-weight: 600; }
.hbVoice { display: flex; align-items: center; gap: 10px; }
.hbSectionLabel { font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: var(--fg-dim); }
.hbVol { display: flex; align-items: center; gap: 6px; color: var(--fg-dim); }
.hbVol svg { width: 16px; height: 16px; }
.hbSlider { width: 110px; accent-color: var(--red); }
.hbSoon { font-size: 10px; text-transform: uppercase; letter-spacing: 1px; color: var(--red-bright); border: 1px solid rgba(236, 65, 80, .4); border-radius: 999px; padding: 2px 8px; }
.hbConn { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--fg-mute); }
.hbDot { width: 8px; height: 8px; border-radius: 50%; background: var(--fg-mute); }

/* "Coming soon" blocks in the hangout settings dialog. */
.comingSoon { border: 1px dashed var(--line); border-radius: var(--radius); padding: 18px; background: rgba(0, 0, 0, 0.15); color: var(--fg-dim); }
.comingSoonTag { display: inline-block; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; color: var(--red-bright); border: 1px solid rgba(236, 65, 80, .4); border-radius: 999px; padding: 2px 9px; margin-bottom: 10px; }
.comingSoon p { margin: 0; line-height: 1.6; }

/* ---- Voice chat (2026-07-16): bottom-bar controls, roster speaking state, settings panel ---- */
/* Join / Leave: a red call-to-action when off, a green "connected" pill when in voice. */
.hbJoin { background: var(--red); border-color: var(--red); color: #fff; font-weight: 600; }
.hbJoin:hover:not(:disabled) { background: var(--red-bright); border-color: var(--red-bright); color: #fff; }
.hbJoin.isConnected { background: rgba(55, 196, 106, .16); border-color: rgba(55, 196, 106, .55); color: #cdeed6; }
.hbJoin.isConnected:hover:not(:disabled) { background: rgba(236, 65, 80, .16); border-color: var(--red); color: #fff; }
/* Music (channel) volume group, mirrors the voice group's layout. */
.hbMusic { display: flex; align-items: center; gap: 10px; }
/* Mute toggles (mic + music): a diagonal slash + red when muted. */
.hbMic, .hbMusicMute, .hbDeafen { position: relative; }
.hbBtn.isMuted { border-color: rgba(236, 65, 80, .6); color: var(--red-bright); }
.hbSlash { display: none; position: absolute; left: 50%; top: 50%; width: 22px; height: 2px; background: currentColor; border-radius: 2px; transform: translate(-50%, -50%) rotate(-45deg); pointer-events: none; }
.hbBtn.isMuted .hbSlash { display: block; }
/* a muted control stays red on hover (the generic hover turns icons white, which read as "unmuted") */
.hbBtn.isMuted:hover:not(:disabled) { color: var(--red-bright); border-color: rgba(236, 65, 80, .6); }
/* Camera toggle: green when broadcasting. */
.hbCam.isOn { border-color: rgba(55, 196, 106, .6); color: #8ee6a6; }
/* Connection dot states. */
@keyframes hbPulse { 0%, 100% { opacity: .4; } 50% { opacity: 1; } }
.hbConn[data-state="connecting"] .hbDot { background: #e0a92e; animation: hbPulse 1s infinite; }
.hbConn[data-state="connected"] .hbDot { background: #37c46a; box-shadow: 0 0 6px rgba(55, 196, 106, .8); }
.hbConn[data-state="connected"] { color: var(--fg); }
.hbConn[data-state="error"] .hbDot { background: var(--red-bright); }
.hbConn[data-state="error"] { color: var(--red-bright); }
/* Live mic-level meter in the bar (shown while connected; fed by the local VAD loop). */
.hbLevel { display: none; width: 34px; height: 8px; border-radius: 4px; background: rgba(255, 255, 255, .1); overflow: hidden; flex: none; }
.hbLevelFill { display: block; height: 100%; width: 0; background: linear-gradient(90deg, #4a8fe0, #37c46a); transition: width .05s linear; }

/* Roster voice state: a ring on the avatar (idle in-voice = faint, muted = red, speaking = green glow) plus a
   little mic-off badge when muted. Painted by voice.js matching participant id -> roster row data-uid. */
.chatUser img { border-radius: 50%; transition: box-shadow .12s; }
.chatUser.isAway img { box-shadow: 0 0 0 2px #e0b020; }   /* away: yellow ring (voice rings below override it when in voice) */
.chatUserAway { margin-left: 5px; font-weight: 300; font-size: 11px; color: var(--fg-mute); flex: none; }
.chatUser.inVoice img { box-shadow: 0 0 0 2px rgba(255, 255, 255, .22); }
.chatUser.isMutedVoice img { box-shadow: 0 0 0 2px rgba(236, 65, 80, .55); }
.chatUser.isSpeaking img { box-shadow: 0 0 0 2px #37c46a, 0 0 9px rgba(55, 196, 106, .75); }
.chatUserMic { display: none; margin-left: auto; width: 15px; height: 15px; flex: none; background: center/contain no-repeat; }
/* In voice + not muted -> a green mic; muted -> a red mic-off (the muted rule follows so it wins when both apply). */
.chatUser.inVoice .chatUserMic { display: block; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2337c46a' stroke-width='2.2' stroke-linecap='round'%3E%3Crect x='9' y='3' width='6' height='11' rx='3'/%3E%3Cpath d='M6 11a6 6 0 0012 0M12 17v4M8.5 21h7'/%3E%3C/svg%3E"); }
.chatUser.isMutedVoice .chatUserMic { display: block; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ec4150' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='M9 9V6a3 3 0 016 0v3'/%3E%3Cpath d='M6 11a6 6 0 009.5 4.9M12 17v4M8.5 21h7'/%3E%3Cpath d='M4 4l16 16'/%3E%3C/svg%3E"); }

/* Voice settings panel (the Voice tab of the hangout settings dialog). */
.voiceSettings { display: flex; flex-direction: column; gap: 14px; }
.vsPerm { border: 1px solid rgba(236, 65, 80, .4); background: rgba(236, 65, 80, .08); border-radius: var(--radius); padding: 14px; }
.vsPerm p { margin: 0 0 10px; color: var(--fg-dim); line-height: 1.5; }
.vsField { display: flex; flex-direction: column; gap: 6px; }
.vsLabel { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: var(--fg-dim); }
.vsSelect { background: var(--bg-elev-2); border: 1px solid var(--line); color: var(--fg); border-radius: 6px; padding: 9px 10px; font: inherit; font-size: 14px; }
.vsSelect:disabled { opacity: .5; }
.vsMeterHead { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.vsTestBtn { padding: 5px 12px; font-size: 13px; }
.vsTestBtn.isActive { border-color: var(--red); color: #fff; background: rgba(236, 65, 80, .16); }
.vsMeterWrap { position: relative; padding: 5px 0; cursor: ew-resize; touch-action: none; }
.vsMeter { height: 14px; border-radius: 7px; background: rgba(255, 255, 255, .07); box-shadow: inset 0 0 0 1px var(--line); overflow: hidden; }
.vsMeterFill { height: 100%; width: 0; border-radius: 7px 0 0 7px; background: linear-gradient(90deg, #4a8fe0, #6fb8ff); transition: width .05s linear; }
.vsMeter.isHot .vsMeterFill { background: linear-gradient(90deg, #37c46a, #b6e84f); }
.vsMeterThresh { position: absolute; top: 2px; bottom: 2px; left: 15%; width: 3px; margin-left: -1.5px; background: #fff; border-radius: 2px; box-shadow: 0 0 0 1px rgba(0, 0, 0, .55); cursor: ew-resize; }
.vsMeterThresh::after { content: ""; position: absolute; left: 50%; top: -5px; width: 11px; height: 11px; transform: translateX(-50%) rotate(45deg); background: #fff; border-radius: 2px; box-shadow: 0 0 0 1px rgba(0, 0, 0, .55); }
.vsTestHint { margin: 0; font-size: 12px; color: #b6e84f; }
.vsToggles { display: flex; flex-direction: column; gap: 10px; margin-top: 2px; }
.vsHint, .vsNote { margin: 0; font-size: 12px; color: var(--fg-mute); line-height: 1.5; }

/* ---- Cameras column: to the RIGHT of the chat, one cam per row, draggable width, speaking outline ---- */
.chatCams { flex: none; width: var(--chatCamsW, 220px); min-height: 0; overflow-y: auto; background: rgba(0, 0, 0, 0.16); }
.chatCamsHead { position: sticky; top: 0; padding: 11px 12px 9px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; color: var(--fg-dim); background: rgba(18, 18, 21, 0.96); border-bottom: 1px solid var(--line-soft); }
.chatCamsCount { color: var(--red-bright); margin-left: 3px; }
.camList { display: flex; flex-direction: column; gap: 8px; padding: 10px; }
.camTile { position: relative; width: 100%; aspect-ratio: 16 / 9; background: #000; border-radius: 8px; overflow: hidden; box-shadow: 0 0 0 1px var(--line); }
.camTile video { width: 100%; height: 100%; object-fit: cover; display: block; background: #000; }
.camTile.isSelf video { transform: scaleX(-1); }   /* mirror your own self-view */
.camTile.isSpeaking { box-shadow: 0 0 0 2px #37c46a, 0 0 10px rgba(55, 196, 106, .6); }
.camName { position: absolute; left: 6px; bottom: 6px; max-width: calc(100% - 12px); padding: 2px 7px; font-size: 11.5px; font-weight: 600; color: #fff; background: rgba(0, 0, 0, .6); border-radius: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* the cam divider shares the chat/user divider look */
.chatCamsResize { position: relative; flex: none; align-self: stretch; width: 10px; cursor: col-resize; }
.chatCamsResize::before { content: ""; position: absolute; top: 0; bottom: 0; left: 50%; width: 1px; transform: translateX(-50%); background: var(--line); }
.chatCamsResize::after { content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 4px; height: 40px; border-radius: 3px; background: var(--silver-2); opacity: .5; transition: opacity .15s, background .15s; }
.chatCamsResize:hover::after, .chatCamsResize:focus-visible::after, .chatCamsResize.isDragging::after { opacity: 1; background: var(--red-bright); }
.chatCamsResize:focus-visible { outline: none; }
/* Video settings preview (mirrored, like a webcam self-view) */
.videoSettings { display: flex; flex-direction: column; gap: 14px; }
.vcPreviewWrap { position: relative; width: 100%; aspect-ratio: 16 / 9; background: #000; border-radius: 8px; overflow: hidden; box-shadow: inset 0 0 0 1px var(--line); }
.vcPreview { width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); display: block; }
.vcPreviewOff { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--fg-mute); font-size: 13px; }

/* ---- Mobile / narrow: a voice-chat-app stack. The player is the "screen" up top, then the people (a
   horizontal avatar strip), then chat. The bottom bar wraps so every control stays reachable. If a viewer
   just wants the channel they can leave the hangout and watch live TV. ---- */
@media (max-width: 1000px) {
	.hangoutLayout { grid-template-columns: 1fr; height: auto; overflow: visible; }
	.hangoutDivider { display: none; }
	.hangoutMainCol { order: 1; overflow: visible; padding-left: 0; }
	.hangoutChatCol { order: 2; padding-right: 0; margin-top: 14px; }
	.hangoutChatCol .chatPanel { flex: none; }
	.hangoutChatCol .chatMain .chatLog { height: 320px; flex: none; }

	/* user list -> a horizontal avatar strip sitting above the messages (the resize handle is not used) */
	.hangoutChatCol .chatBodyRow { flex-direction: column; }
	.chatUsersResize { display: none; }
	.chatRoster { display: block; width: auto; min-height: 0; max-height: none; overflow: hidden; }
	.chatRoster .chatRosterHead { position: static; }
	.chatRoster .chatUserList { display: flex; flex-direction: row; flex-wrap: nowrap; gap: 12px; padding: 10px 12px; overflow-x: auto; }
	.chatRoster .chatUser { flex: none; flex-direction: column; align-items: center; width: 58px; gap: 5px; padding: 2px; }
	.chatRoster .chatUser img { width: 42px; height: 42px; }
	.chatRoster .chatUserName { max-width: 58px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; font-size: 11px; }
	.chatRoster .chatUserMic { display: none !important; }   /* the avatar ring carries voice state in the compact strip */
	.chatRoster .chatUserAway { display: none; }             /* the yellow ring carries "away" in the compact strip */

	/* cameras stack full-width below the chat as a compact grid; the resize divider is not used on mobile */
	.chatCamsResize { display: none; }
	.chatCams { display: block; width: auto; max-height: none; overflow: visible; }
	.chatCams .chatCamsHead { position: static; }
	.chatCams .camList { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 8px; }

	/* bottom bar wraps instead of overflowing off-screen */
	.hangoutBar { flex-wrap: wrap; gap: 10px 12px; }
	.hangoutBar .hbLeft { flex-wrap: wrap; gap: 8px 10px; }
	.hangoutBar .hbRight { margin-left: auto; }
}
@media (max-width: 640px) {
	/* trim the bar to fit a phone */
	.hbSectionLabel { display: none; }
	.hbSettings span { display: none; }
	.hbSep { display: none; }
	.hbSlider { width: 84px; }
	.hbLevel { width: 26px; }

	/* song / info panel: art + text on the first row, links wrap to a full-width row below */
	.hangoutInfoPanel .hiInner { flex-wrap: wrap; gap: 12px; }
	.hangoutInfoPanel .hiArt, .hangoutInfoPanel .hiArt img, .hangoutInfoPanel .hiArt .photoFallback { width: 64px; height: 64px; }
	.hangoutInfoPanel .hiText { flex: 1 1 150px; }
	.hangoutInfoPanel .hiLinks { max-width: none; flex-basis: 100%; justify-content: flex-start; }
}

/* ---- Hangout mini-games (2026-07-16): context menu, tabs, Scorched Earth board, invite dialog ---- */
/* Right-click context menu on the user list (Ubuntu-style: dark, rounded, red hover, fly-out submenu). */
.ctxMenu { position: fixed; z-index: 3000; min-width: 176px; padding: 5px; background: #2b2b2e; border: 1px solid rgba(255, 255, 255, .14); border-radius: 8px; box-shadow: 0 14px 40px rgba(0, 0, 0, .55); font-size: 13.5px; color: #eee; }
.ctxHead { padding: 6px 10px 8px; font-size: 11px; text-transform: uppercase; letter-spacing: .6px; color: var(--fg-mute); border-bottom: 1px solid rgba(255, 255, 255, .08); margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctxItem { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; text-align: left; padding: 8px 10px; border: 0; background: none; color: inherit; font: inherit; border-radius: 5px; cursor: pointer; }
.ctxItem:hover:not(.ctxDisabled), .ctxItem:focus-visible { background: var(--red); color: #fff; outline: none; }
.ctxItem.isActive { color: var(--red-bright); }
.ctxItem.isActive::after { content: "\2713"; font-size: 12px; }   /* tick the current status (turns white on hover, inherited) */
.ctxDisabled { color: var(--fg-mute); cursor: default; }
.ctxArrow { opacity: .7; font-size: 10px; }
/* Fly-out submenu. visibility+opacity with a close DELAY (not display:none) plus an invisible ::before
   bridge over the gap, so a diagonal mouse path from "Games" to the submenu can't drop it. */
.ctxSub { position: absolute; top: -6px; left: 100%; margin-left: 3px; min-width: 152px; padding: 5px; background: #2b2b2e; border: 1px solid rgba(255, 255, 255, .14); border-radius: 8px; box-shadow: 0 14px 40px rgba(0, 0, 0, .55); visibility: hidden; opacity: 0; transition: opacity .12s ease, visibility 0s linear .35s; }
.ctxSub::before { content: ""; position: absolute; top: 0; bottom: -10px; left: -14px; width: 14px; }
.ctxHasSub:hover > .ctxSub, .ctxHasSub:focus-within > .ctxSub { visibility: visible; opacity: 1; transition-delay: 0s; }

/* Main-area tabs (Watch / game). */
.hangoutTabs { display: flex; gap: 4px; flex: none; padding: 0 2px; }
.hgTab { display: inline-flex; align-items: center; gap: 8px; background: rgba(20, 20, 23, .55); border: 1px solid var(--line); border-bottom: none; color: var(--fg-dim); border-radius: 8px 8px 0 0; padding: 8px 14px; font: inherit; font-size: 13px; cursor: pointer; }
.hgTab.isActive { background: rgba(20, 20, 23, .92); color: #fff; }
.hgTabClose { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 50%; font-size: 15px; line-height: 1; opacity: .7; }
.hgTabClose:hover { background: var(--red); color: #fff; opacity: 1; }
.hangoutPane { min-height: 0; }
.hangoutPane[data-hg-pane="watch"] { flex: 1 1 auto; display: flex; flex-direction: column; gap: 14px; min-height: 0; }
.hangoutPane[data-hg-pane="game"] { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }   /* column: board fills, music bar tucks BELOW it (was row -> bar landed beside the board) */
.gameMount { flex: 1 1 auto; min-height: 0; width: 100%; }

/* Scorched Earth board. The canvas is a 1280x720 world scaled to FILL the stage (object-fit keeps 16:9). */
.seGame { display: flex; flex-direction: column; gap: 12px; height: 100%; min-height: 0; }
.seStage { position: relative; flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center; }
.seCanvas { display: block; width: 100%; height: 100%; object-fit: contain; border-radius: 8px; background: transparent; }
.seBanner { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; background: rgba(0, 0, 0, .6); border-radius: 8px; }
.seBannerText { font-size: 26px; font-weight: 800; color: #fff; text-shadow: 0 2px 12px rgba(0, 0, 0, .7); }
.seBannerBtns { display: flex; gap: 10px; }
.seBannerBtns .btn[disabled] { opacity: .55; cursor: default; }
.seBannerNote { font-size: 12.5px; color: var(--fg-mute); min-height: 1em; }
/* The control bar under the board (per the concept): one dark pill, ANGLE | POWER | WEAPON, with each
   control showing its bound key. Dimmed (still visible) when it is not your turn. */
.seBar { display: flex; align-items: stretch; justify-content: space-between; flex: none; width: 100%; max-width: 780px; margin: 0 auto; background: #121214; border: 1px solid var(--line); border-radius: 999px; padding: 10px 26px; transition: opacity .2s; }
.seBar.isDisabled { opacity: .45; }
.seBar.isDisabled .seChev, .seBar.isDisabled .sePowerBar { pointer-events: none; }
.seSec { display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 0; }
.seKick { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 1.3px; color: var(--fg-mute); }
.seSecBody { display: flex; align-items: center; gap: 8px; }
.seChev { background: none; border: 0; color: var(--fg-dim); font-size: 24px; line-height: 1; padding: 0 6px; cursor: pointer; }
.seChev:hover { color: #fff; }
.seVal { min-width: 56px; text-align: center; font-variant-numeric: tabular-nums; font-weight: 700; font-size: 19px; color: #fff; }
.sePowerBar { position: relative; width: 230px; height: 20px; border-radius: 10px; background: rgba(255, 255, 255, .08); cursor: pointer; touch-action: none; }
.sePowerFill { height: 100%; border-radius: 10px; background: var(--red); transition: width .06s linear; }
.sePowerPct { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; color: #fff; text-shadow: 0 1px 3px rgba(0, 0, 0, .7); pointer-events: none; }
.seWeapon { font-size: 16px; font-weight: 700; color: #fff; }
.seKeys { display: inline-flex; gap: 4px; font-size: 11px; color: var(--fg-mute); border: 1px solid var(--line); border-radius: 5px; padding: 2px 7px; background: rgba(255, 255, 255, .04); white-space: nowrap; }
.seKbd { font-size: 11px; color: rgba(255, 255, 255, .85); border: 1px solid rgba(255, 255, 255, .35); border-radius: 4px; padding: 1px 6px; margin-left: 2px; font-family: inherit; }
.seFire { border-radius: 999px; font-weight: 700; padding: 8px 18px; display: inline-flex; align-items: center; gap: 6px; }
.seDiv { width: 1px; background: var(--line); margin: 4px 20px; }
/* Weapon dock: bottom-left of the board (Quake-style icon chips; click to switch, badge = ammo left). */
.seWeapons { display: flex; gap: 10px; justify-content: center; margin: 10px 0 2px; }   /* a row below the board (floating over it hid tanks in deep valleys) */
.seWepChip { position: relative; width: 52px; height: 52px; border-radius: 10px; background: rgba(10, 10, 13, .8); border: 1px solid var(--line); display: flex; align-items: center; justify-content: center; padding: 0; cursor: pointer; transition: border-color .15s, box-shadow .15s, opacity .15s; }
.seWepChip svg { width: 30px; height: 30px; }
.seWepChip:hover:not(:disabled) { border-color: rgba(255, 255, 255, .5); }
.seWepChip.isSelected { border-color: var(--wep, var(--red)); box-shadow: 0 0 12px var(--wep, rgba(236, 65, 80, .55)); }
.seWepChip:disabled { opacity: .35; cursor: default; }
.seSec.isNA { opacity: .35; pointer-events: none; }   /* power section greyed for hitscan weapons + fuel */
.seWepAmmo { position: absolute; right: -6px; top: -8px; min-width: 21px; padding: 1px 5px; border-radius: 999px; background: #17171a; border: 1px solid var(--line); font-size: 11px; font-weight: 800; color: #fff; text-align: center; line-height: 1.5; }
/* Custom weapon-name bubble on hover (replaces the native title tooltip); pops above the chip. */
.seWepChip::after { content: attr(data-name); position: absolute; left: 50%; bottom: calc(100% + 10px); transform: translateX(-50%) translateY(3px); padding: 4px 9px; border-radius: 7px; background: #17171a; border: 1px solid var(--line); color: #fff; font-size: 12px; font-weight: 700; white-space: nowrap; pointer-events: none; opacity: 0; transition: opacity .12s ease, transform .12s ease; z-index: 30; }
.seWepChip::before { content: ""; position: absolute; left: 50%; bottom: calc(100% + 5px); width: 9px; height: 9px; transform: translateX(-50%) rotate(45deg); background: #17171a; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); pointer-events: none; opacity: 0; transition: opacity .12s ease; z-index: 30; }
.seWepChip:hover::after { opacity: 1; transform: translateX(-50%) translateY(0); }
.seWepChip:hover::before { opacity: 1; }
/* Game-tab music bar: compact channel switch + now-playing, so players change the music without leaving the game. */
.gameMusicBar { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin: 10px 0 2px; padding: 8px 12px; background: rgba(10, 10, 13, .7); border: 1px solid var(--line); border-radius: 10px; }
.gmbChannels { display: flex; flex-wrap: wrap; gap: 6px; }
.gmbChan { padding: 4px 11px; border-radius: 999px; border: 1px solid var(--line); background: rgba(255, 255, 255, .03); color: var(--fg-mute); font-size: 12px; font-weight: 700; text-decoration: none; cursor: pointer; transition: color .12s, border-color .12s, background .12s; }
.gmbChan:hover { color: #fff; border-color: rgba(255, 255, 255, .4); }
.gmbChan.isActive { color: #fff; background: var(--red); border-color: var(--red); }
.gmbNow { display: flex; align-items: center; gap: 8px; min-width: 0; color: var(--fg); font-size: 12.5px; font-weight: 600; }
.gmbNowDot { width: 8px; height: 8px; border-radius: 50%; background: #37c46a; box-shadow: 0 0 8px rgba(55, 196, 106, .8); flex: none; animation: gmbPulse 2s ease-in-out infinite; }
#gmbNowText { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@keyframes gmbPulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
/* Controls legend: bottom-right of the board, a little keyboard of the (rebindable) keys. */
.seKeyhelp { position: absolute; right: 14px; bottom: 14px; z-index: 5; display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 9px 12px 8px; background: rgba(10, 10, 13, .8); border: 1px solid var(--line); border-radius: 10px; user-select: none; }
.khClose { position: absolute; top: 2px; right: 4px; width: 18px; height: 18px; display: flex; align-items: center; justify-content: center; padding: 0; border: 0; background: none; color: var(--fg-mute); font-size: 16px; line-height: 1; cursor: pointer; border-radius: 4px; }
.khClose:hover { color: #fff; background: rgba(255, 255, 255, .12); }
.khTitle { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 1.4px; color: var(--fg-mute); padding: 0 10px; }
.khGrid { display: flex; flex-direction: column; align-items: center; gap: 5px; }
.khRow { display: flex; gap: 5px; justify-content: center; }
.kcap { display: inline-flex; align-items: center; justify-content: center; min-width: 34px; height: 34px; padding: 0 7px; font-family: inherit; font-size: 14px; font-weight: 700; color: #e6e9ec; background: linear-gradient(#34353b, #23242a); border: 1px solid #474954; border-bottom-width: 3px; border-radius: 7px; box-shadow: 0 1px 0 rgba(0, 0, 0, .4); }
.kcapWide { width: 122px; gap: 7px; }
.kcapTag { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .6px; color: var(--fg-mute); }
.kcapKey { font-size: 13px; font-weight: 700; color: #e6e9ec; }
.khHint { font-size: 10.5px; color: var(--fg-mute); }
@media (max-width: 1000px) { .seKeyhelp { display: none; } }   /* touch/mobile uses the on-screen buttons */

/* Challenge invite dialog + toast + game-sound slider. */
.gameInviteOverlay { position: fixed; inset: 0; z-index: 3300; display: flex; align-items: center; justify-content: center; background: rgba(0, 0, 0, .55); }
.gameInviteCard { width: min(360px, 92vw); background: var(--bg-elev); border: 1px solid var(--line); border-radius: 12px; padding: 22px; text-align: center; box-shadow: 0 24px 70px rgba(0, 0, 0, .6); }
.giIcon { font-size: 30px; margin-bottom: 8px; }
.giMsg { font-size: 15px; color: #fff; margin-bottom: 16px; line-height: 1.5; }
.giActions { display: flex; gap: 10px; justify-content: center; }
.gameToast { position: fixed; left: 50%; bottom: 92px; transform: translate(-50%, 12px); z-index: 3200; background: #2b2b2e; border: 1px solid rgba(255, 255, 255, .14); color: #fff; padding: 10px 16px; border-radius: 8px; box-shadow: 0 12px 34px rgba(0, 0, 0, .5); font-size: 13.5px; opacity: 0; transition: opacity .25s, transform .25s; }
.gameToast.isIn { opacity: 1; transform: translate(-50%, 0); }
.gsSlider { width: 100%; }

@media (max-width: 1000px) {
	.seGame { height: auto; }
	.seStage { height: 56vw; max-height: 70vh; }   /* the stage needs a concrete height once the page scrolls */
	.seBar { flex-wrap: wrap; justify-content: center; gap: 10px 18px; border-radius: 20px; padding: 10px 16px; }
	.seDiv { display: none; }
	.sePowerBar { width: 170px; }
}

/* Game-sound volume group in the bar (shown only while a game is running). */
.hbGame { display: flex; align-items: center; gap: 10px; }
/* Key-rebinding UI in the Games settings tab. */
.gameKeys { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.gkRow { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.gkLabel { color: var(--fg-dim); font-size: 14px; }
.gkKey { min-width: 92px; padding: 7px 12px; background: var(--bg-elev-2); border: 1px solid var(--line); color: #fff; border-radius: 6px; font: inherit; font-size: 13px; cursor: pointer; text-align: center; }
.gkKey:hover { border-color: var(--red); }
.gkKey.isRebinding { border-color: var(--red); color: var(--red-bright); }
.gkReset { align-self: flex-start; }
.gameSettings .vsNote { margin-top: 18px; }
