/* ============================================================
         THEME TOKENS — three skins driving one layout.
         Existing var names (--accent/--good/--ink/…) are preserved so
         inline styles set by the game JS re-theme automatically.
         ============================================================ */
:root {
	/* default = Upside Down (also see [data-theme="upside"]) */
	--bg: #050b0a;
	--bg-2: #0c1d18;
	--panel: rgba(10, 26, 22, 0.6);
	--panel-2: rgba(14, 34, 28, 0.62);
	--story-bg: rgba(6, 16, 13, 0.66);
	--ink: #dcefe6;
	--muted: #7fa695;
	--line: rgba(90, 160, 130, 0.22);
	--accent: #ff3b3b;
	--accent-2: #7adcc3;
	--good: #61e2a2;
	--glow: rgba(255, 59, 59, 0.35);
	--grad-1: linear-gradient(120deg, #ff3b3b, #b56576 45%, #7adcc3);
	--grad-hero: radial-gradient(120% 130% at 50% 0%, rgba(255, 59, 59, 0.12), transparent 70%);
	--panel-brd: rgba(90, 160, 130, 0.28);
	--panel-glow:
		0 18px 48px rgba(0, 4, 3, 0.6), 0 0 34px rgba(255, 59, 59, 0.06), inset 0 1px 0 rgba(200, 255, 230, 0.05);
	--btn-ink: #04100c;
	--chip: rgba(255, 59, 59, 0.12);
	--chip-brd: rgba(255, 59, 59, 0.4);
	--scroll-thumb: rgba(90, 160, 130, 0.4);
	--font-display: "Space Grotesk", sans-serif;
	--font-body: "Space Grotesk", sans-serif;
	--font-story: "Newsreader", serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
	--tsn: 0.5s cubic-bezier(0.6, 0.01, 0.05, 1);
	/* The editorial revamp's radius scale: sharp panels, not pillowy cards.
	   Pills (99px/999px) stay pills — that's the metadata grammar. */
	--r-lg: 8px;
	--r-md: 6px;
	--r-sm: 4px;
	/* The bottom strip carrying theme peek + the tip jar. Everything else
	   pinned to the bottom of the window clears itself by this. */
	--footbar-h: 32px;
}
[data-theme="neon"] {
	--bg: #0a0616;
	--bg-2: #140a2e;
	--panel: rgba(30, 16, 66, 0.5);
	--panel-2: rgba(43, 25, 86, 0.55);
	--story-bg: rgba(16, 9, 32, 0.6);
	--ink: #f4ecff;
	--muted: #b9a9e6;
	--line: rgba(168, 120, 255, 0.22);
	--accent: #ff3ea5;
	--accent-2: #22d3ee;
	--good: #37e0a0;
	--glow: rgba(255, 62, 165, 0.4);
	--grad-1: linear-gradient(120deg, #ff3ea5, #a855f7 45%, #22d3ee);
	--grad-hero: radial-gradient(
		120% 140% at 50% 0%,
		rgba(255, 62, 165, 0.18),
		rgba(34, 211, 238, 0.04) 55%,
		transparent 75%
	);
	--panel-brd: rgba(168, 120, 255, 0.28);
	--panel-glow:
		0 20px 50px rgba(10, 4, 25, 0.5), 0 0 40px rgba(120, 60, 220, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.05);
	--btn-ink: #0a0616;
	--chip: rgba(255, 62, 165, 0.14);
	--chip-brd: rgba(255, 62, 165, 0.4);
	--scroll-thumb: rgba(168, 120, 255, 0.5);
	--font-display: "Orbitron", sans-serif;
	--font-body: "Space Grotesk", sans-serif;
	--font-story: "Space Grotesk", sans-serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="aurora"] {
	--bg: #faf3ff;
	--bg-2: #eef1ff;
	--panel: rgba(255, 255, 255, 0.55);
	--panel-2: rgba(255, 255, 255, 0.62);
	--story-bg: rgba(255, 255, 255, 0.5);
	--ink: #3a2a55;
	--muted: #7c6a99;
	--line: rgba(180, 150, 220, 0.35);
	--accent: #e35d9c;
	--accent-2: #6f7dff;
	--good: #2fa06f;
	--glow: rgba(227, 93, 156, 0.3);
	--grad-1: linear-gradient(120deg, #ff9ecb, #c17bff 50%, #8b9dff);
	--grad-hero: linear-gradient(135deg, rgba(255, 158, 203, 0.28), rgba(139, 157, 255, 0.2));
	--panel-brd: rgba(255, 255, 255, 0.85);
	--panel-glow: 0 20px 55px rgba(180, 140, 220, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.9);
	--btn-ink: #ffffff;
	--chip: rgba(193, 123, 255, 0.13);
	--chip-brd: rgba(193, 123, 255, 0.35);
	--scroll-thumb: rgba(193, 123, 255, 0.4);
	--font-display: "Fraunces", serif;
	--font-body: "Plus Jakarta Sans", sans-serif;
	--font-story: "Fraunces", serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="ink"] {
	--bg: #efe6d4;
	--bg-2: #e6dcc4;
	--panel: rgba(255, 251, 240, 0.72);
	--panel-2: rgba(250, 244, 230, 0.8);
	--story-bg: rgba(252, 247, 235, 0.75);
	--ink: #33291d;
	--muted: #7a6a4c;
	--line: rgba(120, 95, 60, 0.24);
	--accent: #b5502e;
	--accent-2: #5c7a52;
	--good: #4f7a3f;
	--glow: rgba(181, 80, 46, 0.28);
	--grad-1: linear-gradient(120deg, #b5502e, #c08a2d 55%, #5c7a52);
	--grad-hero: radial-gradient(120% 120% at 50% 0%, rgba(192, 138, 45, 0.16), transparent 70%);
	--panel-brd: rgba(120, 95, 60, 0.22);
	--panel-glow: 0 14px 34px rgba(90, 66, 35, 0.14), inset 0 1px 0 rgba(255, 255, 255, 0.6);
	--btn-ink: #fffaf0;
	--chip: rgba(181, 80, 46, 0.1);
	--chip-brd: rgba(181, 80, 46, 0.3);
	--scroll-thumb: rgba(120, 95, 60, 0.35);
	--font-display: "Fraunces", serif;
	--font-body: "Newsreader", serif;
	--font-story: "Newsreader", serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}

* {
	box-sizing: border-box;
}
body {
	margin: 0;
	min-height: 100vh;
	overflow-x: hidden;
	background: var(--bg);
	color: var(--ink);
	font-family: var(--font-body);
	display: flex;
	justify-content: center;
	padding: 20px 0 var(--footbar-h);
	transition:
		background var(--tsn),
		color var(--tsn);
}
::selection {
	background: var(--accent);
	color: var(--btn-ink);
}
::-webkit-scrollbar {
	width: 10px;
	height: 10px;
}
::-webkit-scrollbar-thumb {
	background: var(--scroll-thumb);
	border-radius: var(--r-lg);
}
::-webkit-scrollbar-track {
	background: transparent;
}

/* ---------- Background layers (fixed, cross-fade per theme) ---------- */
/* Strictly BEHIND all UI: negative z-index, and no descendant (animated
   sprites, vines, fleets, …) may ever intercept a click. */
.bg-layers {
	position: fixed;
	inset: 0;
	z-index: -1;
	overflow: hidden;
	pointer-events: none;
}
.bg-layers,
.bg-layers * {
	pointer-events: none !important;
}
.bg-wash {
	position: absolute;
	inset: 0;
	transition: background var(--tsn);
	background:
		radial-gradient(80% 60% at 15% -5%, var(--bg-2), transparent 60%),
		radial-gradient(70% 60% at 100% 0%, var(--bg-2), transparent 55%);
	opacity: 0.9;
}
.bg-set {
	position: absolute;
	inset: 0;
	opacity: 0;
	transition: opacity 0.7s ease;
}
[data-theme="neon"] .bg-set.neon {
	opacity: 1;
}
[data-theme="aurora"] .bg-set.aurora {
	opacity: 1;
}
[data-theme="ink"] .bg-set.ink {
	opacity: 1;
}
/* neon */
.bg-set.neon .glow-sun {
	position: absolute;
	left: 50%;
	top: 2%;
	width: 640px;
	height: 640px;
	transform: translateX(-50%);
	border-radius: 50%;
	background: radial-gradient(circle, rgba(255, 62, 165, 0.35), transparent 62%);
	filter: blur(30px);
}
.bg-set.neon .sun {
	position: absolute;
	left: 50%;
	top: 8%;
	width: 520px;
	height: 520px;
	transform: translateX(-50%);
	border-radius: 50%;
	background: linear-gradient(#ffde59, #ff3ea5 55%, #a855f7);
	filter: blur(2px);
	/* half a sun, and nothing else: the retro slats are gone, so the disc is a
	   clean gradient cut off at the horizon rather than a set of blinds. */
	-webkit-mask: linear-gradient(#000 60%, transparent 60%);
	mask: linear-gradient(#000 60%, transparent 60%);
	opacity: 0.8;
}
/* The horizon ripples. Two rings spread outward along the line where the grid
   meets the sky (the grid's top edge sits at 45%), so the edge reads as water
   under the sun instead of a hard seam. They hold still for a visitor who
   asked for less motion. */
.bg-set.neon .ripple {
	position: absolute;
	left: 0;
	right: 0;
	top: 45%;
	height: 200px;
	transform: translateY(-50%);
	-webkit-mask: radial-gradient(120% 100% at 50% 50%, #000 25%, transparent 78%);
	mask: radial-gradient(120% 100% at 50% 50%, #000 25%, transparent 78%);
}
.bg-set.neon .ripple::before,
.bg-set.neon .ripple::after {
	content: "";
	position: absolute;
	left: 50%;
	top: 50%;
	width: 360px;
	height: 36px;
	margin: -18px 0 0 -180px;
	border-radius: 50%;
	border: 1px solid rgba(34, 211, 238, 0.5);
	box-shadow: 0 0 14px rgba(255, 62, 165, 0.3);
	opacity: 0.3;
	filter: blur(0.4px);
}
.bg-set.neon .ripple::after {
	transform: scale(2);
	opacity: 0.16;
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.neon .ripple::before,
	.bg-set.neon .ripple::after {
		animation: horizon-ripple 7s ease-out infinite;
	}
	.bg-set.neon .ripple::after {
		animation-delay: 3.5s;
	}
}
@keyframes horizon-ripple {
	from {
		transform: scale(0.35);
		opacity: 0;
	}
	18% {
		opacity: 0.55;
	}
	to {
		transform: scale(3.6);
		opacity: 0;
	}
}
.bg-set.neon .grid {
	position: absolute;
	left: -25%;
	right: -25%;
	bottom: -10%;
	height: 65%;
	background-image:
		linear-gradient(rgba(34, 211, 238, 0.5) 1px, transparent 1px),
		linear-gradient(90deg, rgba(168, 120, 255, 0.4) 1px, transparent 1px);
	background-size: 56px 56px;
	transform: perspective(340px) rotateX(68deg);
	transform-origin: 50% 100%;
	-webkit-mask: linear-gradient(transparent, #000 40%);
	mask: linear-gradient(transparent, #000 40%);
	animation: gridmove 2.4s linear infinite;
}
@keyframes gridmove {
	to {
		background-position: 0 56px;
	}
}
.bg-set.neon .stars {
	position: absolute;
	inset: 0;
	opacity: 0.5;
	background-image:
		radial-gradient(1px 1px at 20% 20%, #fff, transparent), radial-gradient(1px 1px at 70% 15%, #fff, transparent),
		radial-gradient(1.5px 1.5px at 40% 30%, #fff, transparent), radial-gradient(1px 1px at 85% 25%, #fff, transparent),
		radial-gradient(1px 1px at 55% 8%, #fff, transparent);
}
.bg-set.neon .scan {
	position: absolute;
	inset: 0;
	mix-blend-mode: overlay;
	opacity: 0.45;
	background: repeating-linear-gradient(transparent 0 3px, rgba(0, 0, 0, 0.18) 3px 4px);
}
/* aurora */
.bg-set.aurora .orb {
	position: absolute;
	border-radius: 50%;
	filter: blur(60px);
	opacity: 0.75;
}
.bg-set.aurora .o1 {
	width: 520px;
	height: 520px;
	left: -6%;
	top: -10%;
	background: radial-gradient(circle, #ff9ecb, transparent 65%);
}
.bg-set.aurora .o2 {
	width: 600px;
	height: 600px;
	right: -10%;
	top: 0;
	background: radial-gradient(circle, #8b9dff, transparent 65%);
}
.bg-set.aurora .o3 {
	width: 520px;
	height: 520px;
	left: 30%;
	bottom: -20%;
	background: radial-gradient(circle, #c17bff, transparent 65%);
}
.bg-set.aurora .o4 {
	width: 360px;
	height: 360px;
	right: 20%;
	bottom: -8%;
	background: radial-gradient(circle, #a0f0e0, transparent 65%);
}
/* ink */
.bg-set.ink .paper {
	position: absolute;
	inset: 0;
	opacity: 0.5;
	background-image: linear-gradient(rgba(120, 95, 60, 0.06) 1px, transparent 1px);
	background-size: 100% 30px;
}
.bg-set.ink .vignette {
	position: absolute;
	inset: 0;
	box-shadow: inset 0 0 260px rgba(80, 55, 20, 0.35);
}
.bg-set.ink .blot {
	position: absolute;
	border-radius: 50%;
	filter: blur(70px);
	opacity: 0.4;
}
.bg-set.ink .b1 {
	width: 440px;
	height: 440px;
	left: -8%;
	top: -6%;
	background: radial-gradient(circle, #c08a2d, transparent 65%);
}
.bg-set.ink .b2 {
	width: 400px;
	height: 400px;
	right: -6%;
	bottom: -10%;
	background: radial-gradient(circle, #5c7a52, transparent 65%);
}
.grain {
	position: absolute;
	inset: 0;
	opacity: 0.05;
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- Hero landing ---------- */
.hero {
	position: fixed;
	inset: 0;
	z-index: 80;
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--bg);
	background-image: var(--grad-hero);
	text-align: center;
	padding: 20px;
}
.hero.hidden {
	display: none;
}
.hero-inner {
	max-width: 900px;
}
.hero-svg {
	width: min(760px, 92vw);
	overflow: visible;
	margin: 0 0 10px;
	filter: drop-shadow(0 6px 30px var(--glow));
}
.hero-svg .hero-word-svg {
	font-family: var(--font-display);
	font-weight: 900;
	font-size: 104px;
	letter-spacing: 3px;
	text-anchor: middle;
	fill: url(#heroGrad);
	stroke: url(#heroGrad);
	stroke-width: 1.6;
	paint-order: stroke;
}
.hero-svg .hg1 {
	stop-color: var(--accent);
}
.hero-svg .hg2 {
	stop-color: var(--accent-2);
}
.hero-svg .hg3 {
	stop-color: var(--good);
}
.hero-tag {
	color: var(--muted);
	font-family: var(--font-mono);
	font-size: clamp(0.85rem, 2vw, 1.05rem);
	margin: 0 0 34px;
}
.hero-tag {
	cursor: pointer;
}
.hero-tag:hover {
	color: var(--ink);
}
.type-caret {
	display: inline-block;
	width: 2px;
	height: 1.1em;
	background: var(--accent-2);
	margin-left: 3px;
	vertical-align: -3px;
	animation: blink 1s steps(1) infinite;
}
.features-modal {
	position: fixed;
	inset: 0;
	z-index: 90;
	background: rgba(0, 0, 0, 0.55);
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 24px;
}
.features-modal.hidden {
	display: none;
}
.features-card {
	max-width: 640px;
	max-height: 84vh;
	overflow-y: auto;
	background: var(--panel);
	border: 1px solid var(--panel-brd);
	border-radius: var(--r-lg);
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(18px);
	-webkit-backdrop-filter: blur(18px);
	padding: 28px;
}
.features-card h2 {
	font-family: var(--font-display);
	margin: 0 0 6px;
	background: var(--grad-1);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}
.feat-sub {
	color: var(--muted);
	font-family: var(--font-mono);
	font-size: 0.82rem;
	margin: 0 0 16px;
}
.feat-list {
	list-style: none;
	padding: 0;
	margin: 0 0 6px;
}
.feat-list li {
	padding: 7px 0;
	border-bottom: 1px solid var(--line);
	font-size: 0.9rem;
	line-height: 1.5;
}
.feat-list li:last-child {
	border-bottom: 0;
}
.hero-btns {
	display: flex;
	justify-content: center;
}
.hero-btns button {
	flex: none;
	min-width: 220px;
	font-size: 1.1rem;
	padding: 16px 34px;
}

/* ---------- Dashboard (logged in) ---------- */
/* A live game is a lit thing: a rail down its left edge with a bulb of the
   theme's accent running the length of it, an aura that swells as the bulb
   passes, a low sheen crossing the surface, a pulsing pip on the online
   count — and on hover the row wakes (lifts, glows, the Join fills in).
   Every colour is a theme token, so each theme lights its own way. */
.live-game {
	--lg-i: 0;
	--lg-run: 5.2s;
	position: relative;
	display: flex;
	align-items: center;
	gap: 10px;
	border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--line));
	border-radius: var(--r-md);
	padding: 12px 12px 12px 22px;
	margin-bottom: 8px;
	background:
		linear-gradient(100deg, color-mix(in srgb, var(--accent) 22%, transparent) 0%, transparent 46%), var(--panel-2);
	overflow: hidden;
	isolation: isolate;
	transition:
		border-color 0.4s ease,
		transform 0.4s cubic-bezier(0.2, 0.8, 0.3, 1),
		box-shadow 0.4s ease;
}
.live-game .lg-rail {
	position: absolute;
	left: 0;
	top: 0;
	bottom: 0;
	width: 3px;
	background: linear-gradient(
		180deg,
		color-mix(in srgb, var(--accent) 30%, transparent),
		color-mix(in srgb, var(--accent) 60%, transparent),
		color-mix(in srgb, var(--accent) 30%, transparent)
	);
}
.live-game .lg-bulb {
	position: absolute;
	left: -3px;
	width: 9px;
	height: 64%;
	top: -90%;
	border-radius: 6px;
	background: linear-gradient(180deg, transparent, var(--accent-2) 45%, var(--accent-2) 55%, transparent);
	box-shadow:
		0 0 14px 4px var(--glow),
		0 0 60px 22px color-mix(in srgb, var(--glow) 55%, transparent);
	animation: lg-bulb var(--lg-run) cubic-bezier(0.45, 0, 0.55, 1) infinite;
	animation-delay: calc(var(--lg-i) * 0.78s);
}
@keyframes lg-bulb {
	0% {
		top: -90%;
		opacity: 0;
	}
	12% {
		opacity: 1;
	}
	72% {
		opacity: 1;
	}
	88% {
		top: 118%;
		opacity: 0;
	}
	100% {
		top: 118%;
		opacity: 0;
	}
}
.live-game .lg-aura {
	position: absolute;
	inset: 0;
	background: radial-gradient(65% 140% at 0% 50%, color-mix(in srgb, var(--accent-2) 22%, transparent), transparent 70%);
	opacity: 0;
	animation: lg-aura var(--lg-run) ease-in-out infinite;
	animation-delay: calc(var(--lg-i) * 0.78s);
	pointer-events: none;
}
@keyframes lg-aura {
	0%,
	100% {
		opacity: 0;
	}
	45% {
		opacity: 1;
	}
}
.live-game .lg-sheen {
	position: absolute;
	inset: 0;
	background: linear-gradient(
		104deg,
		transparent 34%,
		color-mix(in srgb, var(--ink) 8%, transparent) 47%,
		color-mix(in srgb, var(--ink) 14%, transparent) 50%,
		color-mix(in srgb, var(--ink) 8%, transparent) 53%,
		transparent 66%
	);
	transform: translateX(-140%);
	animation: lg-sheen 9.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
	animation-delay: calc(var(--lg-i) * 1.4s + 0.6s);
	pointer-events: none;
}
@keyframes lg-sheen {
	0% {
		transform: translateX(-140%);
	}
	42% {
		transform: translateX(140%);
	}
	100% {
		transform: translateX(140%);
	}
}
.live-game .lg-info > b {
	transition: text-shadow 0.4s ease;
}
.live-game .lg-pip {
	display: inline-block;
	width: 5px;
	height: 5px;
	margin-right: 5px;
	border-radius: 50%;
	background: var(--accent-2);
	vertical-align: 1px;
	animation: lg-pip 2.4s ease-out infinite;
}
@keyframes lg-pip {
	0% {
		box-shadow: 0 0 0 0 var(--glow);
	}
	70% {
		box-shadow: 0 0 0 7px transparent;
	}
	100% {
		box-shadow: 0 0 0 0 transparent;
	}
}
.live-game > button {
	position: relative;
	overflow: hidden;
	transition:
		background 0.35s ease,
		border-color 0.35s ease,
		box-shadow 0.35s ease,
		color 0.35s ease;
}
.live-game > button::after {
	content: "";
	position: absolute;
	inset: 0;
	background: linear-gradient(100deg, transparent 40%, rgba(255, 255, 255, 0.35) 50%, transparent 60%);
	transform: translateX(-160%);
	transition: transform 0.7s cubic-bezier(0.4, 0, 0.2, 1);
}
.live-game:hover {
	border-color: color-mix(in srgb, var(--accent-2) 55%, transparent);
	transform: translateY(-2px);
	box-shadow:
		0 0 0 1px color-mix(in srgb, var(--accent-2) 12%, transparent),
		0 10px 34px -12px var(--glow),
		0 0 70px -20px var(--glow);
}
.live-game:hover .lg-info > b {
	text-shadow: 0 0 18px var(--glow);
}
.live-game:hover .lg-aura {
	animation-duration: calc(var(--lg-run) * 0.55);
}
.live-game:hover > button {
	background: linear-gradient(180deg, var(--accent-2), var(--accent));
	border-color: var(--accent-2);
	color: var(--bg);
	box-shadow: 0 0 24px -4px var(--glow);
}
.live-game:hover > button::after {
	transform: translateX(160%);
}
@media (prefers-reduced-motion: reduce) {
	.live-game .lg-bulb,
	.live-game .lg-sheen,
	.live-game .lg-aura,
	.live-game .lg-pip {
		animation: none;
	}
	.live-game .lg-aura {
		opacity: 0.7;
	}
	.live-game .lg-bulb {
		top: 18%;
		opacity: 1;
	}
	.live-game .lg-sheen {
		display: none;
	}
}
/* Solo writes as rows (profile + dashboard; write-view.js soloRowHtml): the
   same shape as .live-game. A locked row is someone's private write — listed
   because it exists, dimmed because it isn't yours to open. */
.solo-row {
	display: flex;
	align-items: center;
	gap: 10px;
	position: relative; /* anchors the .solo-date corner chip */
	border-radius: var(--r-md);
	padding: 22px 12px 10px; /* room up top for the date chip */
	margin-bottom: 8px;
	background: var(--panel-2);
}
/* the last-updated date rides the top of every solo write, card or row */
.doc-card .doc-date,
.solo-row .solo-date {
	position: absolute;
	top: 6px;
	right: 10px;
	font-size: 0.68rem;
	line-height: 1;
	padding: 3px 7px;
	border-radius: 8px;
	background: rgba(0, 0, 0, 0.35);
	color: var(--muted);
}
.solo-row.locked {
	opacity: 0.6;
}
.solo-info {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 3px;
	text-align: left;
}
.solo-title {
	font-weight: 700;
	color: var(--ink);
	text-decoration: none;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
a.solo-title:hover {
	text-decoration: underline;
}
.solo-meta {
	font-size: 0.75rem;
	color: var(--muted);
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px;
}
.solo-acts {
	flex: 0 0 auto;
	display: flex;
	gap: 6px;
}
.solo-acts .ghost,
.solo-acts a.ghost {
	min-width: 0;
	width: auto;
	flex: 0 0 auto;
	padding: 6px 10px;
	font-size: 0.8rem;
	text-decoration: none;
	display: inline-flex;
	align-items: center;
}
.solo-lock {
	font-size: 0.78rem;
	color: var(--muted);
	white-space: nowrap;
}
/* a private write on someone else's library page: shown, not openable */
.game-card.locked {
	opacity: 0.6;
}
/* a game gathering writers calls out to everyone not in it: pulsing glow
   (CSS) + GSAP sparkles (dashboard script) */
.live-game.gathering {
	position: relative;
	border-color: var(--accent-2);
	overflow: hidden;
}
@media (prefers-reduced-motion: no-preference) {
	.live-game.gathering {
		animation: gather-glow 1.8s ease-in-out infinite;
	}
}
@keyframes gather-glow {
	0%,
	100% {
		box-shadow: 0 0 4px 0 var(--glow);
	}
	50% {
		box-shadow: 0 0 20px 4px var(--glow);
	}
}
.live-game .sparkle {
	position: absolute;
	pointer-events: none;
	color: var(--accent-2);
	font-size: 11px;
	opacity: 0;
	text-shadow: 0 0 8px var(--glow);
}
.live-game .lg-info {
	flex: 1;
	min-width: 0;
	font-size: 0.86rem;
	text-align: left;
}
.live-game .lg-sub {
	display: block;
	color: var(--muted);
	font-family: var(--font-mono);
	font-size: 0.7rem;
	margin-top: 2px;
}
.live-game button {
	flex: none;
	min-width: 0;
	padding: 8px 16px;
}
.dash-h {
	margin: 18px 0 8px !important;
}
/* the little + beside a section heading (e.g. Solo Writes → new write) */
.dash-h .h-plus {
	flex: none;
	min-width: 0;
	width: 24px;
	height: 24px;
	padding: 0;
	display: grid;
	place-items: center;
	border-radius: 50%;
	font-size: 1rem;
	line-height: 1;
}

/* ---------- Top bar: signed-in user chip + theme switcher ---------- */
.topbar {
	position: fixed;
	top: 16px;
	right: 72px; /* hamburger owns the corner */
	z-index: 50;
	display: flex;
	align-items: flex-start;
	gap: 10px;
}
.user-chip {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 9px 14px;
	border-radius: var(--r-md);
	background: var(--panel);
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(14px);
	-webkit-backdrop-filter: blur(14px);
	font-size: 0.85rem;
	font-weight: 700;
	white-space: nowrap;
	/* it is a link to your own profile — but it must not read as body text */
	text-decoration: none;
	color: inherit;
	cursor: pointer;
	transition:
		border-color 0.2s,
		transform 0.12s;
}
.user-chip:hover {
	border-color: var(--accent-2);
	transform: translateY(-1px);
}
.user-chip.hidden {
	display: none;
}
@media (max-width: 600px) {
	.user-chip {
		display: none;
	}
}
.theme-switch {
	position: relative;
	perspective: 700px;
}
.theme-switch button {
	flex: 0 0 auto;
	min-width: auto;
	display: flex;
	align-items: center;
	gap: 7px;
	font-family: var(--font-body);
	font-size: 12px;
	font-weight: 600;
	color: var(--muted);
	background: transparent;
	border: 0;
	padding: 7px 12px;
	border-radius: var(--r-md);
	cursor: pointer;
	transition:
		color 0.3s,
		background 0.3s;
}
.theme-switch button .sw {
	width: 14px;
	height: 14px;
	border-radius: 5px;
	flex: none;
}
.sw-neon {
	background: linear-gradient(120deg, #ff3ea5, #22d3ee);
}
.sw-aurora {
	background: linear-gradient(120deg, #ff9ecb, #8b9dff);
}
.sw-ink {
	background: linear-gradient(120deg, #b5502e, #5c7a52);
}
.theme-toggle {
	color: var(--ink) !important;
	background: var(--panel) !important;
	border: 1px solid var(--panel-brd) !important;
	border-radius: var(--r-md) !important;
	padding: 9px 14px !important;
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(14px);
	-webkit-backdrop-filter: blur(14px);
}
.theme-toggle .chev {
	font-style: normal;
	font-size: 10px;
	color: var(--muted);
	transition: transform 0.3s;
}
.theme-switch.open .theme-toggle .chev {
	transform: rotate(180deg);
}
.theme-menu {
	position: absolute;
	top: calc(100% + 8px);
	right: 0;
	/* 19 themes is taller than a laptop window: the list scrolls inside
	   itself, and sits above the write page's sticky toolbar (which is a
	   later sibling once the topbar is moved into the document head). */
	z-index: 120;
	min-width: 150px;
	max-height: min(60vh, 430px);
	overflow-y: auto;
	overscroll-behavior: contain;
	display: flex;
	flex-direction: column;
	gap: 3px;
	padding: 6px;
	border-radius: var(--r-md);
	background: var(--panel);
	border: 1px solid var(--panel-brd);
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(14px);
	-webkit-backdrop-filter: blur(14px);
	transform-origin: 50% 0;
	visibility: hidden;
	opacity: 0;
	transform: rotateX(-90deg);
	transition:
		opacity 0.3s,
		transform 0.35s ease-out,
		visibility 0s 0.35s;
}
.theme-switch.open .theme-menu {
	visibility: visible;
	opacity: 1;
	transform: rotateX(0deg);
	transition-delay: 0s;
}
.theme-menu button {
	width: 100%;
	flex: 0 0 auto;
	text-align: left;
}
.theme-menu button.active {
	color: var(--ink);
	background: var(--chip);
}
/* the Font row at the foot of the theme menu (see chrome.js TOPBAR) */
.theme-menu .theme-sep {
	flex: 0 0 auto;
	width: 100%;
	margin: 3px 0;
	border: 0;
	border-top: 1px solid var(--panel-brd);
}
.theme-menu .theme-font-row {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 4px 8px 4px 10px;
}
.theme-menu .theme-font-row label {
	margin: 0;
	font-family: var(--font-body);
	font-size: 12px;
	color: var(--muted);
	white-space: nowrap;
}
.theme-menu .theme-font-row .theme-font-pick {
	flex: 1 1 auto;
	min-width: 0;
}
.theme-menu .theme-font-row .flip-select {
	width: 100%;
}
.theme-menu .theme-font-row .flip-toggle {
	width: 100%;
	padding: 5px 8px;
	font-size: 12px;
}
@media (max-width: 760px) {
	.topbar {
		top: 12px;
		right: 62px;
	}
	/* the fixed controls (theme switch, ☰) need their own band on a phone:
	   without it they sit on the page title */
	body {
		padding-top: 72px;
	}
}
/* "Forgot your password? · Forgot your username?": whole links wrap, never a
   phrase mid-way, and the dot goes when they stack */
.auth-links {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: center;
	gap: 4px 10px;
}
.auth-links a {
	white-space: nowrap;
}
@media (max-width: 480px) {
	.auth-links {
		flex-direction: row;
		gap: 8px 12px;
	}
	.auth-links .dot {
		display: none;
	}
}

/* ---------- flip-select: the theme switcher's dropdown, reusable ----------
   Same fold, one difference that is the whole point: this menu is OPAQUE. The
   theme menu can afford to be translucent — a colour swatch reads through
   anything — but a typeface preview cannot: letterforms over a busy background
   are exactly the thing you were trying to judge. `--flip-bg` is a solid
   panel colour, so every face is read against one flat surface. */
.flip-select {
	position: relative;
	perspective: 700px;
	display: inline-flex;
	--flip-bg: color-mix(in srgb, var(--bg-2) 92%, var(--ink) 8%);
}
.flip-toggle {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	max-width: 190px;
	font-family: var(--font-body);
	font-size: 12px;
	font-weight: 600;
	color: var(--ink);
	background: var(--chip);
	border: 1px solid var(--chip-brd);
	border-radius: var(--r-md);
	padding: 6px 10px;
	cursor: pointer;
}
.flip-toggle:hover {
	border-color: var(--accent-2);
}
.flip-cur {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.flip-toggle .chev {
	font-style: normal;
	font-size: 10px;
	opacity: 0.7;
	transition: transform 0.3s ease;
}
.flip-select.open .chev {
	transform: rotate(180deg);
}
.flip-menu {
	position: absolute;
	top: calc(100% + 8px);
	left: 0;
	z-index: 130;
	min-width: 200px;
	max-height: min(60vh, 420px);
	overflow-y: auto;
	overscroll-behavior: contain;
	display: flex;
	flex-direction: column;
	gap: 3px;
	padding: 6px;
	border-radius: var(--r-md);
	background: var(--flip-bg); /* opaque on purpose — see above */
	border: 1px solid var(--panel-brd);
	box-shadow: var(--panel-glow);
	transform-origin: 50% 0;
	visibility: hidden;
	opacity: 0;
	transform: rotateX(-90deg);
	transition:
		opacity 0.3s,
		transform 0.35s ease-out,
		visibility 0s 0.35s;
}
.flip-select.open .flip-menu,
.flip-menu.open {
	visibility: visible;
	opacity: 1;
	transform: rotateX(0deg);
	transition-delay: 0s;
}
/* lifted out to <body> while open (portal option) — fixed at the toggle's
   spot, above the topbar's menus, and scrollable within the room it has */
.flip-menu.flip-portal {
	position: fixed;
	z-index: 140;
	perspective: 700px;
}
.flip-menu button {
	width: 100%;
	text-align: left;
	font-size: 14px;
	line-height: 1.5;
	color: var(--ink);
	background: transparent;
	border: 0;
	border-radius: 9px;
	padding: 7px 10px;
	cursor: pointer;
}
.flip-menu button:hover {
	background: var(--chip);
}
.flip-menu button.active {
	background: var(--chip);
	box-shadow: inset 0 0 0 1px var(--chip-brd);
}

/* ---------- Hamburger + slide-in nav drawer ---------- */
.hamburger {
	position: fixed;
	top: 16px;
	right: 16px;
	z-index: 70;
	width: 44px;
	height: 42px;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 5px;
	background: var(--panel);
	border: 0;
	border-radius: var(--r-md);
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(14px);
	-webkit-backdrop-filter: blur(14px);
	cursor: pointer;
	padding: 0;
	flex: none;
	min-width: 0;
}
.hamburger:hover {
	transform: translateY(-1px);
}
.hamburger .bar {
	display: block;
	width: 20px;
	height: 2px;
	border-radius: 2px;
	background: var(--ink);
	pointer-events: none;
}
.nav-scrim {
	position: fixed;
	inset: 0;
	z-index: 60;
	background: rgba(0, 0, 0, 0.45);
	opacity: 0;
	visibility: hidden;
}
.nav-drawer {
	position: fixed;
	top: 0;
	right: 0;
	bottom: 0;
	z-index: 65;
	width: min(300px, 85vw);
	/* the foot bar (z 100) sits over the drawer (z 65): the drawer's padding
	   clears it so the last link is never hidden under it, and the list
	   scrolls inside the drawer on a short screen */
	padding: 76px 18px calc(var(--footbar-h, 0px) + 18px);
	overflow-y: auto;
	overscroll-behavior: contain;
	background: var(--panel);
	border-left: 1px solid var(--panel-brd);
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(18px);
	-webkit-backdrop-filter: blur(18px);
	transform: translateX(100%);
	visibility: hidden;
	display: flex;
	flex-direction: column;
	gap: 8px;
}
.nav-drawer .nav-title {
	font-family: var(--font-mono);
	font-size: 0.7rem;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: var(--muted);
	margin: 0 0 4px 6px;
}
.nav-drawer button {
	flex: none;
	width: 100%;
	text-align: left;
	background: var(--panel-2);
	color: var(--ink);
	border: 1px solid var(--line);
	font-size: 1rem;
}
.nav-drawer button:hover {
	border-color: var(--accent-2);
}
.nav-drawer a {
	flex: none;
	width: 100%;
	text-align: left;
	background: var(--panel-2);
	color: var(--ink);
	border: 1px solid var(--line);
	border-radius: var(--r-md);
	padding: 12px 16px;
	font-size: 1rem;
	text-decoration: none;
	box-sizing: border-box;
}
.nav-drawer a:hover {
	border-color: var(--accent-2);
}
/* the AO3 skin previewer: a door out to its own site, so it glows */
.nav-drawer a.nav-glow {
	border-color: color-mix(in srgb, var(--accent-2) 55%, transparent);
	animation: nav-glow 2.6s ease-in-out infinite;
}
@keyframes nav-glow {
	0%,
	100% {
		box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-2) 0%, transparent);
	}
	50% {
		box-shadow: 0 0 14px 1px color-mix(in srgb, var(--accent-2) 45%, transparent);
	}
}
@media (prefers-reduced-motion: reduce) {
	.nav-drawer a.nav-glow {
		animation: none;
		box-shadow: 0 0 10px 0 color-mix(in srgb, var(--accent-2) 35%, transparent);
	}
}
/* No focus ring on menu links — the accent border is the focus cue, like hover. */
.nav-drawer a:focus,
.nav-drawer button:focus {
	outline: none;
}
.nav-drawer a:focus-visible,
.nav-drawer button:focus-visible {
	border-color: var(--accent-2);
}
/* logout pinned to the drawer's bottom-right corner */
/* Log out is a plain text row pinned to the top of the drawer — icon + word,
   not a button — so it stays put while the menu scrolls beneath it. */
.nav-drawer .nav-logout {
	position: sticky;
	z-index: 1;
	margin: 0 0 6px;
	width: 100%;
	min-width: 0;
	padding: 6px 6px 8px;
	background: var(--panel);
	border: 0;
	border-radius: 0;
	box-shadow: none;
	color: var(--muted);
	font-size: 0.9rem;
	text-align: left;
	transform: none;
	cursor: pointer;
}
.nav-drawer .nav-logout:hover {
	color: var(--accent-2);
	border: 0;
	background: var(--panel);
	transform: none;
	box-shadow: none;
}
/* The current page is marked by ink alone — no ring around the row. */
.nav-drawer a[aria-current="page"] {
	color: var(--accent-2);
}

/* ---------- Badge unlock: centered pop + confetti rain ---------- */
.badge-pop {
	position: fixed;
	left: 50%;
	top: 42%;
	transform: translate(-50%, -50%);
	z-index: 82;
	max-width: min(420px, 88vw);
	background: var(--panel);
	border: 1px solid var(--chip-brd);
	border-radius: var(--r-lg);
	padding: 20px 28px;
	box-shadow:
		var(--panel-glow),
		0 0 60px var(--glow);
	backdrop-filter: blur(14px);
	-webkit-backdrop-filter: blur(14px);
	display: grid;
	gap: 5px;
	text-align: center;
	pointer-events: none;
	opacity: 0;
}
.badge-pop b {
	font-size: 1.05rem;
}
.badge-pop span {
	color: var(--muted);
	font-size: 0.84rem;
}
/* what the rank handed out — themes, gimmicks — reads as the reward, not a footnote */
.badge-pop .badge-unlocks {
	color: var(--accent);
	font-weight: 600;
	margin-top: 4px;
}
/* no-GSAP pages (e.g. archive delete) show/hide the pop with CSS alone */
.badge-pop.show {
	opacity: 1;
	animation: toast-in 0.4s cubic-bezier(0.2, 0.9, 0.3, 1.2);
	transition: opacity 0.7s;
}
.badge-pop.show.gone {
	opacity: 0;
}
.confetti {
	position: fixed;
	left: 50%;
	top: 42%;
	z-index: 81; /* just behind the .badge-pop card */
	width: 9px;
	height: 13px;
	opacity: 0;
	pointer-events: none;
}

/* ---------- Badge unlock toasts ---------- */
.toasts {
	position: fixed;
	top: 74px;
	right: 18px;
	z-index: 80;
	display: flex;
	flex-direction: column;
	gap: 10px;
	pointer-events: none;
	max-width: min(340px, 86vw);
}
.toast {
	background: var(--panel);
	border: 1px solid var(--chip-brd);
	border-radius: var(--r-md);
	padding: 12px 16px;
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(14px);
	-webkit-backdrop-filter: blur(14px);
	display: grid;
	gap: 3px;
	animation: toast-in 0.4s cubic-bezier(0.2, 0.9, 0.3, 1.2);
	transition:
		opacity 0.7s,
		transform 0.7s;
}
.toast b {
	font-size: 0.92rem;
}
.toast span {
	color: var(--muted);
	font-size: 0.8rem;
}
.toast.gone {
	opacity: 0;
	transform: translateX(24px);
}
@keyframes toast-in {
	from {
		opacity: 0;
		transform: translateX(32px);
	}
	to {
		opacity: 1;
		transform: none;
	}
}
@media (prefers-reduced-motion: reduce) {
	.toast {
		animation: none;
	}
}

/* ---------- Profile pictures (mini + fit preference) ---------- */
.mini-avatar {
	width: 20px;
	height: 20px;
	border-radius: 50%;
	object-fit: cover;
	vertical-align: middle;
	margin-right: 6px;
	border: 1px solid var(--line);
	background: var(--panel-2);
	flex: none;
}
.mini-avatar.fit-contain,
.avatar-img.fit-contain {
	object-fit: contain;
}
/* the no-picture fallback: colored disc + initial */
.mini-initial {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: 10px;
	font-weight: 700;
	font-family: var(--font-body);
	color: #fff;
	text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
	border: none;
}

/* ---------- Inline line editing + edited marker ---------- */
.story-line .line-edit {
	float: right;
	flex: none;
	min-width: 0;
	margin-left: 8px;
	padding: 1px 8px;
	font-size: 0.72rem;
	background: transparent;
	color: var(--muted);
	border: 1px solid var(--line);
	border-radius: 8px;
	opacity: 0;
	transition: opacity 0.15s;
	cursor: pointer;
}
.story-line:hover .line-edit {
	opacity: 1;
}
.story-line .line-edit:hover {
	color: var(--accent-2);
	border-color: var(--accent-2);
}
.story-line .line-del:hover,
.story-line .line-del[data-armed="1"] {
	color: #e05555;
	border-color: rgba(224, 85, 85, 0.6);
	opacity: 1;
}
.edited-tag {
	font-family: var(--font-mono);
	font-size: 0.62rem;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--muted);
	margin-right: 8px;
}
.line-editor {
	margin-top: 6px;
	min-height: 42px;
}

/* ---------- Previous-games archive page ---------- */
.archive-page {
	position: fixed;
	inset: 0;
	z-index: 55;
	overflow-y: auto;
	background: var(--bg);
	padding: 24px 20px 60px;
	visibility: hidden;
	transform: translateX(100%);
}
.archive-inner {
	max-width: 860px;
	margin: 0 auto;
}
/* A page that runs the full width of the window still needs an edge: 16px of
   gutter so cards, headings and rules never touch the bezel. It lives on the
   .wrap > child (the drawer's copy is padded already), it costs nothing while
   the 860px reading column is centred, and it is exactly what the multi-column
   shelf needs once max-width gives way to 100%. The solo write page is
   deliberately exempt — it is full-bleed by design and .doc-main carries its
   own gutter. */
.wrap > .archive-inner {
	padding-left: 16px;
	padding-right: 16px;
}
.archive-head {
	display: flex;
	align-items: center;
	gap: 14px;
	margin: 8px 0 22px;
}
.archive-head h2 {
	font-family: var(--font-display);
	font-size: clamp(1.3rem, 4vw, 2rem);
	margin: 0;
	background: var(--grad-1);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}
.archive-head button {
	flex: none;
	min-width: 0;
}
/* ---- The all-stories shelf: list or N across ----
   The reader picks how many covers share a row. `auto-fill` with a floor
   derived from the chosen count gives exactly that many columns when they fit
   and quietly drops to fewer when they'd be slivers — so "6 across" on a
   laptop degrades instead of shredding the cards. */
.st-grid {
	display: grid;
	gap: 14px;
	grid-template-columns: repeat(
		auto-fill,
		minmax(max(190px, calc((100% - (var(--st-cols, 3) - 1) * 14px) / var(--st-cols, 3))), 1fr)
	);
	align-items: stretch;
}
.st-grid .game-card {
	margin-bottom: 0;
	height: 100%;
}
/* The archive groups its cards under headings; the heading stays outside the
   grid (it isn't a card and mustn't take a column), so the grid class lands on
   a per-group container instead of the whole list. */
.arch-group {
	margin-bottom: 18px;
}
/* The archive has no search row to hang the layout picker off, so it gets a
   thin one of its own — right-aligned, clear of the fixed topbar. */
.arch-tools {
	display: flex;
	justify-content: flex-end;
	margin: 0 0 14px;
}
/* Past four across there isn't room for a paragraph of prompt — the cover and
   the title do the work, and the metadata stops competing with them. */
.st-grid .gc-date {
	font-size: 0.68rem;
}
.st-grid .game-card .gc-prompt {
	font-size: 0.94rem;
	display: -webkit-box;
	-webkit-line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
.st-grid .game-card .gc-meta {
	gap: 6px;
	font-size: 0.66rem;
}
/* Multi-column views need more page than the 860px reading column. */
.archive-inner.wide {
	max-width: min(1560px, 100%);
}

/* the layout picker: one segment per view, the pressed one is the current */
.st-views {
	display: flex;
	gap: 4px;
	align-items: center;
	background: var(--panel-2);
	border: 1px solid var(--panel-brd);
	border-radius: var(--r-md);
	padding: 3px;
}
.st-view {
	display: inline-flex;
	align-items: center;
	gap: 3px;
	min-width: 0;
	width: auto;
	flex: 0 0 auto;
	padding: 6px 9px;
	border: 0;
	border-radius: 9px;
	background: none;
	box-shadow: none;
	color: var(--muted);
	font: 600 0.78rem/1 var(--font-body);
	cursor: pointer;
}
.st-view:hover {
	color: var(--ink);
}
.st-view.on {
	background: var(--chip);
	color: var(--ink);
	border: 1px solid var(--chip-brd);
	padding: 5px 8px; /* the border must not move the row */
}
.st-view-n {
	font-family: var(--font-mono);
	font-size: 0.7rem;
}

.game-card {
	width: 100%;
	position: relative; /* anchors the .gc-date corner chip */
	text-align: left;
	background: var(--panel);
	border: 1px solid var(--panel-brd);
	border-radius: var(--r-lg);
	padding: 16px 18px;
	margin-bottom: 12px;
	cursor: pointer;
	color: var(--ink);
	font-family: var(--font-body);
	transition:
		border-color 0.2s,
		transform 0.12s;
	flex: none;
}
.game-card:hover {
	border-color: var(--accent-2);
	transform: translateY(-2px);
}
/* the save date rides the top of every archive/story card */
.game-card .gc-date {
	position: absolute;
	top: 8px;
	right: 10px;
	z-index: 2;
	font-size: 0.7rem;
	line-height: 1;
	padding: 3px 8px;
	border-radius: 8px;
	background: rgba(0, 0, 0, 0.45);
	color: rgba(255, 255, 255, 0.85);
}
.game-card .gc-cover {
	display: block;
	width: 100%;
	aspect-ratio: 3 / 1; /* banner proportions: height follows the card width */
	height: auto;
	border-radius: var(--r-md);
	margin-bottom: 10px;
}
.game-card .gc-prompt {
	font-family: var(--font-story);
	font-size: 1.02rem;
	line-height: 1.45;
	margin: 0 0 8px;
}
.game-card .gc-meta {
	font-family: var(--font-mono);
	font-size: 0.72rem;
	color: var(--muted);
	display: flex;
	gap: 12px;
	flex-wrap: wrap;
}
/* live preview under the host's header-image input — banner proportions,
   exactly how the cover will crop on dashboard/archive cards */
.cover-preview {
	display: block;
	width: 100%;
	aspect-ratio: 3 / 1;
	object-fit: cover;
	border-radius: var(--r-md);
	border: 1px solid var(--panel-2);
	margin-top: 8px;
}

/* the all-stories card leads with a big, readable date */
.gc-date {
	display: block;
	font-family: var(--font-mono);
	font-size: 1.02rem;
	font-weight: 700;
	color: var(--ink);
	letter-spacing: 0.05em;
	margin: 0 0 8px;
}

/* tumblr-style tag editor — a quiet, secondary control: label, compact
   accent-2-tinted pills, and a chip-sized dashed add field, one wrapping row */
.tag-field {
	margin-top: 10px;
}
.tag-label {
	display: flex;
	align-items: baseline;
	gap: 10px;
	font-family: var(--font-mono);
	font-size: 0.68rem;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--muted);
	text-align: left;
	margin: 0 0 6px;
}
.tag-help {
	font-size: 0.85rem;
	color: var(--ink);
	opacity: 0.8;
	text-align: left;
	margin: 0 0 10px;
}
.tag-chips {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 8px;
}
.tag-chip {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	padding: 6px 8px 6px 15px;
	border-radius: 999px;
	background: color-mix(in srgb, var(--good) 14%, transparent);
	border: 1px solid color-mix(in srgb, var(--good) 45%, transparent);
	color: var(--good);
	font-family: var(--font-mono);
	font-size: 0.78rem;
	letter-spacing: 0.02em;
	white-space: nowrap;
	flex: none;
}
/* every other chip takes the theme's second accent (mint, blue, mint, blue…) */
.tag-chip:nth-of-type(even) {
	background: color-mix(in srgb, var(--accent-2) 14%, transparent);
	border-color: color-mix(in srgb, var(--accent-2) 45%, transparent);
	color: var(--accent-2);
}
/* the remove ✕: its own comfortable round target; red only on hover/active.
   (flex/min-width/padding zero out the global button sizing — otherwise the
   ✕ inflates to a 110px oval and drags the pill wide with it) */
.tag-chip button {
	flex: none;
	min-width: 0;
	width: 18px;
	height: 18px;
	display: grid;
	place-items: center;
	border-radius: 50%;
	background: none;
	border: none;
	cursor: pointer;
	color: inherit;
	opacity: 0.55;
	font-size: 0.8em;
	padding: 0;
	line-height: 1;
}
.tag-chip button:hover,
.tag-chip button:active {
	opacity: 1;
	color: #e05555;
	background: rgba(224, 85, 85, 0.14);
}
.tag-chip button:focus-visible {
	opacity: 1;
	outline: 2px solid var(--accent-2);
	outline-offset: 1px;
}
/* the add field: its own row under the chips — a rounded rect with a ＋,
   mint-bordered, about half the card wide */
.tag-add {
	display: flex;
	align-items: center;
	gap: 10px;
	width: min(420px, 100%);
	margin-top: 10px;
	padding: 10px 16px;
	border-radius: var(--r-md);
	border: 1px solid color-mix(in srgb, var(--good) 55%, transparent);
	background: transparent;
	color: var(--muted);
	cursor: text;
}
.tag-plus {
	font-size: 0.95rem;
	line-height: 1;
	opacity: 0.8;
}
.tag-add input {
	flex: 1;
	width: 100%;
	min-width: 0;
	margin: 0;
	padding: 0;
	border: none;
	border-radius: 0;
	background: transparent;
	font-size: 0.88rem;
	line-height: 1.5;
	color: var(--ink);
	outline: none;
	box-shadow: none;
}
.tag-add:focus-within {
	border-color: var(--good);
	box-shadow: 0 0 0 2px color-mix(in srgb, var(--good) 25%, transparent);
	color: var(--ink);
}
/* duplicate/invalid feedback — quiet, reserved space so nothing jumps */
.tag-msg {
	font-family: var(--font-mono);
	font-size: 0.7rem;
	color: var(--muted);
	text-align: left;
	min-height: 1em;
	margin: 4px 0 0;
}

.archive-empty {
	color: var(--muted);
	font-family: var(--font-mono);
	text-align: center;
	padding: 40px 0;
}

/* Containers wait invisible for their GSAP entrance so they never paint
   fully-formed and then restart the animation (the double-glitch). A tiny
   head script sets .pre-anim before first paint; the entrance script — or
   its safety timer, if GSAP never loads — lifts it. */
.pre-anim .card,
.pre-anim .wrap > h1 {
	opacity: 0;
}

/* ---------- Layout ---------- */
.wrap {
	position: relative;
	z-index: 1;
	width: 100%;
}
.wrap > h1,
.wrap > .tag {
	padding: 0 20px;
}
.main {
	min-width: 0;
	padding: 0 20px;
	max-width: 980px;
	margin: 0 auto;
}

/* ---------- Docked chat panel ---------- */
.chat-tabs {
	display: flex;
	gap: 6px;
	margin-bottom: 6px;
}
.chat-tabs button {
	flex: 1;
	font-family: var(--font-mono);
	font-size: 0.72rem;
	padding: 5px 8px;
	border-radius: var(--r-md);
	border: 1px solid var(--panel-brd);
	background: transparent;
	color: var(--muted);
	cursor: pointer;
}
.chat-tabs button.active {
	color: var(--ink);
	border-color: var(--accent-2);
}
.chat-tabs button.unseen::after {
	content: "•";
	color: var(--accent);
	margin-left: 4px;
}
.msg.spec-msg {
	opacity: 0.9;
}
.chat-dock {
	position: fixed;
	right: 18px;
	bottom: var(--footbar-h);
	/* above every gimmick layer (z 60): no spill, die or cup ever covers the chat */
	z-index: 62;
	width: min(340px, calc(100vw - 36px));
	display: flex;
	flex-direction: column;
	background: var(--panel);
	border: 1px solid var(--panel-brd);
	border-bottom: 0;
	border-radius: var(--r-lg) var(--r-lg) 0 0;
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(16px);
	-webkit-backdrop-filter: blur(16px);
	transition:
		background var(--tsn),
		border-color var(--tsn);
}
.chat-dock.hidden {
	display: none;
}
.chat-dock-head {
	flex: none;
	min-width: 0;
	display: flex;
	align-items: center;
	gap: 8px;
	width: 100%;
	background: transparent;
	border: 0;
	color: var(--ink);
	padding: 12px 16px;
	font-weight: 700;
	font-family: var(--font-body);
	font-size: 0.9rem;
	cursor: pointer;
}
.chat-dock-head:hover {
	transform: none;
}
.chat-dock-head .chev {
	margin-left: auto;
	font-style: normal;
	color: var(--muted);
	transition: transform 0.3s;
}
.chat-dock.collapsed .chat-dock-head .chev {
	transform: rotate(180deg);
}
.chat-unread {
	background: var(--accent);
	color: var(--btn-ink);
	border-radius: var(--r-lg);
	font-size: 0.7rem;
	font-family: var(--font-mono);
	padding: 2px 8px;
	font-weight: 700;
}
.chat-unread:empty {
	display: none;
}
.chat-dock-body {
	padding: 0 12px 12px;
	overflow: hidden;
}
.chat-dock.collapsed .chat-dock-body {
	display: none;
}
@media (max-width: 760px) {
	.chat-dock {
		right: 10px;
	}
	.chat-log {
		height: 200px !important;
	}
}

/* Reveal page: breathing room between the finished story and the
   continue-the-story rules panel */
#continueControls {
	margin-top: 20px;
}

/* ---- Typed last-line on profiles: blinking caret while GSAP types ---- */
.type-caret {
	color: var(--accent-2);
	animation: caret-blink 0.9s step-end infinite;
}
.type-caret.done {
	display: none;
}
@keyframes caret-blink {
	50% {
		opacity: 0;
	}
}

/* ---- Tooltip (.tip-pop): one floating element for the whole page, shown
       instantly on hover (js/tooltip.js). Every color rides the theme vars,
       so it re-skins itself with the theme like the menus and toasts. ---- */
.tip-pop {
	position: fixed;
	z-index: 99999;
	max-width: 260px;
	padding: 8px 12px;
	background: var(--panel);
	border: 1px solid var(--panel-brd);
	border-radius: var(--r-md);
	color: var(--ink);
	font: 500 0.8rem/1.45 var(--font-body);
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(14px);
	-webkit-backdrop-filter: blur(14px);
	pointer-events: none;
	opacity: 0;
	transform: translateY(4px);
	transition:
		opacity 0.12s ease-out,
		transform 0.12s ease-out;
}
.tip-pop.below {
	transform: translateY(-4px);
}
.tip-pop.show {
	opacity: 1;
	transform: none;
}

/* ---- Your-turn toast: same popup language as the join-request cards
       (panel glass, top-center below the topbar); stays until dismissed
       or the turn ends ---- */
.turn-toast {
	position: fixed;
	top: 70px;
	left: 50%;
	transform: translateX(-50%);
	z-index: 66;
	display: flex;
	align-items: center;
	gap: 10px;
	width: min(420px, 92vw);
	background: var(--panel);
	border: 1px solid var(--panel-brd);
	border-radius: var(--r-md);
	padding: 12px 14px;
	box-shadow:
		var(--panel-glow),
		0 0 40px var(--glow);
	backdrop-filter: blur(14px);
	-webkit-backdrop-filter: blur(14px);
	animation: toast-in 0.4s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}
.turn-toast.hidden {
	display: none;
}
.turn-toast span {
	flex: 1;
	font-size: 0.92rem;
	font-weight: 600;
	color: var(--good);
}
.turn-toast button,
.turn-toast a.turn-toast-go {
	flex: 0 0 auto;
	min-width: 0;
	padding: 8px 12px;
	font-size: 0.8rem;
}
.turn-toast a.turn-toast-go {
	border: 1px solid var(--panel-brd);
	border-radius: var(--r-md);
	color: var(--accent-2);
	text-decoration: none;
	font-weight: 600;
	white-space: nowrap;
}
.turn-toast a.turn-toast-go:hover {
	border-color: var(--accent-2);
}
.turn-toast-close {
	background: none;
	border: 0;
	color: var(--ink);
	font-size: 0.95rem;
	cursor: pointer;
	line-height: 1;
}

/* ---- Quill-mark logo (header + hero) — every color rides the theme vars ---- */
.brand-logo {
	height: 1.06em;
	width: auto;
	vertical-align: -0.16em;
	margin-right: 0.26em;
	overflow: visible;
}
h1 .brand-logo {
	filter: drop-shadow(0 4px 22px var(--glow));
}
.brand-quill {
	height: 1.06em;
	width: auto;
	vertical-align: -0.16em;
	margin-left: 0.2em;
	overflow: visible;
}
h1 .brand-quill {
	filter: drop-shadow(0 4px 22px var(--glow));
}
.logo-stop-a {
	stop-color: var(--accent);
}
.logo-stop-b {
	stop-color: var(--accent-2);
}
.logo-stop-ink {
	stop-color: var(--ink);
}
.logo-barb-a {
	fill: var(--accent);
}
.logo-barb-b {
	fill: var(--accent-2);
}
.hero-logo {
	display: flex;
	justify-content: center;
	margin: 0 0 4px;
}
.hero-logo .brand-logo {
	height: auto;
	width: clamp(86px, 13vw, 132px);
	margin: 0;
	vertical-align: baseline;
	filter: drop-shadow(0 6px 30px var(--glow));
}

h1 {
	font-family: var(--font-display);
	font-weight: 900;
	font-size: clamp(1.9rem, 6vw, 2.9rem);
	margin: 0 0 6px;
	letter-spacing: 0.5px;
	line-height: 1.02;
	background: var(--grad-1);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
	filter: drop-shadow(0 4px 22px var(--glow));
}
[data-theme="aurora"] h1,
[data-theme="ink"] h1 {
	letter-spacing: -1px;
}
h1.brand-link {
	cursor: pointer;
	width: fit-content;
}
h1.brand-link:focus-visible {
	outline: 2px solid var(--accent-2);
	outline-offset: 4px;
	border-radius: 8px;
}
.tag {
	color: var(--muted);
	margin: 0 0 24px;
	font-size: 0.9rem;
	font-family: var(--font-mono);
}

.card {
	background: var(--panel);
	border: 1px solid var(--panel-brd);
	border-radius: var(--r-md);
	padding: 24px;
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(16px);
	-webkit-backdrop-filter: blur(16px);
	margin-bottom: 16px;
	transition:
		background var(--tsn),
		border-color var(--tsn),
		box-shadow var(--tsn);
}
label {
	display: block;
	font-size: 0.72rem;
	color: var(--muted);
	margin: 14px 0 6px;
	text-transform: uppercase;
	letter-spacing: 1.2px;
	font-family: var(--font-mono);
}
input,
textarea {
	width: 100%;
	padding: 12px 14px;
	background: var(--panel-2);
	border: 1px solid var(--line);
	border-radius: var(--r-md);
	color: var(--ink);
	font-family: inherit;
	font-size: 1rem;
	transition: border-color 0.2s;
}
input:focus,
textarea:focus {
	outline: none;
	border-color: var(--accent-2);
	box-shadow: 0 0 0 3px var(--chip);
}
input.code {
	text-transform: uppercase;
	letter-spacing: 6px;
	font-weight: 700;
	text-align: center;
	font-size: 1.4rem;
	font-family: var(--font-mono);
}
.row {
	display: flex;
	gap: 10px;
	flex-wrap: wrap;
	margin-top: 18px;
}
button {
	flex: 1;
	min-width: 110px;
	padding: 13px 16px;
	border: none;
	border-radius: var(--r-md);
	font-family: inherit;
	font-size: 0.98rem;
	font-weight: 600;
	cursor: pointer;
	transition:
		transform 0.12s ease,
		filter 0.15s,
		box-shadow 0.2s,
		border-color 0.2s;
}
button:hover {
	transform: translateY(-2px);
}
button:active {
	transform: translateY(1px);
}
button:disabled {
	opacity: 0.45;
	cursor: not-allowed;
	transform: none;
}
/* every disabled control looks it — not just buttons */
input:disabled,
select:disabled,
textarea:disabled {
	opacity: 0.45;
}
input[type="checkbox"]:disabled:hover {
	border-color: var(--muted); /* no inviting hover glow while off */
}
/* and anything disabled says so with the cursor, globally */
:disabled,
[disabled],
[aria-disabled="true"] {
	cursor: not-allowed;
}
/* a disabled checkbox hides inside a clickable label — the label says no too */
label:has(> input:disabled) {
	cursor: not-allowed;
	opacity: 0.7;
}
.primary {
	background: var(--grad-1);
	color: var(--btn-ink);
	box-shadow: 0 8px 22px -8px var(--glow);
}
.primary:hover {
	box-shadow: 0 12px 28px -8px var(--glow);
	filter: brightness(1.05);
}
.ghost {
	background: var(--panel-2);
	color: var(--ink);
	border: 1px solid var(--line);
}
.ghost:hover {
	border-color: var(--accent-2);
}
.divider {
	text-align: center;
	color: var(--muted);
	margin: 20px 0;
	font-size: 0.78rem;
	letter-spacing: 2px;
	font-family: var(--font-mono);
}
.hidden {
	display: none;
}
.err {
	color: var(--accent);
	font-size: 0.85rem;
	min-height: 1.2em;
	margin-top: 10px;
}
.codechip {
	font-family: var(--font-display);
	font-weight: 900;
	font-size: 2.4rem;
	letter-spacing: 10px;
	text-align: center;
	margin: 4px 0;
	background: var(--grad-1);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}
.subtle {
	color: var(--muted);
	font-size: 0.85rem;
	text-align: center;
}
.swatches {
	display: flex;
	gap: 10px;
	flex-wrap: wrap;
	margin-top: 6px;
}
.swatch {
	width: 32px;
	height: 32px;
	border-radius: 50%;
	cursor: pointer;
	border: 3px solid transparent;
	transition: transform 0.1s;
}
.swatch:hover {
	transform: scale(1.12);
}
.swatch.sel {
	border-color: var(--ink);
	box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
}
ul.roster {
	list-style: none;
	padding: 0;
	margin: 16px 0 0;
}
ul.roster li {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 10px 12px;
	border: 1px solid var(--line);
	border-radius: var(--r-md);
	margin-bottom: 8px;
	background: var(--panel-2);
}
.dot {
	width: 9px;
	height: 9px;
	border-radius: 50%;
}
.rname {
	font-weight: 700;
}
.badge {
	margin-left: auto;
	font-size: 0.62rem;
	color: var(--accent);
	text-transform: uppercase;
	letter-spacing: 1px;
	font-family: var(--font-mono);
	background: var(--chip);
	border: 1px solid var(--chip-brd);
	padding: 3px 8px;
	border-radius: var(--r-lg);
}
.prompt-banner {
	/* a guided prompt is one bulleted clause per line — keep its breaks, and
	   hang the wrap so a long clause lines up under its own text */
	white-space: pre-line;
	text-indent: -1.15em;
	padding: 18px 20px 18px calc(20px + 1.15em);
	background: var(--chip);
	border: 1px solid var(--chip-brd);
	border-left: 4px solid var(--accent);
	border-radius: var(--r-md);
	font-family: var(--font-story);
	font-size: 1.1rem;
	line-height: 1.5;
	color: var(--ink);
}
.option {
	position: relative;
	text-align: left;
	white-space: pre-line;
	text-indent: -1.15em;
	background: var(--panel-2);
	border: 2px solid var(--line);
	color: var(--ink);
	font-family: var(--font-story);
	font-size: 1.02rem;
	line-height: 1.45;
	padding: 16px 60px 16px calc(16px + 1.15em);
	border-radius: var(--r-md);
	cursor: pointer;
	width: 100%;
	margin-bottom: 10px;
	flex: none;
	transition:
		border-color 0.2s,
		transform 0.12s;
}
.option:hover {
	border-color: var(--accent-2);
	transform: translateY(-2px);
}
.option.mine {
	border-color: var(--good);
}
.votecount {
	position: absolute;
	right: 14px;
	top: 50%;
	transform: translateY(-50%);
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
	padding: 4px 10px;
	font-family: var(--font-mono);
	font-size: 0.85rem;
	color: var(--accent-2);
	font-weight: 700;
}
.story {
	margin-top: 18px;
	padding: 20px;
	background: var(--story-bg);
	border: 1px solid var(--line);
	border-radius: var(--r-md);
	font-family: var(--font-story);
	font-size: 1.12rem;
	line-height: 1.75;
	max-height: 40vh;
	overflow-y: auto;
	position: relative;
}
.story p {
	margin: 0 0 0.7em;
}
.story p:last-child {
	margin-bottom: 0;
}
.story .who {
	font-family: var(--font-mono);
	font-size: 0.7rem;
	text-transform: uppercase;
	letter-spacing: 1px;
	margin-right: 6px;
	font-weight: 700;
}
.story .empty {
	color: var(--muted);
	font-style: italic;
}
.story .fresh {
	animation: fade 0.6s ease;
}
@keyframes fade {
	from {
		opacity: 0;
		transform: translateY(6px);
		filter: blur(4px);
	}
	to {
		opacity: 1;
		filter: blur(0);
	}
}
.live {
	margin-top: 12px;
	padding: 14px 16px;
	background: var(--panel-2);
	border: 1px dashed var(--accent-2);
	border-radius: var(--r-md);
	font-family: var(--font-story);
	font-size: 1.08rem;
	line-height: 1.65;
	opacity: 0.92;
}
.live .who {
	font-family: var(--font-mono);
	font-size: 0.7rem;
	text-transform: uppercase;
	letter-spacing: 1px;
	margin-right: 6px;
	font-weight: 700;
}
.live .caret {
	display: inline-block;
	width: 2px;
	height: 1.05em;
	background: var(--accent-2);
	margin-left: 2px;
	vertical-align: -2px;
	animation: blink 1s steps(1) infinite;
}
@keyframes blink {
	50% {
		opacity: 0;
	}
}
.turnbar {
	display: flex;
	align-items: center;
	gap: 12px;
	margin-top: 18px;
}
.who-turn {
	font-weight: 700;
	font-size: 1.05rem;
	font-family: var(--font-body);
}
.timer {
	margin-left: auto;
	font-weight: 700;
	font-size: 1.3rem;
	font-family: var(--font-mono);
	font-variant-numeric: tabular-nums;
	min-width: 3ch;
	text-align: right;
}
.timer.low {
	color: var(--accent);
	animation: pulse 0.6s infinite alternate;
}
.timer.paused {
	color: var(--accent-2);
}
@keyframes pulse {
	to {
		opacity: 0.5;
	}
}
/* WYSIWYG editor */
.toolbar {
	display: flex;
	gap: 6px;
	margin-top: 12px;
	flex-wrap: wrap;
}
.toolbar select {
	flex: 0 0 auto;
	padding: 8px 10px;
	border-radius: var(--r-md);
	background: var(--panel-2);
	color: var(--ink);
	border: 1px solid var(--line);
	font-family: inherit;
	font-size: 0.9rem;
	cursor: pointer;
}
/* The global h1 is gradient text (color: transparent + background-clip), and
   the caret is drawn in `color` — so inside an editable heading the cursor
   vanished. Every editing surface names its caret explicitly. */
.editor,
.doc-editor,
[contenteditable="true"],
.editor h1,
.doc-editor h1,
[contenteditable="true"] h1,
.editor h2,
.doc-editor h2,
[contenteditable="true"] h2,
.editor h3,
.doc-editor h3,
[contenteditable="true"] h3 {
	caret-color: var(--accent-2);
}
/* rich block formats (story, editor, live preview, archive) */
.story h1,
.editor h1,
.live h1 {
	font-size: 1.55em;
	line-height: 1.25;
	margin: 0.35em 0 0.25em;
}
.story h2,
.editor h2,
.live h2 {
	font-size: 1.3em;
	line-height: 1.3;
	margin: 0.35em 0 0.2em;
}
.story h3,
.editor h3,
.live h3 {
	font-size: 1.12em;
	line-height: 1.3;
	margin: 0.3em 0 0.15em;
}
.story hr,
.editor hr,
.live hr {
	border: 0;
	border-top: 1px solid var(--line);
	margin: 0.8em 0;
}
.story .al-c,
.editor .al-c,
.live .al-c {
	text-align: center;
}
.story .al-r,
.editor .al-r,
.live .al-r {
	text-align: right;
}
.story-line {
	margin: 0 0 0.7em;
}
.story-line:last-child {
	margin-bottom: 0;
}
.story-line p {
	margin: 0 0 0.4em;
}
.toolbar button {
	flex: 0 0 auto;
	min-width: 42px;
	padding: 8px 12px;
	font-size: 1rem;
}
.toolbar .tb-b {
	font-weight: 800;
}
.toolbar .tb-i {
	font-style: italic;
}
.toolbar .tb-u {
	text-decoration: underline;
}
.editor {
	width: 100%;
	min-height: 170px;
	max-height: 46vh;
	resize: vertical;
	margin-top: 8px;
	padding: 16px;
	overflow-y: auto;
	background: var(--panel-2);
	border: 1px solid var(--accent-2);
	border-radius: var(--r-md);
	color: var(--ink);
	font-family: var(--font-story);
	font-size: 1.12rem;
	line-height: 1.7;
	outline: none;
}
.editor:empty:before {
	content: attr(data-placeholder);
	color: var(--muted);
}
.editor[aria-disabled="true"] {
	opacity: 0.5;
	pointer-events: none;
}
.hint {
	color: var(--muted);
	font-size: 0.78rem;
	margin-top: 6px;
	font-family: var(--font-mono);
}
.grid2 {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 10px;
}
.progress {
	color: var(--muted);
	font-size: 0.8rem;
	text-align: center;
	margin-top: 12px;
	font-family: var(--font-mono);
}
.chat-log {
	height: 300px;
	overflow-y: auto;
	background: var(--panel-2);
	border: 1px solid var(--line);
	border-radius: var(--r-md);
	padding: 12px;
	margin-bottom: 10px;
}
.chat-log .msg {
	margin: 0 0 6px;
	font-size: 0.92rem;
	line-height: 1.4;
}
/* the continue panel, visible to writers but host-only to drive */
.continue-locked {
	opacity: 0.55;
	cursor: not-allowed;
}
.continue-locked input,
.continue-locked select,
.continue-locked button,
.continue-locked .checkline {
	pointer-events: none;
}
/* …except tags: every writer may tag, not just the host */
.continue-locked #overTags input,
.continue-locked #overTags button {
	pointer-events: auto;
}
/* the tag block sits inside the rules-form panel: separate it with the same
   hairline the panel already uses between its own rows */
.rules-form .tag-field {
	border-top: 1px solid var(--line);
	margin-top: 14px;
	padding-top: 12px;
}

/* the writers directory scrolls instead of growing forever */
#writersList {
	max-height: 340px;
	overflow-y: auto;
	padding-right: 4px;
}

/* archive group subheadings: hosted vs joined */
.arch-sub {
	margin: 18px 0 8px;
	font-family: var(--font-mono);
	font-size: 0.72rem;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--muted);
	text-align: left;
	border-bottom: 1px solid var(--line);
	padding-bottom: 5px;
}
.arch-sub:first-child {
	margin-top: 0;
}

/* the folded word-count ladder on profiles */
.ladder-acc {
	margin-top: 8px;
}
.ladder-acc summary {
	cursor: pointer;
	font-family: var(--font-mono);
	font-size: 0.78rem;
	color: var(--muted);
	padding: 6px 2px;
	user-select: none;
}
.ladder-acc summary:hover {
	color: var(--ink);
}

/* ============================================================
   HOUSE RULES — the shared rules form (lobby, host panel, continue).
   One inset panel whose left keyline + description emphasis wear the
   selected story mode: friendly = --good, non-friendly = --accent.
   Everything derives from the theme tokens so all three skins work.
   ============================================================ */
.rules-form {
	--rules-mode: var(--good);
	background: var(--panel-2);
	border: 1px solid var(--line);
	border-left: 3px solid var(--rules-mode);
	border-radius: var(--r-md);
	padding: 14px 16px 16px;
	transition: border-color 0.35s ease;
}
.rules-form[data-mode="hostile"] {
	--rules-mode: var(--accent);
}
.rules-mode-row {
	display: flex;
	align-items: flex-start;
	gap: 16px;
}
.rules-mode-pick {
	flex: 0 0 auto;
	min-width: 150px;
}
.rules-form label:not(.checkline):not(.tag-add) {
	display: block;
	font-family: var(--font-mono);
	font-size: 0.68rem;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--muted);
	margin: 0 0 5px;
}
/* the native select never got themed; dress it like the inputs here */
.rules-mode-pick {
	position: relative;
}
.rules-form select {
	appearance: none;
	-webkit-appearance: none;
	width: 100%;
	padding: 10px 34px 10px 12px;
	background: var(--panel-2);
	border: 1px solid var(--line);
	border-radius: var(--r-md);
	color: var(--ink);
	font-family: inherit;
	font-size: 0.95rem;
	cursor: pointer;
	transition: border-color 0.2s;
}
.rules-form select:hover {
	border-color: var(--rules-mode);
}
.rules-form select:focus-visible {
	outline: none;
	border-color: var(--accent-2);
	box-shadow: 0 0 0 3px var(--chip);
}
.rules-mode-pick::after {
	content: "▾";
	position: absolute;
	right: 13px;
	bottom: 11px;
	color: var(--muted);
	pointer-events: none;
	font-size: 0.85rem;
}
.rules-mode-desc {
	flex: 1;
	margin: 0;
	align-self: center;
	font-size: 0.8rem;
	line-height: 1.5;
	color: var(--muted);
}
.rules-mode-desc b {
	font-weight: 600;
}
/* the sentence for the selected mode carries the ink; the other recedes */
.rmode-desc {
	opacity: 0.45;
	transition:
		opacity 0.35s ease,
		color 0.35s ease;
}
.rules-form[data-mode="friendly"] .rmode-desc[data-desc="friendly"],
.rules-form[data-mode="hostile"] .rmode-desc[data-desc="hostile"] {
	opacity: 1;
	color: var(--ink);
}
.rules-form[data-mode="friendly"] .rmode-desc[data-desc="friendly"] b,
.rules-form[data-mode="hostile"] .rmode-desc[data-desc="hostile"] b {
	color: var(--rules-mode);
}
.rules-inline-row {
	display: flex;
	align-items: flex-end;
	flex-wrap: wrap;
	gap: 12px;
	margin-top: 14px;
	padding-top: 12px;
	border-top: 1px solid var(--line);
}
.rules-inline-row .checkline {
	margin: 0 0 9px;
	white-space: nowrap;
}
.rules-num {
	flex: 1;
	min-width: 72px;
}
.rules-num input[type="number"] {
	width: 100%;
}
/* a rule switched off (infinite / no timer) dims but stays visible */
.rules-num input:disabled {
	opacity: 0.4;
	text-decoration: line-through;
}
/* narrow mounts (the host side panel): stack the mode row */
#hostRules .rules-mode-row {
	flex-direction: column;
	gap: 8px;
}
#hostRules .rules-mode-desc {
	align-self: auto;
}
@media (max-width: 560px) {
	.rules-mode-row {
		flex-direction: column;
		gap: 8px;
	}
	.rules-mode-desc {
		align-self: auto;
	}
}
@media (prefers-reduced-motion: reduce) {
	.rules-form,
	.rmode-desc {
		transition: none;
	}
}

/* story mode chip (session bar) + the no-timer checkbox row */
.mode-chip {
	font-family: var(--font-mono);
	font-size: 0.68rem;
	color: var(--muted);
	border: 1px solid var(--line);
	border-radius: 999px;
	padding: 2px 9px;
	white-space: nowrap;
}
.checkline {
	display: flex;
	align-items: center;
	gap: 8px;
	margin-top: 26px;
	font-size: 0.82rem;
	color: var(--muted);
	cursor: pointer;
}
.checkline input {
	width: auto;
	margin: 0;
}
/* checkboxes ride the theme everywhere */
input[type="checkbox"] {
	appearance: none;
	-webkit-appearance: none;
	width: 17px;
	height: 17px;
	padding: 0; /* the global input padding would balloon the box */
	flex: none;
	border: 1.5px solid var(--muted); /* always visible, every theme */
	border-radius: 5px;
	background: var(--panel-2);
	cursor: pointer;
	position: relative;
	transition:
		border-color 0.2s,
		background 0.2s,
		box-shadow 0.2s;
}
input[type="checkbox"]:hover {
	border-color: var(--accent-2);
}
input[type="checkbox"]:checked {
	background: var(--accent);
	border-color: var(--accent);
	box-shadow: 0 0 12px var(--glow);
}
input[type="checkbox"]:checked::after {
	content: "";
	position: absolute;
	left: 5px;
	top: 1.5px;
	width: 4px;
	height: 9px;
	border: solid var(--btn-ink);
	border-width: 0 2px 2px 0;
	transform: rotate(42deg);
}
input[type="checkbox"]:focus-visible {
	outline: 2px solid var(--accent-2);
	outline-offset: 2px;
}
.checkline:has(input:checked) {
	color: var(--ink);
}

/* "(host)" tag after names, and the byline block above each story line */
.host-tag {
	color: var(--muted);
	font-family: var(--font-mono);
	font-size: 0.72em;
	margin: 0 6px 0 4px;
}
.story-line .line-by {
	display: block;
	margin-bottom: 3px;
}
/* hover-a-name stat tooltip: all-time words + achievements in a column */
.stat-tip {
	position: fixed;
	z-index: 95;
	display: flex;
	flex-direction: column;
	gap: 3px;
	max-width: 230px;
	padding: 10px 12px;
	border-radius: var(--r-md);
	background: var(--panel-2);
	border: 1px solid var(--panel-brd);
	box-shadow: var(--panel-glow);
	font-size: 0.78rem;
	pointer-events: none;
	backdrop-filter: blur(10px);
	-webkit-backdrop-filter: blur(10px);
}
.stat-tip.hidden {
	display: none;
}
.stat-tip .tt-words {
	font-family: var(--font-mono);
	color: var(--ink);
}
.stat-tip .tt-h {
	margin-top: 4px;
	font-size: 0.66rem;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--muted);
}
.stat-tip .tt-badge {
	color: var(--muted);
}
[data-user] {
	cursor: pointer; /* hover = stats tooltip, click = profile in a new tab */
}

.chat-log .cn-host {
	color: var(--muted);
	font-family: var(--font-mono);
	font-size: 0.72em;
	margin-right: 6px;
}
.chat-log .cn {
	font-weight: 700;
	margin-right: 6px;
}
.chat-log .msg.sys {
	color: var(--muted);
	font-style: italic;
}
.chat-row {
	display: flex;
	gap: 8px;
}
.chat-row input,
.chat-row select {
	flex: 1;
	min-width: 0;
}
/* the invite dropdown wears the theme like an input: same panel, line, ink
   and radius, its options on the panel too (a bare select is the browser's) */
#inviteFriend {
	appearance: none;
	-webkit-appearance: none;
	padding: 12px 34px 12px 14px;
	background: var(--panel-2);
	border: 1px solid var(--line);
	border-radius: var(--r-md);
	color: var(--ink);
	font-family: inherit;
	font-size: 0.95rem;
	cursor: pointer;
	background-image:
		linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
	background-position:
		right 18px center,
		right 13px center;
	background-size:
		5px 5px,
		5px 5px;
	background-repeat: no-repeat;
}
#inviteFriend:focus {
	outline: none;
	border-color: var(--accent-2);
}
#inviteFriend option {
	background: var(--panel);
	color: var(--ink);
}
.chat-row button {
	flex: 0 0 auto;
	min-width: auto;
	padding: 12px 18px;
}
.st-dot {
	display: inline-block;
	width: 8px;
	height: 8px;
	border-radius: 50%;
	flex: none;
	cursor: default;
	margin-right: 5px;
	vertical-align: baseline;
}
/* the words beside the dot in the writers / players lists: a seat is either
   In game (has this game open) or Not in game */
.st-label {
	font-size: 0.66rem;
	font-weight: 600;
	letter-spacing: 0.03em;
	text-transform: uppercase;
	color: var(--muted);
	margin-right: 4px;
	white-space: nowrap;
}
.st-label.on {
	color: var(--good);
}
.st-dot.unknown {
	display: none;
}
.st-dot.on {
	background: var(--good);
	box-shadow: 0 0 6px var(--good);
}
.st-dot.off {
	background: var(--muted);
	opacity: 0.55;
}
.players-row {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin-top: 14px;
}
/* the host's ⋯ on another seat (lobby roster + players row): the dashboard
   card's .mg-menu shape, inline here rather than pinned to a corner */
.seat-menu {
	position: relative;
	display: inline-flex;
	margin-left: auto;
}
.seat-menu .mg-more {
	margin: 0;
	padding: 0 7px;
	min-width: 0;
	width: auto;
	font-size: 0.95rem;
	line-height: 1.3;
	border-radius: 8px;
	opacity: 0.6;
}
.seat-menu .mg-more:hover,
.seat-menu.open .mg-more {
	opacity: 1;
}
.seat-menu .mg-menu-list {
	display: none;
	position: absolute;
	top: calc(100% + 6px);
	right: 0;
	z-index: 20;
	min-width: 160px;
	flex-direction: column;
	gap: 3px;
	padding: 6px;
	border-radius: var(--r-md);
	background: color-mix(in srgb, var(--bg-2) 90%, var(--ink) 10%);
	border: 1px solid var(--panel-brd);
	box-shadow: var(--panel-glow);
}
.seat-menu.open .mg-menu-list {
	display: flex;
}
.seat-menu .mg-menu-list button {
	margin: 0;
	width: 100%;
	min-width: 0;
	text-align: left;
	padding: 8px 10px;
	font-size: 0.85rem;
	white-space: nowrap;
}
.player-chip {
	display: flex;
	align-items: center;
	gap: 6px;
	padding: 5px 11px;
	border-radius: var(--r-lg);
	border: 1px solid var(--line);
	background: var(--panel-2);
	font-size: 0.82rem;
	font-weight: 600;
}
.player-chip.now {
	border-color: var(--accent-2);
	box-shadow: 0 0 8px var(--chip-brd);
}
/* the players row reads in turn order: a small seat number on every chip,
   and the next connected writer wears the "up next" tag */
.player-chip .turn-no {
	font: 700 0.66rem/1 var(--font-mono);
	color: var(--muted);
}
.player-chip.next {
	border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
}
.player-chip .up-next {
	font: 700 0.6rem/1 var(--font-mono);
	font-style: normal;
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--accent);
	border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
	border-radius: 999px;
	padding: 2px 6px;
}
.who-mark {
	cursor: default;
	margin-right: 3px;
	font-style: normal;
}

/* ---------- Session bar (name + copyable code) ---------- */
.session-bar {
	display: flex;
	align-items: center;
	gap: 8px;
	margin-bottom: 14px;
	flex-wrap: wrap;
}
.session-bar .sess-name {
	font-weight: 700;
	font-size: 1.05rem;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
}
.session-bar .sess-name.unnamed {
	color: var(--muted);
	font-weight: 500;
	font-style: italic;
}
/* the game-level actions ride at the far end of the bar, away from the name */
.session-bar .sess-acts {
	display: flex;
	align-items: center;
	gap: 8px;
	margin-left: auto;
}
.session-bar .sess-input {
	flex: 1 1 140px;
	width: auto;
	padding: 7px 10px;
	font-size: 0.95rem;
}
.session-bar button {
	flex: 0 0 auto;
	min-width: 0;
	padding: 6px 11px;
	font-size: 0.8rem;
	font-family: var(--font-mono);
}
.session-bar .sess-host {
	font-family: var(--font-mono);
	font-size: 0.72rem;
	color: var(--muted);
	white-space: nowrap;
}
.session-bar [data-sess-code] {
	margin-left: auto;
	letter-spacing: 2px;
	font-weight: 700;
}
#codeDisplay {
	cursor: pointer;
}
/* host's pending join-request popups */
.join-reqs {
	position: fixed;
	top: 70px;
	left: 50%;
	transform: translateX(-50%);
	z-index: 66;
	display: flex;
	flex-direction: column;
	gap: 8px;
	width: min(420px, 92vw);
}
.join-req {
	display: flex;
	align-items: center;
	gap: 10px;
	background: var(--panel);
	border: 1px solid var(--panel-brd);
	border-radius: var(--r-md);
	padding: 12px 14px;
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(14px);
	-webkit-backdrop-filter: blur(14px);
}
.join-req span {
	flex: 1;
	font-size: 0.92rem;
}
.join-req button {
	flex: 0 0 auto;
	min-width: 0;
	padding: 8px 12px;
	font-size: 0.8rem;
}

/* ---------- Account / auth ---------- */
/* The lobby (login/signup/guest) card sits centered on desktop. */
#lobby {
	width: 100%;
	max-width: 460px;
	margin-left: auto;
	margin-right: auto;
}
@media (min-width: 761px) {
	.main:has(#lobby:not(.hidden)) {
		display: flex;
		flex-direction: column; /* "Live right now" sits UNDER the auth card */
		align-items: center;
		justify-content: center;
		gap: 18px;
		min-height: calc(100vh - 220px);
	}
}
/* The homepage "Live right now" card matches the auth card's footprint */
#liveWatch {
	width: 100%;
	max-width: 460px;
	margin-left: auto;
	margin-right: auto;
}
.linky {
	color: var(--accent-2);
	text-decoration: none;
	border-bottom: 1px dotted var(--accent-2);
}
/* A .linky that happens to be a BUTTON has to be un-buttoned first: the global
   button rules (pill fill, min-width, form padding) otherwise win and turn a
   text action into a slab. Same reset the comment-card actions do locally. */
button.linky {
	display: inline;
	min-width: 0;
	width: auto;
	padding: 0;
	border: 0;
	border-bottom: 1px dotted var(--accent-2);
	border-radius: 0;
	background: none;
	box-shadow: none;
	font: inherit;
	cursor: pointer;
}
.linky:hover {
	border-bottom-style: solid;
}
.badge-chip {
	font-family: var(--font-mono);
	font-size: 0.68rem;
	background: var(--chip);
	border: 1px solid var(--chip-brd);
	color: var(--accent);
	border-radius: var(--r-lg);
	padding: 2px 8px;
	letter-spacing: 0.5px;
	white-space: nowrap;
}
.me-row {
	display: flex;
	align-items: center;
	gap: 10px;
	flex-wrap: wrap;
}
.me-row .me-name {
	font-weight: 700;
	font-size: 1.15rem;
}
.me-row button {
	flex: 0 0 auto;
	min-width: 0;
	padding: 8px 12px;
	font-size: 0.8rem;
}
/* only the FIRST button pushes right, so the pair stays snug together */
.me-row button:first-of-type {
	margin-left: auto;
}
.me-games {
	display: flex;
	gap: 6px;
	flex-wrap: wrap;
	margin-top: 8px;
}
.me-games button {
	flex: 0 0 auto;
	min-width: 0;
	padding: 6px 10px;
	font-family: var(--font-mono);
	font-size: 0.8rem;
}
ul.roster .badge-chip,
/* The badge you are WEARING is a label on your own name, not a control: inside
   the topbar chip it would be a pill inside a pill, and on a profile head it is
   read, never clicked. Both lose the outline; the chips that stand alone in a
   list keep theirs. */
#ucBadge,
#meBadge {
	border: 0;
	padding-left: 4px;
	padding-right: 4px;
}
.chat-log .badge-chip {
	font-size: 0.6rem;
	padding: 1px 6px;
}
.chat-log .badge-chip {
	margin-right: 6px;
}

h3 {
	font-family: var(--font-display);
	margin: 0 0 12px;
	color: var(--muted);
	font-weight: 700;
	font-size: 1rem;
	letter-spacing: 0.3px;
}
[data-theme="neon"] h3 {
	text-transform: uppercase;
	letter-spacing: 1.5px;
	font-size: 0.82rem;
}

/* ==========================================================================
   Extra themes: The Wall, Snow Ball, Upside Down, Starcourt, Palace Arcade,
   Cerebro. Same token contract as the original three.
   ========================================================================== */

/* ---------- The Wall (Byers living room: dark florals + christmas lights) */
[data-theme="wall"] {
	--bg: #17100c;
	--bg-2: #261912;
	--panel: rgba(40, 27, 19, 0.6);
	--panel-2: rgba(54, 37, 26, 0.65);
	--story-bg: rgba(30, 20, 14, 0.65);
	--ink: #f3e6d3;
	--muted: #b39a7e;
	--line: rgba(214, 164, 110, 0.22);
	--accent: #ff5545;
	--accent-2: #ffc857;
	--good: #7fd069;
	--glow: rgba(255, 85, 69, 0.35);
	--grad-1: linear-gradient(120deg, #ff5545, #ffc857 50%, #6fb3ff);
	--grad-hero: radial-gradient(120% 130% at 50% 0%, rgba(255, 200, 87, 0.14), transparent 70%);
	--panel-brd: rgba(214, 164, 110, 0.28);
	--panel-glow: 0 18px 44px rgba(8, 4, 2, 0.55), inset 0 1px 0 rgba(255, 230, 190, 0.06);
	--btn-ink: #1a120c;
	--chip: rgba(255, 200, 87, 0.12);
	--chip-brd: rgba(255, 200, 87, 0.4);
	--scroll-thumb: rgba(214, 164, 110, 0.4);
	--font-display: "Fraunces", serif;
	--font-body: "Plus Jakarta Sans", sans-serif;
	--font-story: "Newsreader", serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="wall"] .bg-set.wall {
	opacity: 1;
}
.bg-set.wall .wallpaper {
	position: absolute;
	inset: 0;
	background-image:
		radial-gradient(circle at 25% 25%, rgba(255, 200, 87, 0.06) 2px, transparent 3px),
		radial-gradient(circle at 75% 75%, rgba(255, 85, 69, 0.05) 2px, transparent 3px),
		radial-gradient(120% 100% at 50% 120%, rgba(0, 0, 0, 0.5), transparent 60%);
	background-size:
		46px 46px,
		62px 62px,
		100% 100%;
}
.bg-set.wall .lights {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	height: 110px;
	filter: drop-shadow(0 0 14px rgba(255, 200, 87, 0.6));
	background-image:
		radial-gradient(circle 5px at 4% 34px, #ff5959 60%, transparent 61%),
		radial-gradient(circle 5px at 14% 52px, #ffd23f 60%, transparent 61%),
		radial-gradient(circle 5px at 24% 36px, #6fb3ff 60%, transparent 61%),
		radial-gradient(circle 5px at 34% 54px, #7fd069 60%, transparent 61%),
		radial-gradient(circle 5px at 44% 34px, #ff8ac2 60%, transparent 61%),
		radial-gradient(circle 5px at 54% 52px, #ff5959 60%, transparent 61%),
		radial-gradient(circle 5px at 64% 36px, #ffd23f 60%, transparent 61%),
		radial-gradient(circle 5px at 74% 54px, #6fb3ff 60%, transparent 61%),
		radial-gradient(circle 5px at 84% 36px, #7fd069 60%, transparent 61%),
		radial-gradient(circle 5px at 94% 50px, #ff8ac2 60%, transparent 61%);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.wall .lights {
		animation: wall-flicker 5s ease-in-out infinite;
	}
}
@keyframes wall-flicker {
	0%,
	100% {
		opacity: 1;
	}
	42% {
		opacity: 0.75;
	}
	46% {
		opacity: 1;
	}
	72% {
		opacity: 0.85;
	}
}

/* ---------- Snow Ball (gym at night: snowfall + disco glint) */
[data-theme="snowball"] {
	--bg: #0a1128;
	--bg-2: #14224e;
	--panel: rgba(18, 30, 64, 0.55);
	--panel-2: rgba(26, 40, 80, 0.6);
	--story-bg: rgba(12, 20, 46, 0.62);
	--ink: #eaf1ff;
	--muted: #9fb0d9;
	--line: rgba(150, 180, 255, 0.22);
	--accent: #f0a6c8;
	--accent-2: #8fb6ff;
	--good: #63e6b5;
	--glow: rgba(143, 182, 255, 0.35);
	--grad-1: linear-gradient(120deg, #f0a6c8, #b9c8ff 50%, #8fb6ff);
	--grad-hero: radial-gradient(120% 130% at 50% 0%, rgba(143, 182, 255, 0.16), transparent 72%);
	--panel-brd: rgba(150, 180, 255, 0.3);
	--panel-glow: 0 18px 48px rgba(4, 8, 24, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.07);
	--btn-ink: #0a1128;
	--chip: rgba(240, 166, 200, 0.13);
	--chip-brd: rgba(240, 166, 200, 0.4);
	--scroll-thumb: rgba(150, 180, 255, 0.4);
	--font-display: "Fraunces", serif;
	--font-body: "Plus Jakarta Sans", sans-serif;
	--font-story: "Newsreader", serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="snowball"] .bg-set.snowball {
	opacity: 1;
}
.bg-set.snowball .flakes {
	position: absolute;
	inset: -260px 0 0;
	background-image:
		radial-gradient(circle 2px at 22% 18%, rgba(255, 255, 255, 0.8) 60%, transparent 61%),
		radial-gradient(circle 1.5px at 58% 44%, rgba(255, 255, 255, 0.6) 60%, transparent 61%),
		radial-gradient(circle 2.5px at 81% 72%, rgba(255, 255, 255, 0.7) 60%, transparent 61%),
		radial-gradient(circle 1.5px at 8% 82%, rgba(255, 255, 255, 0.5) 60%, transparent 61%);
	background-size: 260px 260px;
}
.bg-set.snowball .flakes.f2 {
	background-size: 420px 420px;
	opacity: 0.6;
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.snowball .flakes.f1 {
		animation: snow-fall 18s linear infinite;
	}
	.bg-set.snowball .flakes.f2 {
		animation: snow-fall 34s linear infinite;
	}
	.bg-set.snowball .glint {
		animation: glint-sweep 9s ease-in-out infinite;
	}
}
@keyframes snow-fall {
	to {
		transform: translateY(260px);
	}
}
.bg-set.snowball .glint {
	position: absolute;
	inset: 0;
	background: linear-gradient(64deg, transparent 42%, rgba(255, 255, 255, 0.07) 50%, transparent 58%);
}
@keyframes glint-sweep {
	0%,
	100% {
		transform: translateX(-30%);
	}
	50% {
		transform: translateX(30%);
	}
}

/* ---------- Upside Down (toxic dark: spores + red veins) */
[data-theme="upside"] {
	--bg: #050b0a;
	--bg-2: #0c1d18;
	--panel: rgba(10, 26, 22, 0.6);
	--panel-2: rgba(14, 34, 28, 0.62);
	--story-bg: rgba(6, 16, 13, 0.66);
	--ink: #dcefe6;
	--muted: #7fa695;
	--line: rgba(90, 160, 130, 0.22);
	--accent: #ff3b3b;
	--accent-2: #7adcc3;
	--good: #61e2a2;
	--glow: rgba(255, 59, 59, 0.35);
	--grad-1: linear-gradient(120deg, #ff3b3b, #b56576 45%, #7adcc3);
	--grad-hero: radial-gradient(120% 130% at 50% 0%, rgba(255, 59, 59, 0.12), transparent 70%);
	--panel-brd: rgba(90, 160, 130, 0.28);
	--panel-glow:
		0 18px 48px rgba(0, 4, 3, 0.6), 0 0 34px rgba(255, 59, 59, 0.06), inset 0 1px 0 rgba(200, 255, 230, 0.05);
	--btn-ink: #04100c;
	--chip: rgba(255, 59, 59, 0.12);
	--chip-brd: rgba(255, 59, 59, 0.4);
	--scroll-thumb: rgba(90, 160, 130, 0.4);
	--font-display: "Space Grotesk", sans-serif;
	--font-body: "Space Grotesk", sans-serif;
	--font-story: "Newsreader", serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="upside"] .bg-set.upside {
	opacity: 1;
}
.bg-set.upside .veins {
	position: absolute;
	inset: 0;
	background:
		radial-gradient(55% 45% at 0% 100%, rgba(255, 40, 40, 0.14), transparent 62%),
		radial-gradient(55% 45% at 100% 100%, rgba(255, 40, 40, 0.12), transparent 62%),
		radial-gradient(60% 35% at 50% 0%, rgba(30, 90, 70, 0.2), transparent 65%);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.upside .veins {
		animation: vein-pulse 7s ease-in-out infinite alternate;
	}
}
@keyframes vein-pulse {
	from {
		opacity: 0.65;
	}
	to {
		opacity: 1;
	}
}
.bg-set.upside .spore {
	position: absolute;
	width: 10px;
	height: 10px;
	border-radius: 50%;
	background: rgba(215, 255, 235, 0.5);
	filter: blur(1px);
	box-shadow: 0 0 12px rgba(215, 255, 235, 0.4);
}
.bg-set.upside .s1 {
	left: 18%;
	top: 30%;
}
.bg-set.upside .s2 {
	left: 66%;
	top: 18%;
	width: 6px;
	height: 6px;
}
.bg-set.upside .s3 {
	left: 42%;
	top: 64%;
	width: 14px;
	height: 14px;
	opacity: 0.5;
}
.bg-set.upside .s4 {
	left: 84%;
	top: 56%;
	width: 7px;
	height: 7px;
}

/* ---------- Starcourt (1985 mall pastels) */
[data-theme="starcourt"] {
	--bg: #fdf3ec;
	--bg-2: #ffe3ef;
	--panel: rgba(255, 255, 255, 0.6);
	--panel-2: rgba(255, 255, 255, 0.68);
	--story-bg: rgba(255, 252, 248, 0.58);
	--ink: #35284a;
	--muted: #8a7398;
	--line: rgba(0, 180, 190, 0.28);
	--accent: #ff5fa2;
	--accent-2: #00b4be;
	--good: #29a36a;
	--glow: rgba(255, 95, 162, 0.3);
	--grad-1: linear-gradient(120deg, #ff5fa2, #ffb86b 50%, #00b4be);
	--grad-hero: linear-gradient(135deg, rgba(255, 95, 162, 0.22), rgba(0, 180, 190, 0.16));
	--panel-brd: rgba(255, 255, 255, 0.9);
	--panel-glow: 0 18px 50px rgba(230, 140, 180, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.95);
	--btn-ink: #ffffff;
	--chip: rgba(0, 180, 190, 0.12);
	--chip-brd: rgba(0, 180, 190, 0.35);
	--scroll-thumb: rgba(255, 95, 162, 0.35);
	--font-display: "Fraunces", serif;
	--font-body: "Plus Jakarta Sans", sans-serif;
	--font-story: "Plus Jakarta Sans", sans-serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="starcourt"] .bg-set.starcourt {
	opacity: 1;
}
.bg-set.starcourt .sunset {
	position: absolute;
	inset: 0;
	background: radial-gradient(
		60% 45% at 50% 104%,
		rgba(255, 150, 90, 0.4),
		rgba(255, 95, 162, 0.2) 55%,
		transparent 75%
	);
}
.bg-set.starcourt .squiggle {
	position: absolute;
	border-radius: 999px;
}
.bg-set.starcourt .q1 {
	left: 8%;
	top: 16%;
	width: 150px;
	height: 56px;
	border: 10px solid rgba(0, 180, 190, 0.18);
	transform: rotate(-18deg);
}
.bg-set.starcourt .q2 {
	right: 10%;
	top: 30%;
	width: 100px;
	height: 100px;
	border: 12px dotted rgba(255, 95, 162, 0.22);
}

/* ---------- Palace Arcade (cabinet glow + 80s carpet) */
[data-theme="arcade"] {
	--bg: #12081f;
	--bg-2: #24103c;
	--panel: rgba(38, 18, 64, 0.55);
	--panel-2: rgba(52, 26, 86, 0.6);
	--story-bg: rgba(24, 12, 40, 0.64);
	--ink: #f6edff;
	--muted: #b9a3d9;
	--line: rgba(255, 210, 63, 0.22);
	--accent: #ffd23f;
	--accent-2: #ff4f79;
	--good: #46e08c;
	--glow: rgba(255, 210, 63, 0.3);
	--grad-1: linear-gradient(120deg, #ffd23f, #ff4f79 50%, #4fa8ff);
	--grad-hero: radial-gradient(120% 130% at 50% 0%, rgba(255, 210, 63, 0.14), transparent 72%);
	--panel-brd: rgba(255, 210, 63, 0.26);
	--panel-glow:
		0 18px 48px rgba(6, 2, 12, 0.6), 0 0 34px rgba(255, 79, 121, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.05);
	--btn-ink: #1a0b2e;
	--chip: rgba(255, 79, 121, 0.13);
	--chip-brd: rgba(255, 79, 121, 0.4);
	--scroll-thumb: rgba(255, 210, 63, 0.35);
	--font-display: "Orbitron", sans-serif;
	--font-body: "Space Grotesk", sans-serif;
	--font-story: "Space Grotesk", sans-serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="arcade"] .bg-set.arcade {
	opacity: 1;
}
.bg-set.arcade .carpet {
	position: absolute;
	inset: 0;
	opacity: 0.55;
	background-image:
		radial-gradient(circle 3px at 12% 22%, rgba(255, 210, 63, 0.5) 60%, transparent 61%),
		radial-gradient(circle 2px at 44% 64%, rgba(255, 79, 121, 0.5) 60%, transparent 61%),
		radial-gradient(circle 3px at 76% 34%, rgba(79, 168, 255, 0.5) 60%, transparent 61%),
		radial-gradient(circle 2px at 90% 82%, rgba(70, 224, 140, 0.45) 60%, transparent 61%);
	background-size: 130px 130px;
}
.bg-set.arcade .cab-glow {
	position: absolute;
	left: 55%;
	bottom: -140px;
	width: 480px;
	height: 320px;
	border-radius: 50%;
	background: radial-gradient(closest-side, rgba(255, 79, 121, 0.25), rgba(255, 210, 63, 0.1) 60%, transparent);
	filter: blur(30px);
}

/* ---------- Cerebro (ham-radio amber on black) */
[data-theme="cerebro"] {
	--bg: #0c0803;
	--bg-2: #1e1306;
	--panel: rgba(32, 21, 9, 0.6);
	--panel-2: rgba(44, 29, 12, 0.62);
	--story-bg: rgba(24, 15, 6, 0.66);
	--ink: #ffdfae;
	--muted: #b98f5a;
	--line: rgba(255, 176, 0, 0.22);
	--accent: #ffb000;
	--accent-2: #ff7a3d;
	--good: #a8d95a;
	--glow: rgba(255, 176, 0, 0.3);
	--grad-1: linear-gradient(120deg, #ffb000, #ff7a3d 60%, #ffdfae);
	--grad-hero: radial-gradient(120% 130% at 50% 0%, rgba(255, 176, 0, 0.14), transparent 70%);
	--panel-brd: rgba(255, 176, 0, 0.26);
	--panel-glow:
		0 18px 44px rgba(4, 2, 0, 0.6), 0 0 30px rgba(255, 176, 0, 0.07), inset 0 1px 0 rgba(255, 220, 160, 0.06);
	--btn-ink: #140c04;
	--chip: rgba(255, 176, 0, 0.12);
	--chip-brd: rgba(255, 176, 0, 0.4);
	--scroll-thumb: rgba(255, 176, 0, 0.35);
	--font-display: "Inconsolata", ui-monospace, monospace;
	--font-body: "Space Grotesk", sans-serif;
	--font-story: "Inconsolata", ui-monospace, monospace;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="cerebro"] .bg-set.cerebro {
	opacity: 1;
}
.bg-set.cerebro .waves {
	position: absolute;
	inset: 0;
	background: repeating-linear-gradient(0deg, transparent 0 26px, rgba(255, 176, 0, 0.05) 26px 27px);
}
.bg-set.cerebro .signal {
	position: absolute;
	top: 42%;
	left: -40%;
	width: 40%;
	height: 2px;
	background: linear-gradient(90deg, transparent, rgba(255, 176, 0, 0.55), transparent);
	box-shadow: 0 0 24px 6px rgba(255, 176, 0, 0.18);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.cerebro .signal {
		animation: signal-sweep 7s linear infinite;
	}
}
@keyframes signal-sweep {
	to {
		transform: translateX(350vw);
	}
}

/* ---------- Theme swatches for the picker ---------- */
.sw-wall {
	background: linear-gradient(120deg, #ff5545, #ffc857);
}
.sw-snowball {
	background: linear-gradient(120deg, #f0a6c8, #8fb6ff);
}
.sw-upside {
	background: linear-gradient(120deg, #ff3b3b, #7adcc3);
}
.sw-starcourt {
	background: linear-gradient(120deg, #ff5fa2, #00b4be);
}
.sw-arcade {
	background: linear-gradient(120deg, #ffd23f, #ff4f79);
}
.sw-cerebro {
	background: linear-gradient(120deg, #ffb000, #ff7a3d);
}

/* ==========================================================================
   Intensified theme animations (overrides — later in the cascade wins)
   ========================================================================== */

/* ---- The Wall: chasing light strings ---- */
.bg-set.wall .lights2 {
	transform: translate(26px, 14px);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.wall .lights {
		animation: wall-blink 2.4s ease-in-out infinite;
	}
	.bg-set.wall .lights2 {
		animation-delay: -1.2s;
	}
}
@keyframes wall-blink {
	0%,
	100% {
		opacity: 1;
	}
	50% {
		opacity: 0.25;
	}
}

/* ---- Snow Ball: faster swirling snow, brighter glint ---- */
@media (prefers-reduced-motion: no-preference) {
	.bg-set.snowball .flakes.f1 {
		animation: snow-swirl 11s linear infinite;
	}
	.bg-set.snowball .flakes.f2 {
		animation: snow-swirl 21s linear infinite reverse;
	}
	.bg-set.snowball .glint {
		animation: glint-sweep 4.5s ease-in-out infinite;
	}
}
@keyframes snow-swirl {
	0% {
		transform: translate(0, 0);
	}
	50% {
		transform: translate(34px, 130px);
	}
	100% {
		transform: translate(0, 260px);
	}
}

/* ---- Upside Down: dust storm + layered mountain landscape ---- */
.bg-set.upside .dust {
	position: absolute;
	inset: 0 0 -320px;
	background-image:
		radial-gradient(circle 2px at 12% 8%, rgba(215, 255, 235, 0.5) 60%, transparent 61%),
		radial-gradient(circle 1.5px at 34% 36%, rgba(215, 255, 235, 0.35) 60%, transparent 61%),
		radial-gradient(circle 2.5px at 56% 64%, rgba(215, 255, 235, 0.45) 60%, transparent 61%),
		radial-gradient(circle 1px at 72% 22%, rgba(215, 255, 235, 0.3) 60%, transparent 61%),
		radial-gradient(circle 2px at 88% 80%, rgba(215, 255, 235, 0.4) 60%, transparent 61%),
		radial-gradient(circle 1.5px at 22% 74%, rgba(215, 255, 235, 0.3) 60%, transparent 61%);
	background-size: 320px 320px;
}
.bg-set.upside .d2 {
	background-size: 520px 520px;
	opacity: 0.7;
}
.bg-set.upside .d3 {
	background-size: 180px 180px;
	opacity: 0.45;
	filter: blur(1px);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.upside .d1 {
		animation: dust-rise 26s linear infinite;
	}
	.bg-set.upside .d2 {
		animation: dust-rise 44s linear infinite reverse;
	}
	.bg-set.upside .d3 {
		animation: dust-rise 15s linear infinite;
	}
	.bg-set.upside .veins {
		animation: vein-throb 4s ease-in-out infinite alternate;
	}
}
@keyframes dust-rise {
	0% {
		transform: translate(0, 0);
	}
	50% {
		transform: translate(-28px, -160px);
	}
	100% {
		transform: translate(0, -320px);
	}
}
@keyframes vein-throb {
	from {
		opacity: 0.35;
	}
	to {
		opacity: 1;
	}
}
.bg-set.upside .mtn {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	/* the traced landscape (public/img/upside-mountains.svg, 1672x421, transparent
	   sky): cover keeps the bottom edge pinned and crops the sides when the
	   min-height wins */
	aspect-ratio: 1672 / 421;
	min-height: 26vh;
	background: url("../img/upside-mountains.svg") bottom center / cover no-repeat;
}
/* the red rim behind the ridgeline: the same silhouette, blurred, nudged up,
   painted in the theme's red — a glow the svg itself no longer carries */
.bg-set.upside .mtn-glow {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	aspect-ratio: 1672 / 421;
	min-height: 26vh;
	/* the blur lives on the parent so it softens the masked child's EDGES —
	   filter on the masked element itself blurs before masking and leaves a
	   crisp outline. No offset: an even blur of the ridge edge is the same
	   width everywhere, and .mtn hides the half that bleeds downhill */
	filter: blur(18px);
	opacity: 0.65;
}
/* the second, wider pass: same silhouette, much bigger blur, pushed up a
   touch — the glow dissolves into the sky instead of stopping */
.bg-set.upside .mtn-glow.g2 {
	filter: blur(46px);
	opacity: 0.4;
}
.bg-set.upside .mtn-glow::before {
	content: "";
	position: absolute;
	inset: 0;
	background: #c22a2a;
	-webkit-mask: url("../img/upside-mountains.svg") bottom center / cover no-repeat;
	mask: url("../img/upside-mountains.svg") bottom center / cover no-repeat;
}
.bg-set.upside .mtn-glow.g2::before {
	transform: translateY(-3%);
}

/* ---- Starcourt: striped retro sun + bouncing 80s shapes ---- */
.bg-set.starcourt .retro-sun {
	position: absolute;
	left: 50%;
	bottom: 4%;
	width: 320px;
	height: 320px;
	margin-left: -160px;
	border-radius: 50%;
	background: linear-gradient(#ffd76b, #ff5fa2 58%, #b455ff);
	-webkit-mask-image: repeating-linear-gradient(0deg, #000 0 22px, transparent 22px 30px);
	mask-image: repeating-linear-gradient(0deg, #000 0 22px, transparent 22px 30px);
	opacity: 0.55;
}
.bg-set.starcourt .zig {
	position: absolute;
	width: 96px;
	height: 26px;
	background: repeating-linear-gradient(115deg, transparent 0 10px, rgba(0, 180, 190, 0.35) 10px 15px);
}
.bg-set.starcourt .z1 {
	left: 14%;
	top: 58%;
	transform: rotate(-12deg);
}
.bg-set.starcourt .z2 {
	right: 16%;
	top: 14%;
	transform: rotate(9deg);
	background: repeating-linear-gradient(115deg, transparent 0 10px, rgba(255, 95, 162, 0.35) 10px 15px);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.starcourt .retro-sun {
		animation:
			sun-rise 12s ease-in-out infinite alternate,
			neon-flicker 7s steps(1) infinite;
	}
	.bg-set.starcourt .zig {
		animation: zig-bounce 4.5s ease-in-out infinite alternate;
	}
	.bg-set.starcourt .squiggle {
		animation: squiggle-spin 10s linear infinite;
	}
}
@keyframes sun-rise {
	from {
		transform: translateY(46px) scale(0.97);
	}
	to {
		transform: translateY(0) scale(1.03);
	}
}
@keyframes neon-flicker {
	0%,
	100% {
		filter: brightness(1);
	}
	81% {
		filter: brightness(1);
	}
	82% {
		filter: brightness(1.6);
	}
	84% {
		filter: brightness(0.8);
	}
	86% {
		filter: brightness(1.5);
	}
	88% {
		filter: brightness(1);
	}
}
@keyframes zig-bounce {
	from {
		transform: translateY(-26px) rotate(-14deg);
	}
	to {
		transform: translateY(26px) rotate(10deg);
	}
}
@keyframes squiggle-spin {
	to {
		transform: rotate(360deg);
	}
}

/* ---- Palace Arcade: Galaga fleet, starfield, ship + shots ---- */
.bg-set.arcade .astars {
	position: absolute;
	inset: -420px 0 0;
	background-image:
		radial-gradient(circle 1.5px at 18% 12%, rgba(255, 255, 255, 0.7) 60%, transparent 61%),
		radial-gradient(circle 1px at 46% 48%, rgba(255, 255, 255, 0.5) 60%, transparent 61%),
		radial-gradient(circle 2px at 72% 26%, rgba(160, 210, 255, 0.6) 60%, transparent 61%),
		radial-gradient(circle 1px at 88% 76%, rgba(255, 255, 255, 0.45) 60%, transparent 61%);
	background-size: 420px 420px;
}
.bg-set.arcade .a2 {
	background-size: 260px 260px;
	opacity: 0.5;
}
/* pixel-art Galaga bee: one 4px "pixel" + a box-shadow sprite grid */
.bg-set.arcade .galaga {
	position: relative;
	display: block;
	width: 4px;
	height: 4px;
	background: transparent;
	box-shadow:
		-8px 0 0 #4fa8ff,
		8px 0 0 #4fa8ff,
		-4px 4px 0 #ff4f79,
		0 4px 0 #ff4f79,
		4px 4px 0 #ff4f79,
		-8px 8px 0 #ffd23f,
		-4px 8px 0 #ffd23f,
		0 8px 0 #ffd23f,
		4px 8px 0 #ffd23f,
		8px 8px 0 #ffd23f,
		-12px 12px 0 #ffd23f,
		-4px 12px 0 #ffd23f,
		0 12px 0 #ffd23f,
		4px 12px 0 #ffd23f,
		12px 12px 0 #ffd23f,
		-4px 16px 0 #ff4f79,
		4px 16px 0 #ff4f79,
		-8px 20px 0 #4fa8ff,
		8px 20px 0 #4fa8ff;
}
.bg-set.arcade .fleet {
	position: absolute;
	top: 11%;
	left: 0;
	display: flex;
	gap: 58px;
}
/* player ship: white/red pixel triangle */
.bg-set.arcade .ship {
	position: absolute;
	bottom: 46px;
	left: 50%;
	width: 4px;
	height: 4px;
	background: transparent;
	box-shadow:
		0 -8px 0 #ff4f79,
		0 -4px 0 #f6edff,
		-4px 0 0 #f6edff,
		0 0 0 #f6edff,
		4px 0 0 #f6edff,
		-8px 4px 0 #f6edff,
		-4px 4px 0 #f6edff,
		0 4px 0 #ff4f79,
		4px 4px 0 #f6edff,
		8px 4px 0 #f6edff;
}
.bg-set.arcade .shot {
	position: absolute;
	bottom: 66px;
	left: 50%;
	width: 3px;
	height: 14px;
	background: #f6edff;
	box-shadow: 0 0 10px rgba(255, 255, 255, 0.8);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.arcade .a1 {
		animation: star-scroll 8s linear infinite;
	}
	.bg-set.arcade .a2 {
		animation: star-scroll 15s linear infinite;
	}
	.bg-set.arcade .fleet {
		animation: fleet-sweep 15s ease-in-out infinite;
	}
	.bg-set.arcade .galaga {
		animation: galaga-bob 1.8s ease-in-out infinite alternate;
	}
	.bg-set.arcade .g2 {
		animation-delay: -0.45s;
	}
	.bg-set.arcade .g3 {
		animation-delay: -0.9s;
	}
	.bg-set.arcade .g4 {
		animation-delay: -1.35s;
	}
	.bg-set.arcade .ship {
		animation: ship-strafe 6.5s ease-in-out infinite alternate;
	}
	.bg-set.arcade .shot {
		animation: shot-up 1.4s linear infinite;
	}
}
@keyframes star-scroll {
	to {
		transform: translateY(420px);
	}
}
@keyframes fleet-sweep {
	0% {
		transform: translateX(-14vw) translateY(0);
	}
	25% {
		transform: translateX(26vw) translateY(34px);
	}
	50% {
		transform: translateX(72vw) translateY(-8px);
	}
	75% {
		transform: translateX(32vw) translateY(-30px);
	}
	100% {
		transform: translateX(-14vw) translateY(0);
	}
}
@keyframes galaga-bob {
	from {
		transform: translateY(-7px) scale(1);
	}
	to {
		transform: translateY(7px) scale(1.08);
	}
}
@keyframes ship-strafe {
	from {
		transform: translateX(-110px);
	}
	to {
		transform: translateX(110px);
	}
}
@keyframes shot-up {
	0% {
		transform: translateY(0);
		opacity: 1;
	}
	85% {
		opacity: 1;
	}
	100% {
		transform: translateY(-72vh);
		opacity: 0;
	}
}

/* ---- Cerebro: twin fast signal sweeps + CRT breathing ---- */
.bg-set.cerebro .signal2 {
	top: 68%;
	opacity: 0.7;
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.cerebro .signal {
		animation: signal-sweep 3.6s linear infinite;
	}
	.bg-set.cerebro .signal2 {
		animation: signal-sweep 5.4s linear infinite;
		animation-delay: -2.2s;
	}
	.bg-set.cerebro .waves {
		animation: crt-breathe 5s ease-in-out infinite alternate;
	}
}
@keyframes crt-breathe {
	from {
		opacity: 0.75;
	}
	to {
		opacity: 1;
	}
}

/* ---------- Confirmation modal + danger buttons ---------- */
.confirm-modal {
	position: fixed;
	inset: 0;
	z-index: 90;
	display: flex;
}
/* .confirm-modal's display:flex outranks the global .hidden by source order —
   without this the modal can never be closed. */
.confirm-modal.hidden {
	display: none;
}
.confirm-modal {
	align-items: center;
	justify-content: center;
	background: rgba(0, 0, 0, 0.55);
	backdrop-filter: blur(6px);
	-webkit-backdrop-filter: blur(6px);
	padding: 20px;
}
.confirm-card {
	background: var(--panel);
	border: 1px solid var(--panel-brd);
	border-radius: var(--r-lg);
	box-shadow: var(--panel-glow);
	padding: 22px 24px;
	max-width: 440px;
	width: 100%;
}
.confirm-card h3 {
	margin: 0 0 8px;
	font-family: var(--font-display);
}
button.danger,
.primary.danger {
	background: #d33131 !important;
	color: #fff !important;
	border-color: #d33131 !important;
}
.ghost.danger {
	background: transparent !important;
	color: #e05555 !important;
	border-color: rgba(224, 85, 85, 0.5) !important;
}

/* ---------- Chapter headers in the story feed ---------- */
.story-line.header-line {
	text-align: center;
	border-bottom: 0;
}

/* ---------- Starcourt v2: Memphis shapes instead of the radial sun ---------- */
.bg-set.starcourt .retro-sun {
	display: none; /* superseded by the shape confetti */
}
.bg-set.starcourt .tri {
	position: absolute;
	width: 0;
	height: 0;
	border-left: 34px solid transparent;
	border-right: 34px solid transparent;
	border-bottom: 58px solid rgba(255, 184, 107, 0.4);
}
.bg-set.starcourt .t1 {
	left: 22%;
	top: 24%;
}
.bg-set.starcourt .t2 {
	right: 26%;
	bottom: 18%;
	border-bottom-color: rgba(180, 85, 255, 0.32);
	transform: rotate(24deg) scale(0.7);
}
.bg-set.starcourt .ring {
	position: absolute;
	border-radius: 50%;
	border: 12px solid rgba(255, 95, 162, 0.3);
}
.bg-set.starcourt .r1 {
	left: 7%;
	bottom: 24%;
	width: 110px;
	height: 110px;
}
.bg-set.starcourt .r2 {
	right: 8%;
	top: 44%;
	width: 62px;
	height: 62px;
	border-color: rgba(0, 180, 190, 0.35);
	border-width: 8px;
}
.bg-set.starcourt .memdots {
	position: absolute;
	left: 58%;
	top: 12%;
	width: 150px;
	height: 100px;
	background-image: radial-gradient(circle 4px, rgba(53, 40, 74, 0.28) 60%, transparent 61%);
	background-size: 26px 26px;
	transform: rotate(-8deg);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.starcourt .tri {
		animation: shape-tumble 8s ease-in-out infinite alternate;
	}
	.bg-set.starcourt .t2 {
		animation-duration: 11s;
		animation-delay: -4s;
	}
	.bg-set.starcourt .ring {
		animation: shape-pop 6s ease-in-out infinite alternate;
	}
	.bg-set.starcourt .r2 {
		animation-duration: 9s;
		animation-delay: -3s;
	}
	.bg-set.starcourt .memdots {
		animation: shape-tumble 13s ease-in-out infinite alternate;
	}
}
@keyframes shape-tumble {
	from {
		transform: translateY(-30px) rotate(-16deg);
	}
	to {
		transform: translateY(30px) rotate(18deg);
	}
}
@keyframes shape-pop {
	from {
		transform: scale(0.85) translateX(-22px);
	}
	to {
		transform: scale(1.12) translateX(22px);
	}
}

/* ---------- Active game: two-column layout (story left, people/controls right) ---------- */
/* The game page gets extra width so the sidebar doesn't squeeze the story. */
.main:has(#game:not(.hidden)) {
	max-width: 1200px;
}
/* Three columns while the host's drawer is open — story, who's here, settings —
   and two when it is closed. The drawer's width is the SAME custom property the
   solo editor's comments rail uses (--doc-side-w), because it is the same
   drawer: see js/components/side-drawer.js and the .doc-side rules further
   down, which give it its sticky card, its grip and its phone sheet. */
.game-cols {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 250px var(--doc-side-w, 300px);
	gap: 28px;
	align-items: start;
}
.game-cols.side-closed {
	grid-template-columns: minmax(0, 1fr) 250px;
}
.game-cols.side-closed .doc-side {
	display: none;
}
/* the drawer's card is sticky against the top of the window here — the game
   page has no sticky editor head to clear */
.game-cols .doc-side-card {
	top: 18px;
	max-height: calc(100vh - 36px);
	overflow: auto;
}
/* the settings scroll inside the card (like the comments pane) — the head
   and grip stay put, so a long rules form never gets clipped at the fold */
#hostPanel {
	flex: 1 1 auto;
	min-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain;
}
.game-main {
	position: relative;
	min-width: 0;
	background-color: var(--story-bg);
	border-radius: var(--r-md);
	padding: 16px;
}
.game-main > * {
	position: relative;
	z-index: 1;
}
.game-side {
	margin-top: 18px;
	display: flex;
	flex-direction: column;
	gap: 14px;
	min-width: 0;
}
.side-sec {
	border-radius: var(--r-md);
	padding: 16px 18px 18px;
}
.side-sec h4 {
	margin: 0 0 12px;
	font-size: 11px;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--muted);
}
.side-sec .players-row {
	margin: 0;
	flex-direction: column;
	align-items: stretch;
	gap: 9px;
}
.side-sec #hostRules {
	display: flex;
	flex-direction: column;
	gap: 10px;
	margin-bottom: 10px;
}
@media (max-width: 860px) {
	/* the drawer becomes a bottom sheet (the .doc-side phone rules do that
	   part); the grid just stops holding a column for it */
	.game-cols,
	.game-cols.side-closed {
		grid-template-columns: minmax(0, 1fr) 250px;
	}
	/* the sheet covers the foot of the page — give the story somewhere to end */
	.game-cols:not(.side-closed) .game-main {
		padding-bottom: calc(var(--doc-side-w, 300px) + 20px);
	}
}
@media (max-width: 760px) {
	.game-cols,
	.game-cols.side-closed {
		grid-template-columns: 1fr;
	}
}

/* ---------- Low-time alarm: the demogorgon stalks the side column, hugging
   the bottom under Writers & spectators, its warning at its feet.
   Final stretch only. ---------- */
.game-side {
	/* span the whole grid row (the grid aligns items start), so the column
	   has a real bottom for the demogorgon to hug */
	align-self: stretch;
}
#doomFx {
	display: none;
	pointer-events: none;
}
#doomFx.on {
	display: block;
	margin-top: auto;
	text-align: center;
}
#doomFx .doom-img {
	width: clamp(110px, 12vw, 190px);
	height: auto;
	animation: doom-in 0.5s ease-out;
}
#doomFx .doom-text {
	display: block;
	margin-top: 4px;
	font-family: var(--font-mono, monospace);
	font-weight: 800;
	font-size: 0.82rem;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: #e03131;
	white-space: nowrap;
	animation: doom-blink 1s step-start infinite;
}
@keyframes doom-blink {
	50% {
		opacity: 0;
	}
}
@keyframes doom-in {
	from {
		opacity: 0;
		transform: scale(0.85);
	}
	to {
		opacity: 1;
		transform: scale(1);
	}
}
@media (max-width: 760px) {
	/* it would cover the editor itself on a phone */
	#doomFx.on {
		display: none;
	}
}
@media (prefers-reduced-motion: reduce) {
	#doomFx .doom-img,
	#doomFx .doom-text {
		animation: none;
	}
}

/* ==========================================================================
   Stranger Things theme pack (10): Hawkins Lab, Castle Byers, Vecna's Clock,
   The Void, Family Video, Hellfire Club, Rink-O-Mania, Camp Know Where,
   Russian Bunker, The Pollywog
   ========================================================================== */

/* ---- Hawkins Lab: lockdown — dark steel, blinking danger lights, metallic
   gates, hazmat suits, and an open rift in the wall ---- */
[data-theme="hawkinslab"] {
	--bg: #0b0d10;
	--bg-2: #151a20;
	--panel: rgba(26, 32, 40, 0.66);
	--panel-2: rgba(34, 41, 51, 0.7);
	--story-bg: rgba(18, 23, 29, 0.74);
	--ink: #e8edf2;
	--muted: #8b98a5;
	--line: rgba(255, 46, 46, 0.24);
	--accent: #ff2e2e;
	--accent-2: #d8c23a;
	--good: #59d98c;
	--glow: rgba(255, 46, 46, 0.32);
	--grad-1: linear-gradient(120deg, #ff2e2e, #ff7a3d 55%, #d8c23a);
	--grad-hero: radial-gradient(120% 130% at 50% 0%, rgba(255, 46, 46, 0.14), transparent 70%);
	--panel-brd: rgba(139, 152, 165, 0.28);
	--panel-glow:
		0 18px 44px rgba(1, 2, 4, 0.65), 0 0 30px rgba(255, 46, 46, 0.07), inset 0 1px 0 rgba(230, 238, 245, 0.06);
	--btn-ink: #0b0d10;
	--chip: rgba(255, 46, 46, 0.12);
	--chip-brd: rgba(255, 46, 46, 0.4);
	--scroll-thumb: rgba(139, 152, 165, 0.4);
	--font-display: "Orbitron", sans-serif;
	--font-body: "Space Grotesk", sans-serif;
	--font-story: "Space Grotesk", sans-serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="hawkinslab"] .bg-set.hawkinslab {
	opacity: 1;
}
/* metallic gates: brushed vertical bars with riveted cross-beams */
.bg-set.hawkinslab .gates {
	position: absolute;
	inset: 0;
	background:
		repeating-linear-gradient(
			90deg,
			transparent 0 110px,
			rgba(160, 175, 190, 0.07) 110px 116px,
			rgba(210, 222, 235, 0.1) 116px 118px,
			rgba(160, 175, 190, 0.07) 118px 124px,
			transparent 124px 234px
		),
		repeating-linear-gradient(0deg, transparent 0 210px, rgba(190, 205, 220, 0.08) 210px 216px);
}
/* blinking danger lights strung along the top */
.bg-set.hawkinslab .danger {
	position: absolute;
	top: 12px;
	left: 0;
	right: 0;
	height: 14px;
	background-image:
		radial-gradient(circle 6px at 8% 50%, rgba(255, 46, 46, 0.9) 55%, transparent 60%),
		radial-gradient(circle 6px at 28% 50%, rgba(255, 46, 46, 0.85) 55%, transparent 60%),
		radial-gradient(circle 6px at 50% 50%, rgba(255, 46, 46, 0.9) 55%, transparent 60%),
		radial-gradient(circle 6px at 72% 50%, rgba(255, 46, 46, 0.85) 55%, transparent 60%),
		radial-gradient(circle 6px at 92% 50%, rgba(255, 46, 46, 0.9) 55%, transparent 60%);
	filter: drop-shadow(0 0 10px rgba(255, 46, 46, 0.8));
}
.bg-set.hawkinslab .dl2 {
	background-image:
		radial-gradient(circle 6px at 18% 50%, rgba(255, 46, 46, 0.85) 55%, transparent 60%),
		radial-gradient(circle 6px at 39% 50%, rgba(255, 46, 46, 0.9) 55%, transparent 60%),
		radial-gradient(circle 6px at 61% 50%, rgba(255, 46, 46, 0.85) 55%, transparent 60%),
		radial-gradient(circle 6px at 82% 50%, rgba(255, 46, 46, 0.9) 55%, transparent 60%);
}
/* hazmat suits: hooded yellow figures caught in the gloom */
.bg-set.hawkinslab .hazmat {
	position: absolute;
	bottom: 6%;
	width: 74px;
	height: 150px;
	background:
		radial-gradient(circle 21px at 50% 15%, rgba(216, 194, 58, 0.5) 92%, transparent),
		radial-gradient(circle 9px at 50% 15%, rgba(24, 30, 36, 0.85) 92%, transparent),
		radial-gradient(42px 62px ellipse at 50% 62%, rgba(216, 194, 58, 0.42) 92%, transparent);
	filter: blur(1px);
}
.bg-set.hawkinslab .h1 {
	left: 7%;
}
.bg-set.hawkinslab .h2 {
	right: 9%;
	transform: scale(0.8);
	opacity: 0.75;
}
/* the rift: a jagged glowing tear, breathing */
.bg-set.hawkinslab .rift {
	position: absolute;
	top: 26%;
	right: 16%;
	width: 120px;
	height: 46vh;
	clip-path: polygon(
		52% 0%,
		68% 12%,
		55% 26%,
		78% 40%,
		58% 55%,
		74% 68%,
		50% 84%,
		60% 100%,
		40% 92%,
		30% 72%,
		44% 58%,
		22% 44%,
		42% 30%,
		28% 14%
	);
	background: linear-gradient(180deg, rgba(255, 122, 61, 0.85), rgba(255, 46, 46, 0.75) 45%, rgba(160, 30, 60, 0.7));
	box-shadow: 0 0 80px 30px rgba(255, 60, 40, 0.25);
	filter: blur(1.5px);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.hawkinslab .danger {
		animation: danger-blink 1.1s steps(1) infinite;
	}
	.bg-set.hawkinslab .dl2 {
		animation-delay: -0.55s;
	}
	.bg-set.hawkinslab .rift {
		animation: rift-breathe 5s ease-in-out infinite;
	}
}
@keyframes danger-blink {
	0%,
	100% {
		opacity: 1;
	}
	50% {
		opacity: 0.12;
	}
}
@keyframes rift-breathe {
	0%,
	100% {
		opacity: 0.85;
		transform: scaleX(1);
	}
	50% {
		opacity: 1;
		transform: scaleX(1.12);
	}
}

/* ---- Castle Byers: deep forest night — pines all around, the wooden
   fort-castle standing in the trees, a flashlight beam through the dark ---- */
[data-theme="castlebyers"] {
	--bg: #0d130d;
	--bg-2: #182417;
	--panel: rgba(26, 36, 24, 0.64);
	--panel-2: rgba(34, 46, 30, 0.68);
	--story-bg: rgba(19, 27, 17, 0.72);
	--ink: #eef2df;
	--muted: #9cae8a;
	--line: rgba(255, 184, 77, 0.22);
	--accent: #ffb84d;
	--accent-2: #6f9a5a;
	--good: #8fbf6b;
	--glow: rgba(255, 184, 77, 0.3);
	--grad-1: linear-gradient(120deg, #ffb84d, #a9843f 50%, #6f9a5a);
	--grad-hero: radial-gradient(
		120% 130% at 50% 0%,
		rgba(255, 184, 77, 0.13),
		rgba(111, 154, 90, 0.06) 55%,
		transparent 75%
	);
	--panel-brd: rgba(111, 154, 90, 0.3);
	--panel-glow:
		0 18px 44px rgba(2, 5, 2, 0.6), 0 0 30px rgba(255, 184, 77, 0.05), inset 0 1px 0 rgba(238, 242, 223, 0.06);
	--btn-ink: #101809;
	--chip: rgba(255, 184, 77, 0.12);
	--chip-brd: rgba(255, 184, 77, 0.38);
	--scroll-thumb: rgba(111, 154, 90, 0.4);
	--font-display: "Fraunces", serif;
	--font-body: "Plus Jakarta Sans", sans-serif;
	--font-story: "Newsreader", serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="castlebyers"] .bg-set.castlebyers {
	opacity: 1;
	/* the four tree layers' inks, back (lightest) to front (darkest) — the SVGs
	   in components/castle-trees.js read these, so recolouring the forest is
	   editing these four lines */
	--cb-l4: #9da976;
	--cb-l3: #5f795d;
	--cb-l2: #2a5246;
	--cb-l1: #101010;
	background: linear-gradient(to bottom, #142415, #0a1810 70%, #06140f);
}
/* The forest: four traced pine layers (components/castle-trees.js), each
   leaning away from the pointer at its own data-depth (startMouseParallax in
   theme.js writes --cb-par per layer). The -12% inset is the bleed that lets a
   layer drift its full 26px without exposing an edge. The slow eased
   transition is the recentre when the pointer leaves; .live swaps in a fast
   linear one while it moves. */
.bg-set.castlebyers .cb-layer {
	position: absolute;
	inset: -12%;
	transform: var(--cb-par, none);
	will-change: transform;
	transition: transform 0.35s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.bg-set.castlebyers.live .cb-layer,
.bg-set.castlebyers.live .fortpic {
	transition: transform 0.12s linear;
}
.bg-set.castlebyers .cb-layer svg {
	width: 100%;
	height: 100%;
	display: block;
}
/* The forest floor. Without it the trees and the fort ended on nothing and the
   page's own background ran up under them; this is the brown-black leaf litter
   they stand in — the theme's own greens dragged down to almost-black earth,
   with a warmer scuff of needles across the front and one soft shadow where the
   fort meets the ground. It sits BETWEEN the trees and the fort, so the fort is
   planted in it rather than floating over it. */
.bg-set.castlebyers .cbground {
	position: absolute;
	inset: auto 0 0;
	height: 26%;
	background:
		radial-gradient(52% 80% at 50% 118%, rgba(0, 0, 0, 0.55), transparent 70%),
		radial-gradient(80% 60% at 22% 8%, rgba(38, 74, 62, 0.22), transparent 72%),
		radial-gradient(70% 55% at 78% 14%, rgba(30, 60, 50, 0.2), transparent 74%),
		linear-gradient(to top, #081009 0%, #0c1613 42%, rgba(10, 18, 15, 0.82) 72%, transparent 100%);
}

/* The mound the fort rests on (img/ground.svg, inked the forest-floor brown):
   centred on the same offset as the fort, a touch wider, its top rising just
   under the fort's base. Leans with the pointer at a depth a hair behind the
   fort's, so the fort stays planted on it as the scene shifts. */
.bg-set.castlebyers .cbmound {
	position: absolute;
	left: 50%;
	/* The blob's rounded base is pushed 220px below the screen edge (its top
	   stays at calc(30% + 200px)), so the earth runs all the way to the
	   bottom of the viewport instead of curving away above it. */
	bottom: -220px;
	width: min(1900px, 120vw);
	height: calc(30% + 420px);
	background: url("/img/ground.svg") center top / 100% 100% no-repeat;
	transform: translateX(calc(-50% + 200px)) var(--cb-par, translate3d(0, 0, 0));
	will-change: transform;
	transition: transform 0.35s cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* A pool of light on the ground under the fort, in the furthest tree line's
   ink (--cb-l4): an ellipse hugging the bottom, centred on the fort's own
   offset, screened over the mound so it lights the earth instead of
   painting over it. It leans between the mound and the fort so it stays
   under the castle as the scene shifts. */
.bg-set.castlebyers .cbspot {
	position: absolute;
	left: 50%;
	bottom: 0;
	width: min(1700px, 108vw);
	height: 22%;
	background: radial-gradient(
		48% 88% at 50% 100%,
		rgba(157, 169, 118, 0.5),
		rgba(157, 169, 118, 0.16) 55%,
		transparent 76%
	);
	mix-blend-mode: screen;
	transform: translateX(calc(-50% + 200px)) var(--cb-par, translate3d(0, 0, 0));
	will-change: transform;
	transition: transform 0.35s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.bg-set.castlebyers.live .cbspot {
	transition: transform 0.12s linear;
}

/* Castle Byers itself: the fort vector, front and center at the bottom,
   BEHIND the darkest tree layer (DOM order in chrome.js) so the nearest
   pines pass in front of it. It leans with the pointer like the trees —
   its data-depth sits between the two front layers — composed after its
   own centring translate. */
.bg-set.castlebyers .fortpic {
	position: absolute;
	left: 50%;
	/* The mound's top is at calc(30% + 200px); the fort's base sits 370px
	   below it so the walls stand deep IN the earth rather than on its rim. */
	bottom: calc(30% - 170px);
	width: min(1085px, 67vw);
	aspect-ratio: 1920 / 1072;
	background: url("/img/castle-byers.svg") center bottom / contain no-repeat;
	transform: translateX(calc(-50% + 200px)) var(--cb-par, translate3d(0, 0, 0));
	will-change: transform;
	transition: transform 0.35s cubic-bezier(0.22, 0.61, 0.36, 1);
	filter: drop-shadow(0 12px 24px rgba(0, 42, 38, 0.45));
	/* keeps the ::after tint blending with the fort alone, not the layers behind */
	isolation: isolate;
}
/* The fort sits in green light: a wash of the trees' own ink, masked to the
   fort's silhouette (same SVG as a mask) and blended hue-only, so the art
   keeps its values but takes the forest's shade. */
.bg-set.castlebyers .fortpic::after {
	content: "";
	position: absolute;
	inset: 0;
	background: var(--cb-l2, #2a5246);
	opacity: 0.45;
	mix-blend-mode: color;
	-webkit-mask: url("/img/castle-byers.svg") center bottom / contain no-repeat;
	mask: url("/img/castle-byers.svg") center bottom / contain no-repeat;
}

/* ---- Vecna's Clock: crimson dark, brass, a huge slow clock ---- */
[data-theme="vecna"] {
	--bg: #120507;
	--bg-2: #260a0e;
	--panel: rgba(46, 12, 16, 0.6);
	--panel-2: rgba(60, 16, 20, 0.64);
	--story-bg: rgba(34, 8, 11, 0.68);
	--ink: #ffd9d0;
	--muted: #c08a80;
	--line: rgba(224, 47, 47, 0.26);
	--accent: #e02f2f;
	--accent-2: #c98f4e;
	--good: #7fbf6b;
	--glow: rgba(224, 47, 47, 0.35);
	--grad-1: linear-gradient(120deg, #e02f2f, #a4243b 55%, #c98f4e);
	--grad-hero: radial-gradient(120% 130% at 50% 0%, rgba(224, 47, 47, 0.16), transparent 70%);
	--panel-brd: rgba(224, 47, 47, 0.28);
	--panel-glow:
		0 18px 44px rgba(8, 1, 2, 0.6), 0 0 30px rgba(224, 47, 47, 0.08), inset 0 1px 0 rgba(255, 220, 210, 0.05);
	--btn-ink: #1c0507;
	--chip: rgba(224, 47, 47, 0.12);
	--chip-brd: rgba(224, 47, 47, 0.4);
	--scroll-thumb: rgba(224, 47, 47, 0.35);
	--font-display: "Fraunces", serif;
	--font-body: "Newsreader", serif;
	--font-story: "Newsreader", serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="vecna"] .bg-set.vecna {
	opacity: 1;
}
/* ---- the grandfather clock (public/js/components/vecna-clock.js) ----------
   Vecna's clock as the object it actually is: a long-case clock standing where
   it has no business standing. It is the subject of the background, centred and
   nearly floor-height, with the old ghost dial still turning behind it as the
   sky. Colours are --vc-* properties so the case re-skins in CSS. */
.bg-set.vecna .grandfather {
	position: absolute;
	inset: auto 0 -2%;
	height: 92vh;
	display: flex;
	justify-content: center;
	opacity: 0.9;
	--vc-wood: #2a0f12;
	--vc-wood-lit: #4a1d1f;
	--vc-wood-dark: #150507;
	--vc-line: rgba(6, 1, 2, 0.75);
	--vc-brass: #c98f4e;
	--vc-face: #23161a;
	--vc-face-lit: #3a2228;
	--vc-hand: #e02f2f;
}
.bg-set.vecna .vc-clock-svg {
	height: 100%;
	width: auto;
	max-width: 100%;
}
.bg-set.vecna .vc-wood {
	fill: url(#vc-grain);
	stroke: var(--vc-line);
	stroke-width: 1.4;
}
.bg-set.vecna .vc-mould {
	fill: var(--vc-wood-lit);
	stroke: var(--vc-line);
	stroke-width: 1.2;
}
.bg-set.vecna .vc-edge {
	fill: var(--vc-wood-lit);
	opacity: 0.5;
}
/* the waist door: dark glass, brass frame */
.bg-set.vecna .vc-pane {
	fill: #0c0407;
	opacity: 0.9;
}
.bg-set.vecna .vc-frame {
	fill: none;
	stroke: var(--vc-brass);
	stroke-width: 2;
	opacity: 0.55;
}
.bg-set.vecna .vc-rod {
	stroke: var(--vc-brass);
	stroke-width: 3;
	opacity: 0.7;
}
.bg-set.vecna .vc-bob {
	fill: var(--vc-brass);
	opacity: 0.72;
}
.bg-set.vecna .vc-bob-in {
	fill: #7a5326;
	opacity: 0.8;
}
/* The pendulum is the only moving part, hinged at its pivot. A stopped clock is
   a prop; this one is counting down to something. */
@media (prefers-reduced-motion: no-preference) {
	.bg-set.vecna #vc-pend {
		transform-origin: 160px 330px; /* CLOCK.cx, CLOCK.pivotY */
		animation: vc-swing 3.4s cubic-bezier(0.45, 0, 0.55, 1) infinite alternate;
	}
}
@keyframes vc-swing {
	from {
		transform: rotate(-7deg);
	}
	to {
		transform: rotate(7deg);
	}
}
/* the face */
.bg-set.vecna .vc-face {
	fill: url(#vc-facepaint);
	stroke: var(--vc-brass);
	stroke-width: 3;
	opacity: 0.95;
}
.bg-set.vecna .vc-chapter {
	fill: none;
	stroke: var(--vc-brass);
	stroke-width: 1;
	opacity: 0.4;
}
.bg-set.vecna .vc-tick {
	stroke: var(--vc-brass);
	stroke-width: 1.2;
	opacity: 0.45;
}
.bg-set.vecna .vc-num {
	fill: var(--vc-brass);
	font-family: var(--font-display);
	font-size: 13px;
	text-anchor: middle;
	opacity: 0.8;
}
.bg-set.vecna .vc-hour,
.bg-set.vecna .vc-min {
	stroke: var(--vc-hand);
	stroke-linecap: round;
	filter: none;
}
.bg-set.vecna .vc-hour {
	stroke-width: 5;
}
.bg-set.vecna .vc-min {
	stroke-width: 3;
}
.bg-set.vecna .vc-cap {
	fill: var(--vc-hand);
}

.bg-set.vecna .clockface {
	position: absolute;
	top: 50%;
	left: 50%;
	width: min(120vh, 100vw);
	aspect-ratio: 1;
	transform: translate(-50%, -50%);
	border-radius: 50%;
	border: 1px solid rgba(201, 143, 78, 0.22);
	background:
		radial-gradient(circle at 50% 50%, transparent 62%, rgba(201, 143, 78, 0.06) 63%, transparent 66%),
		repeating-conic-gradient(from 0deg, rgba(201, 143, 78, 0.16) 0deg 0.7deg, transparent 0.7deg 30deg);
	-webkit-mask: radial-gradient(circle, transparent 55%, #000 56%);
	mask: radial-gradient(circle, transparent 55%, #000 56%);
	opacity: 0.32;
}
.bg-set.vecna .cvines {
	position: absolute;
	inset: auto 0 0;
	height: 34%;
	background:
		radial-gradient(60% 120% at 10% 100%, rgba(74, 12, 18, 0.7), transparent 70%),
		radial-gradient(50% 110% at 90% 100%, rgba(60, 10, 14, 0.7), transparent 70%);
}
.bg-set.vecna .embers {
	position: absolute;
	inset: 0;
	background-image:
		radial-gradient(circle 2px at 20% 70%, rgba(255, 120, 90, 0.5) 90%, transparent),
		radial-gradient(circle 2px at 70% 40%, rgba(255, 120, 90, 0.4) 90%, transparent),
		radial-gradient(circle 3px at 85% 75%, rgba(224, 47, 47, 0.4) 90%, transparent);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.vecna .clockface {
		animation: vecna-turn 240s linear infinite;
	}
}
@keyframes vecna-turn {
	to {
		transform: translate(-50%, -50%) rotate(360deg);
	}
}

/* ---- The Void: a dark desert under alien sky — flying stars overhead, a
   glaring red horizon, space rocks painting the landscape ---- */
[data-theme="void"] {
	--bg: #060407;
	--bg-2: #140a0e;
	--panel: rgba(22, 14, 18, 0.72);
	--panel-2: rgba(30, 18, 23, 0.75);
	--story-bg: rgba(16, 10, 13, 0.78);
	--ink: #f0e8ec;
	--muted: #a38b95;
	--line: rgba(255, 64, 64, 0.24);
	--accent: #ff4040;
	--accent-2: #9fb6ff;
	--good: #6fd3a8;
	--glow: rgba(255, 64, 64, 0.3);
	--grad-1: linear-gradient(120deg, #ff4040, #a4243b 55%, #9fb6ff);
	--grad-hero: radial-gradient(120% 130% at 50% 100%, rgba(255, 64, 64, 0.16), transparent 60%);
	--panel-brd: rgba(255, 64, 64, 0.22);
	--panel-glow:
		0 18px 44px rgba(0, 0, 0, 0.7), 0 0 30px rgba(255, 64, 64, 0.06), inset 0 1px 0 rgba(240, 230, 235, 0.05);
	--btn-ink: #0c0608;
	--chip: rgba(255, 64, 64, 0.12);
	--chip-brd: rgba(255, 64, 64, 0.35);
	--scroll-thumb: rgba(163, 139, 149, 0.35);
	--font-display: "Space Grotesk", sans-serif;
	--font-body: "Space Grotesk", sans-serif;
	--font-story: "Newsreader", serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="void"] .bg-set.void {
	opacity: 1;
}
/* flying stars: two tiled star sheets streaking across the black */
.bg-set.void .stars {
	position: absolute;
	inset: -10% -100% 30% 0;
	background-image:
		radial-gradient(circle 1.5px at 4% 22%, rgba(240, 235, 255, 0.9) 90%, transparent),
		radial-gradient(circle 1px at 18% 64%, rgba(240, 235, 255, 0.7) 90%, transparent),
		radial-gradient(circle 2px at 31% 12%, rgba(255, 200, 200, 0.8) 90%, transparent),
		radial-gradient(circle 1px at 47% 48%, rgba(240, 235, 255, 0.65) 90%, transparent),
		radial-gradient(circle 1.5px at 62% 28%, rgba(240, 235, 255, 0.85) 90%, transparent),
		radial-gradient(circle 1px at 76% 70%, rgba(255, 210, 210, 0.6) 90%, transparent),
		radial-gradient(circle 2px at 89% 40%, rgba(240, 235, 255, 0.8) 90%, transparent);
	background-size: 50% 100%;
	background-repeat: repeat-x;
}
.bg-set.void .st2 {
	opacity: 0.55;
	background-size: 34% 100%;
}
/* the glaring red horizon line burning behind the rocks */
.bg-set.void .horizon {
	position: absolute;
	inset: auto 0 0;
	height: 44vh;
	background:
		linear-gradient(to top, rgba(255, 64, 64, 0.2), transparent 65%),
		radial-gradient(140% 34% at 50% 100%, rgba(255, 64, 64, 0.55), rgba(190, 30, 50, 0.25) 55%, transparent 78%);
}
.bg-set.void .horizon::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: 24vh;
	height: 2px;
	background: linear-gradient(
		90deg,
		transparent,
		rgba(255, 110, 90, 0.75),
		rgba(255, 64, 64, 0.55),
		rgba(255, 110, 90, 0.75),
		transparent
	);
	filter: blur(1px);
	box-shadow: 0 0 26px 6px rgba(255, 64, 64, 0.35);
}
/* orange lightning: the tattoo bolt artwork (img/bolt.svg) used as a mask
   over an orange gradient, striking from the top every 7 seconds */
.bg-set.void .bolt {
	position: absolute;
	top: 0;
	left: 56%;
	width: 34vh;
	height: 54vh;
	background: linear-gradient(
		rgba(255, 220, 150, 0) 0%,
		rgba(255, 220, 150, 0.45) 9%,
		rgba(255, 220, 150, 0.95) 22%,
		rgba(255, 150, 60, 0.9) 55%,
		rgba(255, 100, 45, 0.85)
	);
	-webkit-mask: url("/img/bolt.svg") center top / contain no-repeat;
	mask: url("/img/bolt.svg") center top / contain no-repeat;
	filter: drop-shadow(0 0 18px rgba(255, 150, 60, 0.8));
	opacity: 0;
}
.bg-set.void .bolt2 {
	left: 12%;
	width: 27vh;
	height: 43vh;
	transform: scaleX(-1) rotate(4deg);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.void .bolt {
		animation: void-strike 7s linear infinite;
	}
	.bg-set.void .bolt2 {
		animation-delay: -3.2s;
	}
}
/* strike fast, linger: double-flash then a slow ~1.2s afterglow fade-out */
@keyframes void-strike {
	0%,
	78% {
		opacity: 0;
	}
	79% {
		opacity: 1;
	}
	81% {
		opacity: 0.2;
	}
	83% {
		opacity: 0.95;
	}
	100% {
		opacity: 0;
	}
}

/* the upper half of the horizon: a radial orange burst domed over the line */
.bg-set.void .glare {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 24vh;
	height: 42vh;
	background:
		radial-gradient(
			58% 100% at 50% 100%,
			rgba(255, 150, 60, 0.5),
			rgba(255, 100, 45, 0.26) 42%,
			rgba(255, 70, 45, 0.1) 65%,
			transparent 80%
		),
		radial-gradient(24% 46% at 50% 100%, rgba(255, 200, 120, 0.4), transparent 75%);
}
/* tall jagged mountains standing in front of the orange burst */
.bg-set.void .mtns {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 21vh;
	height: 30vh;
	background:
		conic-gradient(from 78deg at 8% 100%, rgba(10, 5, 7, 0.95) 0 24deg, transparent 24deg),
		conic-gradient(from 74deg at 19% 100%, rgba(14, 7, 10, 0.92) 0 32deg, transparent 32deg),
		conic-gradient(from 80deg at 33% 100%, rgba(10, 5, 7, 0.94) 0 20deg, transparent 20deg),
		conic-gradient(from 76deg at 46% 100%, rgba(14, 7, 10, 0.9) 0 28deg, transparent 28deg),
		conic-gradient(from 79deg at 58% 100%, rgba(10, 5, 7, 0.95) 0 22deg, transparent 22deg),
		conic-gradient(from 73deg at 71% 100%, rgba(14, 7, 10, 0.92) 0 34deg, transparent 34deg),
		conic-gradient(from 80deg at 84% 100%, rgba(10, 5, 7, 0.94) 0 20deg, transparent 20deg),
		conic-gradient(from 75deg at 95% 100%, rgba(14, 7, 10, 0.93) 0 30deg, transparent 30deg);
}
/* space rocks: jagged black monoliths silhouetted against the glare */
.bg-set.void .rocks {
	position: absolute;
	inset: auto 0 0;
	height: 26vh;
	background:
		conic-gradient(from 55deg at 5% 100%, rgba(8, 4, 6, 0.96) 0 70deg, transparent 70deg),
		conic-gradient(from 68deg at 14% 100%, rgba(12, 6, 9, 0.94) 0 44deg, transparent 44deg),
		conic-gradient(from 50deg at 27% 100%, rgba(8, 4, 6, 0.9) 0 80deg, transparent 80deg),
		conic-gradient(from 72deg at 43% 100%, rgba(12, 6, 9, 0.92) 0 36deg, transparent 36deg),
		conic-gradient(from 58deg at 60% 100%, rgba(8, 4, 6, 0.94) 0 64deg, transparent 64deg),
		conic-gradient(from 66deg at 74% 100%, rgba(12, 6, 9, 0.9) 0 48deg, transparent 48deg),
		conic-gradient(from 52deg at 88% 100%, rgba(8, 4, 6, 0.96) 0 76deg, transparent 76deg),
		conic-gradient(from 70deg at 98% 100%, rgba(12, 6, 9, 0.92) 0 40deg, transparent 40deg),
		linear-gradient(to top, rgba(6, 3, 5, 0.98) 12%, transparent 55%);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.void .stars {
		animation: void-fly 26s linear infinite;
	}
	.bg-set.void .st2 {
		animation-duration: 44s;
	}
	.bg-set.void .horizon {
		animation: void-glare 7s ease-in-out infinite;
	}
	.bg-set.void .glare {
		animation: void-glare 7s ease-in-out infinite;
		animation-delay: -3.5s;
	}
}
@keyframes void-fly {
	to {
		transform: translateX(-50vw);
	}
}
@keyframes void-glare {
	0%,
	100% {
		opacity: 0.85;
	}
	50% {
		opacity: 1;
	}
}

/* ---- Family Video: late-80s rental teal/magenta, VHS tracking ---- */
[data-theme="video"] {
	--bg: #101821;
	--bg-2: #16222e;
	--panel: rgba(22, 36, 46, 0.62);
	--panel-2: rgba(28, 44, 56, 0.66);
	--story-bg: rgba(16, 28, 37, 0.7);
	--ink: #e8f6ff;
	--muted: #8fb2c4;
	--line: rgba(41, 211, 199, 0.24);
	--accent: #ff4fd8;
	--accent-2: #29d3c7;
	--good: #59d98c;
	--glow: rgba(255, 79, 216, 0.32);
	--grad-1: linear-gradient(120deg, #ff4fd8, #7d6bff 50%, #29d3c7);
	--grad-hero: radial-gradient(
		120% 130% at 50% 0%,
		rgba(255, 79, 216, 0.14),
		rgba(41, 211, 199, 0.05) 55%,
		transparent 75%
	);
	--panel-brd: rgba(41, 211, 199, 0.26);
	--panel-glow:
		0 18px 44px rgba(2, 8, 12, 0.6), 0 0 30px rgba(255, 79, 216, 0.07), inset 0 1px 0 rgba(220, 245, 255, 0.06);
	--btn-ink: #0b1218;
	--chip: rgba(255, 79, 216, 0.12);
	--chip-brd: rgba(255, 79, 216, 0.4);
	--scroll-thumb: rgba(41, 211, 199, 0.35);
	--font-display: "Orbitron", sans-serif;
	--font-body: "Space Grotesk", sans-serif;
	--font-story: "Space Grotesk", sans-serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="video"] .bg-set.video {
	opacity: 1;
}
.bg-set.video .vscan {
	position: absolute;
	inset: 0;
	background: repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.18) 0 2px, transparent 2px 5px);
}
.bg-set.video .track {
	position: absolute;
	left: 0;
	right: 0;
	top: -12%;
	height: 26px;
	background: linear-gradient(rgba(255, 255, 255, 0.06), rgba(41, 211, 199, 0.14), rgba(255, 255, 255, 0.06));
	filter: blur(1px);
}
.bg-set.video .vhsglow {
	position: absolute;
	right: -8%;
	top: 12%;
	width: 34vw;
	height: 34vw;
	background: radial-gradient(circle, rgba(255, 79, 216, 0.12), transparent 60%);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.video .track {
		animation: vhs-track 9s linear infinite;
	}
}
@keyframes vhs-track {
	to {
		transform: translateY(130vh);
	}
}

/* ---- Hellfire Club: D&D table — graph paper, tumbling d20s ---- */
[data-theme="hellfire"] {
	--bg: #0d0507;
	--bg-2: #1c0a10;
	--panel: rgba(36, 12, 18, 0.62);
	--panel-2: rgba(46, 16, 22, 0.66);
	--story-bg: rgba(26, 9, 13, 0.7);
	--ink: #ffe3e3;
	--muted: #bd8b93;
	--line: rgba(224, 47, 47, 0.24);
	--accent: #e02f2f;
	--accent-2: #b7b7c9;
	--good: #8fbf6b;
	--glow: rgba(224, 47, 47, 0.32);
	--grad-1: linear-gradient(120deg, #e02f2f, #8d2033 55%, #b7b7c9);
	--grad-hero: radial-gradient(120% 130% at 50% 0%, rgba(224, 47, 47, 0.15), transparent 70%);
	--panel-brd: rgba(224, 47, 47, 0.28);
	--panel-glow:
		0 18px 44px rgba(6, 1, 2, 0.6), 0 0 30px rgba(224, 47, 47, 0.08), inset 0 1px 0 rgba(255, 225, 225, 0.05);
	--btn-ink: #180508;
	--chip: rgba(224, 47, 47, 0.12);
	--chip-brd: rgba(224, 47, 47, 0.4);
	--scroll-thumb: rgba(224, 47, 47, 0.35);
	--font-display: "Fraunces", serif;
	--font-body: "Plus Jakarta Sans", sans-serif;
	--font-story: "Newsreader", serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="hellfire"] .bg-set.hellfire {
	opacity: 1;
}
.bg-set.hellfire .graph {
	position: absolute;
	inset: 0;
	background:
		repeating-linear-gradient(0deg, transparent 0 30px, rgba(224, 47, 47, 0.07) 30px 31px),
		repeating-linear-gradient(90deg, transparent 0 30px, rgba(224, 47, 47, 0.07) 30px 31px);
}
/* the furnace: a breathing orange-red gradient + glow BEHIND the black flames */
.bg-set.hellfire .fireglow {
	position: absolute;
	inset: auto 0 0;
	height: 34vh;
	background:
		radial-gradient(60% 55% at 50% 100%, rgba(255, 122, 40, 0.4), rgba(224, 47, 47, 0.22) 45%, transparent 75%),
		radial-gradient(30% 40% at 22% 100%, rgba(255, 152, 60, 0.3), transparent 70%),
		radial-gradient(30% 40% at 78% 100%, rgba(255, 152, 60, 0.3), transparent 70%),
		linear-gradient(to top, rgba(224, 47, 47, 0.28), rgba(120, 20, 20, 0.12) 55%, transparent 85%);
}
/* embers: glowing sparks rising out of the fire */
.bg-set.hellfire .hembers {
	position: absolute;
	inset: 0;
	background-image:
		radial-gradient(circle 2.5px at 12% 96%, rgba(255, 180, 90, 0.95) 55%, transparent 60%),
		radial-gradient(circle 1.5px at 27% 92%, rgba(255, 122, 40, 0.9) 55%, transparent 60%),
		radial-gradient(circle 2px at 43% 97%, rgba(255, 90, 50, 0.9) 55%, transparent 60%),
		radial-gradient(circle 1.5px at 58% 94%, rgba(255, 180, 90, 0.85) 55%, transparent 60%),
		radial-gradient(circle 2.5px at 72% 96%, rgba(255, 122, 40, 0.9) 55%, transparent 60%),
		radial-gradient(circle 1.5px at 88% 93%, rgba(255, 90, 50, 0.85) 55%, transparent 60%);
	background-repeat: no-repeat;
	filter: drop-shadow(0 0 6px rgba(255, 122, 40, 0.7));
}
.bg-set.hellfire .e2 {
	background-position: 42px 0; /* offset spark field (transform belongs to the rise animation) */
	opacity: 0.7;
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.hellfire .fireglow {
		animation: hellfire-glow 3.4s ease-in-out infinite;
	}
	.bg-set.hellfire .hembers {
		animation: ember-rise 7s linear infinite;
	}
	.bg-set.hellfire .e2 {
		animation-duration: 11s;
		animation-delay: -4s;
	}
}
@keyframes hellfire-glow {
	0%,
	100% {
		opacity: 0.75;
	}
	50% {
		opacity: 1;
	}
}
@keyframes ember-rise {
	0% {
		transform: translateY(0);
		opacity: 0;
	}
	8% {
		opacity: 1;
	}
	70% {
		opacity: 0.8;
	}
	100% {
		transform: translateY(-88vh) translateX(24px);
		opacity: 0;
	}
}

/* black flame silhouettes (img/flames.png — true alpha, background removed)
   tiling the bottom edge */
.bg-set.hellfire .flames {
	position: absolute;
	inset: auto 0 -2px;
	height: 24vh;
	transform-origin: 50% 100%;
	background: url("/img/flames.png") left bottom / auto 100% repeat-x;
}
.bg-set.hellfire .fl2 {
	height: 17vh;
	opacity: 0.75;
	background-position-x: 340px; /* offset tile so the two rows interleave */
}
/* the real twenty-sided die artwork (img/d20.png) */
.bg-set.hellfire .die {
	position: absolute;
	width: 140px;
	height: 140px;
	background: url("/img/d20.png") center / contain no-repeat;
	opacity: 0.5;
	filter: drop-shadow(0 6px 22px rgba(224, 47, 47, 0.35));
}
.bg-set.hellfire .d20a {
	top: 14%;
	left: 6%;
	transform: rotate(-12deg);
}
.bg-set.hellfire .d20b {
	bottom: 26%;
	right: 7%;
	transform: rotate(15deg) scale(0.6);
	opacity: 0.4;
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.hellfire .flames {
		animation: hellfire-flicker 2.6s ease-in-out infinite;
	}
	.bg-set.hellfire .fl2 {
		animation: hellfire-flicker 1.9s ease-in-out infinite reverse;
	}
}
@keyframes hellfire-flicker {
	0%,
	100% {
		transform: scaleY(1);
		opacity: 1;
	}
	35% {
		transform: scaleY(1.12);
		opacity: 0.85;
	}
	70% {
		transform: scaleY(0.94);
		opacity: 1;
	}
}

/* ---- Rink-O-Mania: obnoxiously rainbow, aggressively 80s. Full-spectrum
   stripes, memphis zigzags, disco twinkle, hue-cycling glow. ---- */
[data-theme="rink"] {
	--bg: #14061f;
	--bg-2: #2a0b3d;
	--panel: rgba(44, 16, 66, 0.6);
	--panel-2: rgba(58, 22, 84, 0.64);
	--story-bg: rgba(28, 10, 44, 0.7);
	--ink: #fff3fc;
	--muted: #d0a6e6;
	--line: rgba(255, 224, 64, 0.3);
	--accent: #ff2fb9;
	--accent-2: #2fd6ff;
	--good: #b6f542;
	--glow: rgba(255, 47, 185, 0.45);
	--grad-1: linear-gradient(90deg, #ff3355, #ff9d2e 20%, #ffe040 40%, #4fe35f 60%, #2fd6ff 80%, #b45cff);
	--grad-hero: linear-gradient(
		135deg,
		rgba(255, 51, 85, 0.2),
		rgba(255, 224, 64, 0.14) 35%,
		rgba(47, 214, 255, 0.16) 70%,
		rgba(180, 92, 255, 0.2)
	);
	--panel-brd: rgba(255, 47, 185, 0.4);
	--panel-glow:
		0 18px 44px rgba(8, 2, 14, 0.6), 0 0 36px rgba(255, 47, 185, 0.16), inset 0 1px 0 rgba(255, 240, 250, 0.08);
	--btn-ink: #1c0630;
	--chip: rgba(255, 224, 64, 0.14);
	--chip-brd: rgba(255, 224, 64, 0.5);
	--scroll-thumb: rgba(255, 47, 185, 0.5);
	--font-display: "Orbitron", sans-serif;
	--font-body: "Plus Jakarta Sans", sans-serif;
	--font-story: "Space Grotesk", sans-serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="rink"] .bg-set.rink {
	opacity: 1;
}
/* the rainbow: a fat full-spectrum ribbon banking across the whole rink */
.bg-set.rink .rbow {
	position: absolute;
	inset: -20%;
	background: linear-gradient(
		115deg,
		transparent 0 26%,
		rgba(255, 51, 85, 0.34) 26% 31%,
		rgba(255, 157, 46, 0.34) 31% 36%,
		rgba(255, 224, 64, 0.34) 36% 41%,
		rgba(79, 227, 95, 0.32) 41% 46%,
		rgba(47, 214, 255, 0.32) 46% 51%,
		rgba(180, 92, 255, 0.34) 51% 56%,
		transparent 56%
	);
}
/* memphis zigzags — the carpet pattern that follows you home */
.bg-set.rink .zigzag {
	position: absolute;
	inset: auto 0 0;
	height: 24vh;
	background:
		repeating-linear-gradient(45deg, transparent 0 26px, rgba(255, 224, 64, 0.22) 26px 34px, transparent 34px 60px),
		repeating-linear-gradient(-45deg, transparent 0 26px, rgba(47, 214, 255, 0.2) 26px 34px, transparent 34px 60px),
		repeating-linear-gradient(90deg, transparent 0 70px, rgba(255, 47, 185, 0.16) 70px 76px);
	-webkit-mask: linear-gradient(transparent, #000 40%);
	mask: linear-gradient(transparent, #000 40%);
}
.bg-set.rink .specks {
	position: absolute;
	inset: 0;
	background-image:
		radial-gradient(circle 3px at 12% 20%, rgba(255, 224, 64, 0.85) 90%, transparent),
		radial-gradient(circle 2px at 34% 64%, rgba(47, 214, 255, 0.85) 90%, transparent),
		radial-gradient(circle 3px at 58% 30%, rgba(255, 47, 185, 0.85) 90%, transparent),
		radial-gradient(circle 2px at 81% 70%, rgba(79, 227, 95, 0.8) 90%, transparent),
		radial-gradient(circle 3px at 92% 18%, rgba(180, 92, 255, 0.8) 90%, transparent),
		radial-gradient(circle 2px at 22% 84%, rgba(255, 157, 46, 0.8) 90%, transparent),
		radial-gradient(circle 2px at 68% 88%, rgba(255, 255, 255, 0.75) 90%, transparent);
}
.bg-set.rink .p2 {
	transform: translate(40px, 60px) scale(1.3);
	opacity: 0.7;
}
.bg-set.rink .sweep {
	position: absolute;
	top: -20%;
	left: 50%;
	width: 70vw;
	height: 140vh;
	transform-origin: 50% 0;
	background: conic-gradient(
		from 4deg at 50% 0%,
		transparent 0deg,
		rgba(255, 51, 85, 0.14) 5deg,
		rgba(255, 224, 64, 0.14) 10deg,
		rgba(47, 214, 255, 0.14) 15deg,
		transparent 22deg
	);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.rink .rbow {
		animation:
			rink-rainbow-ride 14s linear infinite alternate,
			rink-hue 9s linear infinite;
	}
	.bg-set.rink .zigzag {
		animation: rink-hue 7s linear infinite reverse;
	}
	.bg-set.rink .specks {
		animation: rink-twinkle 1.6s ease-in-out infinite;
	}
	.bg-set.rink .p2 {
		animation-delay: -0.8s;
	}
	.bg-set.rink .sweep {
		animation: rink-sweep 8s ease-in-out infinite alternate;
	}
}
@keyframes rink-rainbow-ride {
	from {
		transform: translateX(-14%);
	}
	to {
		transform: translateX(14%);
	}
}
@keyframes rink-hue {
	to {
		filter: hue-rotate(360deg);
	}
}
@keyframes rink-twinkle {
	0%,
	100% {
		opacity: 1;
	}
	50% {
		opacity: 0.3;
	}
}
@keyframes rink-sweep {
	from {
		transform: translateX(-50%) rotate(-24deg);
	}
	to {
		transform: translateX(-50%) rotate(24deg);
	}
}

/* ---- Cleradin: a cleric and a paladin, moonlight on a castle that never
   ends. Muted blood-red and dark violet, and three silhouette layers that
   parallax vertically as the page scrolls (theme.js drives the transform;
   without JS or with reduced motion they simply sit still). ---- */
[data-theme="cleradin"] {
	--bg: #0b0710;
	--bg-2: #150e1d;
	--panel: rgba(31, 20, 42, 0.62);
	--panel-2: rgba(38, 24, 50, 0.68);
	--story-bg: rgba(22, 14, 30, 0.72);
	--ink: #e6dcf0;
	--muted: #a08fb8;
	--line: rgba(150, 110, 190, 0.24);
	--accent: #8b3a4e;
	--accent-2: #6d4b96;
	--good: #7d9a72;
	--glow: rgba(120, 80, 160, 0.28);
	--grad-1: linear-gradient(120deg, #8b3a4e, #5c3a6e 55%, #6d4b96);
	--grad-hero: radial-gradient(120% 130% at 50% 0%, rgba(120, 80, 160, 0.16), transparent 70%);
	--panel-brd: rgba(150, 110, 190, 0.24);
	--panel-glow:
		0 18px 46px rgba(3, 1, 6, 0.65), 0 0 34px rgba(109, 75, 150, 0.08), inset 0 1px 0 rgba(230, 220, 240, 0.05);
	--btn-ink: #f2ecf8;
	--chip: rgba(139, 58, 78, 0.16);
	--chip-brd: rgba(150, 110, 190, 0.34);
	--scroll-thumb: rgba(150, 110, 190, 0.35);
	--font-display: "Fraunces", serif;
	--font-body: "Plus Jakarta Sans", sans-serif;
	--font-story: "Newsreader", serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="cleradin"] .bg-set.cleradin {
	opacity: 1;
}
/* The moon, hanging opposite the tower. It does not parallax — it is the one
   thing far enough away to stay put.

   Two layers doing two jobs. The element itself is the HALO: the soft gradient
   that was here before, which is what makes the moon sit in air rather than on
   the page. Its ::before is a photograph of the moon (img/moon.webp) laid over
   that halo in `screen`, which is the whole trick — the photo's black sky
   contributes nothing under screen, so the surface arrives with no cut-out and
   no matte, and the halo shows straight through the dark maria. A radial mask
   feathers the disc's edge so the photo's own limb never lands as a hard
   circle, and the theme's violet is dropped back over the top in `color` so
   the moon belongs to this palette instead of being a grey guest in it. */
.bg-set.cleradin .moon {
	position: absolute;
	/* `isolation: isolate` matters for more than tidiness: without it the
	   screen/color layers blend against whatever is behind them in the sky,
	   so every frame the tower moved forced the moon to re-composite. Isolated,
	   the photo blends against the moon's own halo and nothing else. */
	isolation: isolate;
	left: 68%;
	top: 12%;
	width: clamp(150px, 15vw, 240px);
	aspect-ratio: 1;
	border-radius: 50%;
	background: radial-gradient(
		circle at 40% 38%,
		rgba(238, 230, 250, 0.55),
		rgba(180, 160, 210, 0.3) 58%,
		transparent 72%
	);
	filter: drop-shadow(0 0 48px rgba(206, 188, 250, 0.28));
}
.bg-set.cleradin .moon::before,
.bg-set.cleradin .moon::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: 50%;
	/* the feather sits right at the rim, so the lunar disc reaches the edge of
	   the circle instead of floating inside it */
	-webkit-mask-image: radial-gradient(circle at 50% 50%, #000 93%, transparent 100%);
	mask-image: radial-gradient(circle at 50% 50%, #000 93%, transparent 100%);
}
.bg-set.cleradin .moon::before {
	/* The source photo frames its disc with black sky: the lunar disc measures
	   516 of 560px, i.e. 92% of the file. Scaling the image to 109% therefore
	   lands the disc itself at ~100% of this circle — the moon fills the sphere
	   rather than sitting inside the photograph's framing. */
	background: url("/img/moon.webp") center / 109% 109% no-repeat;
	mix-blend-mode: screen;
	opacity: 0.95;
	filter: brightness(0.95) contrast(1.08);
}
/* The colour pass. `screen` above puts the grey surface in the sky; this layer
   puts it in THIS sky — `color` takes the theme's violet hue and saturation
   and leaves every crater's own brightness alone, so the moon is tinted rather
   than painted over. */
.bg-set.cleradin .moon::after {
	background: radial-gradient(
		circle at 42% 38%,
		rgba(222, 206, 255, 0.85),
		rgba(150, 122, 208, 0.8) 62%,
		rgba(120, 82, 165, 0.8)
	);
	mix-blend-mode: color;
	opacity: 0.75;
}

/* Stars: three sheets of them, each a background-image of tiny dots. They do
   two things at once and deliberately not in step — every sheet GLIMMERS on
   its own opacity cycle and DRIFTS on its own long, slow translation, so the
   sky is never quite the same twice and never obviously looping. The far sheet
   is dimmest and slowest, which is the only depth cue a dot can carry. */
.bg-set.cleradin .glimmer {
	position: absolute;
	inset: -6% -6% 30%;
	background-repeat: repeat;
	/* the sheets are big; keep them on their own composited layer and never
	   filter them, so glimmer+drift stay pure compositor work */
	will-change: opacity, transform;
	contain: paint;
}
.bg-set.cleradin .g1 {
	--gl: 0.9;
	opacity: 0.9;
	background-image:
		radial-gradient(circle 1.4px at 12% 18%, rgba(255, 253, 245, 0.95) 60%, transparent 61%),
		radial-gradient(circle 1px at 34% 9%, rgba(226, 214, 255, 0.9) 60%, transparent 61%),
		radial-gradient(circle 1.6px at 58% 24%, rgba(255, 255, 255, 0.9) 60%, transparent 61%),
		radial-gradient(circle 1.1px at 77% 13%, rgba(240, 226, 255, 0.85) 60%, transparent 61%),
		radial-gradient(circle 1.3px at 90% 32%, rgba(255, 255, 255, 0.8) 60%, transparent 61%),
		radial-gradient(circle 1px at 22% 42%, rgba(226, 214, 255, 0.8) 60%, transparent 61%);
	background-size: 620px 520px;
}
.bg-set.cleradin .g2 {
	--gl: 0.6;
	opacity: 0.6;
	background-image:
		radial-gradient(circle 2px at 18% 30%, rgba(255, 255, 255, 0.8) 25%, transparent 75%),
		radial-gradient(circle 1.4px at 46% 12%, rgba(200, 186, 250, 0.8) 25%, transparent 75%),
		radial-gradient(circle 1.8px at 70% 38%, rgba(255, 255, 255, 0.7) 25%, transparent 75%),
		radial-gradient(circle 1.4px at 88% 22%, rgba(226, 214, 255, 0.7) 25%, transparent 75%);
	background-size: 460px 380px;
}
.bg-set.cleradin .g3 {
	--gl: 0.4;
	opacity: 0.4;
	background-image:
		radial-gradient(circle 2.6px at 8% 44%, rgba(255, 255, 255, 0.6) 12%, transparent 82%),
		radial-gradient(circle 2.2px at 40% 26%, rgba(214, 198, 252, 0.6) 12%, transparent 82%),
		radial-gradient(circle 2.8px at 66% 8%, rgba(255, 255, 255, 0.5) 12%, transparent 82%),
		radial-gradient(circle 2px at 94% 46%, rgba(190, 200, 255, 0.55) 12%, transparent 82%);
	background-size: 780px 640px;
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.cleradin .glimmer {
		animation:
			star-glimmer 6s ease-in-out infinite,
			star-drift 220s linear infinite;
	}
	.bg-set.cleradin .g2 {
		animation-duration: 9s, 330s;
		animation-delay:
			-3.5s,
			-60s;
	}
	.bg-set.cleradin .g3 {
		animation-duration: 13s, 480s;
		animation-delay:
			-7s,
			-140s;
	}
}
@keyframes star-glimmer {
	0%,
	100% {
		opacity: var(--gl, 0.9);
	}
	38% {
		opacity: calc(var(--gl, 0.9) * 0.35);
	}
	64% {
		opacity: var(--gl, 0.9);
	}
}
@keyframes star-drift {
	from {
		transform: translate3d(0, 0, 0);
	}
	to {
		transform: translate3d(-140px, 26px, 0);
	}
}

/* The moonlight itself, pooled BEHIND the tower — the tower is a shape cut out
   of it, which is what makes the stone read as backlit rather than lit from the
   front. It sits low and left, on the tower's own side of the sky, and it
   breathes: a long, shallow pulse, nowhere near a flash. It does not parallax,
   because light does not have a distance. */
.bg-set.cleradin .moonglow {
	position: absolute;
	left: -10%;
	top: -12%;
	width: 78%;
	height: 96%;
	/* The softness is IN the gradient, not in a filter. `filter: blur()` on a
	   layer this size re-rasterises whenever it changes, and this one changes
	   continuously — which is exactly the work that made the tower's scroll
	   stutter. Wide, low-stop gradients look the same and cost nothing. */
	background:
		radial-gradient(
			closest-side at 46% 32%,
			rgba(198, 178, 246, 0.3),
			rgba(170, 140, 224, 0.16) 38%,
			rgba(150, 120, 210, 0.07) 62%,
			transparent 82%
		),
		radial-gradient(closest-side at 40% 62%, rgba(139, 58, 78, 0.14), rgba(139, 58, 78, 0.05) 46%, transparent 76%);
	will-change: opacity;
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.cleradin .moonglow {
		animation: moon-breath 11s ease-in-out infinite;
	}
}
/* opacity only: a scaling glow repaints, a fading one composites */
@keyframes moon-breath {
	0%,
	100% {
		opacity: 0.7;
	}
	50% {
		opacity: 1;
	}
}

/* ---- the tower itself (public/js/components/cleradin-tower.js) ----------
   The drawing is the centrepiece, so it is centred in a flex strip rather than
   positioned by a transform — the scroll parallax owns `transform` on this
   element and the two must not fight over it. Every colour the SVG uses is a
   custom property, so the whole tower re-skins with the theme. */
.bg-set.cleradin .tower {
	position: absolute;
	/* Deliberately enormous and hard left: you are standing at the foot of it,
	   not looking at a picture of it. Only the roof is in frame at the top of a
	   page — the rest is below the fold, and scrolling walks down the stone.
	   The height is what the parallax rate is derived from (theme.js), so
	   changing this number changes how fast the tower passes, and nothing else. */
	top: 0;
	left: -14%;
	height: 420vh;
	display: flex;
	justify-content: flex-start;
	will-change: transform;
	/* Night values. The tower is scenery, not an illustration pinned to the
	   page: the stone sits only a few steps above the background so it reads
	   as a shape in the dark, and the moonlit edge does the drawing. */
	/* Backlit night values: the tower is very nearly a silhouette. Almost all
	   of the drawing is carried by the lit left edge, where the glow behind it
	   catches the stone, and by the two warm windows. */
	--cl-stone: #241d36;
	--cl-stone-lit: #46395f;
	--cl-stone-dark: #171128;
	--cl-shade: #0a0612;
	--cl-lit: #5b4b7c;
	--cl-line: rgba(6, 3, 12, 0.7);
	--cl-roof: #113a45;
	--cl-roof-lit: #1c5b64;
	--cl-roof-dark: #0a2833;
	--cl-roof-line: rgba(3, 14, 20, 0.7);
	--cl-wood: #33200f;
	--cl-glass: #0d0818;
	--cl-glow: #caa057;
	--cl-moss: #33482f;
	opacity: 0.95;
}
.bg-set.cleradin .cl-tower-svg {
	height: 100%;
	width: auto;
	max-width: none;
	/* NO filter here. A drop-shadow on a drawing this tall is recomputed as it
	   travels, which is the one thing the scroll cannot afford; the glow pooled
	   behind the tower already does the separating. */
}
/* masonry: hairlines, not outlines — enough to read as blocks up close and to
   vanish into stone at page size */
.bg-set.cleradin .cl-joint {
	fill: none;
	stroke: var(--cl-line);
	stroke-width: 0.9;
	opacity: 0.34;
}
.bg-set.cleradin .cl-joint-v {
	opacity: 0.22;
}
.bg-set.cleradin .cl-petal {
	fill: var(--cl-stone-lit);
	opacity: 0.75;
}
.bg-set.cleradin .cl-frame {
	fill: var(--cl-stone-dark);
	stroke: var(--cl-line);
	stroke-width: 1.2;
}
.bg-set.cleradin .cl-glass {
	fill: var(--cl-glass);
	stroke: var(--cl-line);
	stroke-width: 0.8;
}
/* one window is lit: somebody is still up there */
.bg-set.cleradin .cl-glass-lit {
	fill: var(--cl-glow);
	opacity: 0.85;
	filter: drop-shadow(0 0 10px var(--cl-glow));
}
.bg-set.cleradin .cl-wood {
	fill: var(--cl-wood);
	stroke: var(--cl-line);
	stroke-width: 1.3;
}
.bg-set.cleradin .cl-iron {
	stroke: rgba(30, 20, 14, 0.65);
	stroke-width: 2.4;
	stroke-linecap: round;
}
.bg-set.cleradin .cl-iron-dot {
	fill: rgba(30, 20, 14, 0.7);
}
.bg-set.cleradin .cl-step {
	fill: none;
	stroke: var(--cl-stone-dark);
	stroke-width: 5;
	stroke-linecap: round;
	opacity: 0.85;
}
.bg-set.cleradin .cl-moss {
	fill: var(--cl-moss);
	opacity: 0.3;
}
.bg-set.cleradin .cl-corbel {
	fill: var(--cl-stone);
	stroke: var(--cl-line);
	stroke-width: 1.1;
}
.bg-set.cleradin .cl-rail .cl-arch {
	fill: var(--cl-glass);
	opacity: 0.55;
	stroke: var(--cl-line);
	stroke-width: 0.9;
}
.bg-set.cleradin .cl-caprail {
	fill: none;
	stroke: var(--cl-stone-lit);
	stroke-width: 4;
	stroke-linecap: round;
	opacity: 0.9;
}
.bg-set.cleradin .cl-shingle {
	fill: none;
	stroke: var(--cl-roof-line);
	stroke-width: 1;
	opacity: 0.4;
}
.bg-set.cleradin .cl-eave {
	fill: none;
	stroke: var(--cl-roof-dark);
	stroke-width: 5;
	stroke-linecap: round;
}
.bg-set.cleradin .cl-mast {
	stroke: var(--cl-stone-lit);
	stroke-width: 3;
	stroke-linecap: round;
}
.bg-set.cleradin .cl-finial {
	fill: var(--cl-glow);
	opacity: 0.9;
}
.bg-set.cleradin .cl-pennant {
	fill: var(--cl-roof-lit);
	stroke: var(--cl-roof-line);
	stroke-width: 1;
}
/* The pennant is the only moving part left INSIDE the drawing, and it is a
   transform on a handful of points. The lit window used to breathe too, but
   repainting anything inside a 420vh SVG while that SVG is travelling is what
   a smooth scroll cannot afford — the window is simply lit now. */
@media (prefers-reduced-motion: no-preference) {
	.bg-set.cleradin .cl-flag {
		transform-box: fill-box;
		transform-origin: left center;
		animation: pennant-sway 7s ease-in-out infinite;
	}
}
@keyframes pennant-sway {
	0%,
	100% {
		transform: rotate(-1.5deg) scaleX(1);
	}
	50% {
		transform: rotate(2deg) scaleX(0.94);
	}
}

/* ---- Russian Bunker: cold concrete, amber alarm, Cyrillic stencils ---- */
[data-theme="bunker"] {
	--bg: #14161a;
	--bg-2: #1e2228;
	--panel: rgba(34, 39, 46, 0.66);
	--panel-2: rgba(42, 48, 56, 0.7);
	--story-bg: rgba(26, 30, 36, 0.72);
	--ink: #e6e9ee;
	--muted: #90999f;
	--line: rgba(211, 63, 63, 0.24);
	--accent: #d33f3f;
	--accent-2: #ffb000;
	--good: #7fbf6b;
	--glow: rgba(211, 63, 63, 0.3);
	--grad-1: linear-gradient(120deg, #d33f3f, #8e2f2f 55%, #ffb000);
	--grad-hero: radial-gradient(120% 130% at 50% 0%, rgba(211, 63, 63, 0.12), transparent 70%);
	--panel-brd: rgba(144, 153, 159, 0.26);
	--panel-glow: 0 18px 44px rgba(2, 3, 4, 0.6), inset 0 1px 0 rgba(230, 235, 240, 0.05);
	--btn-ink: #14161a;
	--chip: rgba(211, 63, 63, 0.12);
	--chip-brd: rgba(211, 63, 63, 0.4);
	--scroll-thumb: rgba(144, 153, 159, 0.35);
	--font-display: "Orbitron", sans-serif;
	--font-body: "Space Grotesk", sans-serif;
	--font-story: "Space Grotesk", sans-serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="bunker"] .bg-set.bunker {
	opacity: 1;
}
.bg-set.bunker .concrete {
	position: absolute;
	inset: 0;
	background:
		repeating-linear-gradient(90deg, transparent 0 240px, rgba(230, 235, 240, 0.04) 240px 242px),
		repeating-linear-gradient(0deg, transparent 0 180px, rgba(230, 235, 240, 0.04) 180px 182px);
}
.bg-set.bunker .alarm {
	position: absolute;
	top: 4%;
	right: 6%;
	width: 30vw;
	height: 30vw;
	background: radial-gradient(circle, rgba(255, 176, 0, 0.14), transparent 60%);
}
.bg-set.bunker .stencil {
	position: absolute;
	inset: 0;
	background: repeating-linear-gradient(-45deg, transparent 0 60px, rgba(211, 63, 63, 0.05) 60px 78px);
	-webkit-mask: linear-gradient(transparent 70%, #000);
	mask: linear-gradient(transparent 70%, #000);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.bunker .alarm {
		animation: bunker-alarm 3.2s ease-in-out infinite;
	}
}
@keyframes bunker-alarm {
	0%,
	100% {
		opacity: 0.4;
	}
	50% {
		opacity: 1;
	}
}

/* ---- I Miss the Clouds: layered clouds drifting past one another. The pale,
   slow, blurred formations sit BEHIND the darker, faster, sharper ones, which
   is the whole illusion — depth comes from the order, not from a z-index war.
   theme.js builds the clouds and (with GSAP) sends each layer across on its
   own sine path; without GSAP the same elements drift on the CSS keyframe
   below, and under reduced motion they hang still like weather in a painting. */
[data-theme="clouds"] {
	--bg: #0d1020;
	--bg-2: #191f3a;
	--panel: rgba(30, 38, 68, 0.6);
	--panel-2: rgba(38, 48, 82, 0.66);
	--story-bg: rgba(22, 28, 52, 0.7);
	--ink: #e7e9f8;
	--muted: #9aa3c8;
	--line: rgba(160, 175, 235, 0.24);
	--accent: #7c8cf0;
	--accent-2: #e0a3c8;
	--good: #78d3b4;
	--glow: rgba(124, 140, 240, 0.28);
	--grad-1: linear-gradient(120deg, #7c8cf0, #a58bd8 55%, #e0a3c8);
	--grad-hero: radial-gradient(120% 130% at 50% 0%, rgba(124, 140, 240, 0.16), transparent 70%);
	--panel-brd: rgba(160, 175, 235, 0.26);
	--panel-glow:
		0 18px 44px rgba(4, 6, 16, 0.55), 0 0 30px rgba(124, 140, 240, 0.08), inset 0 1px 0 rgba(230, 235, 255, 0.06);
	--btn-ink: #0b0e1c;
	--chip: rgba(124, 140, 240, 0.14);
	--chip-brd: rgba(160, 175, 235, 0.38);
	--scroll-thumb: rgba(124, 140, 240, 0.35);
	--font-display: "Fraunces", serif;
	--font-body: "Plus Jakarta Sans", sans-serif;
	--font-story: "Newsreader", serif;
	--font-mono: "Inconsolata", ui-monospace, monospace;
}
[data-theme="clouds"] .bg-set.clouds {
	opacity: 1;
}

/* ---- Site font: the theme menu's Font row (public/js/fonts.js SITE_FONTS) ----
   A per-browser override of the theme's body, story AND display (heading)
   faces; only mono stays the theme's, so a chosen face dresses the whole page. `data-font` on <html> is set by
   each page's head script before first paint (like data-theme) and validated
   by initTheme(). html[attr] outranks every [data-theme] token block, so these
   win wherever they sit. test/fonts.test.mjs pins each block to the registry. */
html[data-font="fraunces"] {
	--font-body: "Fraunces", serif;
	--font-story: "Fraunces", serif;
	--font-display: "Fraunces", serif;
}
html[data-font="newsreader"] {
	--font-body: "Newsreader", serif;
	--font-story: "Newsreader", serif;
	--font-display: "Newsreader", serif;
}
html[data-font="jakarta"] {
	--font-body: "Plus Jakarta Sans", sans-serif;
	--font-story: "Plus Jakarta Sans", sans-serif;
	--font-display: "Plus Jakarta Sans", sans-serif;
}
html[data-font="grotesk"] {
	--font-body: "Space Grotesk", sans-serif;
	--font-story: "Space Grotesk", sans-serif;
	--font-display: "Space Grotesk", sans-serif;
}
html[data-font="orbitron"] {
	--font-body: "Orbitron", sans-serif;
	--font-story: "Orbitron", sans-serif;
	--font-display: "Orbitron", sans-serif;
}
html[data-font="inconsolata"] {
	--font-body: "Inconsolata", ui-monospace, monospace;
	--font-story: "Inconsolata", ui-monospace, monospace;
	--font-display: "Inconsolata", ui-monospace, monospace;
}
html[data-font="tiny5"] {
	--font-body: "Tiny5", system-ui, sans-serif;
	--font-story: "Tiny5", system-ui, sans-serif;
	--font-display: "Tiny5", system-ui, sans-serif;
}
html[data-font="jersey15"] {
	--font-body: "Jersey 15", system-ui, sans-serif;
	--font-story: "Jersey 15", system-ui, sans-serif;
	--font-display: "Jersey 15", system-ui, sans-serif;
}
html[data-font="georgia"] {
	--font-body: Georgia, serif;
	--font-story: Georgia, serif;
	--font-display: Georgia, serif;
}
html[data-font="palatino"] {
	--font-body: "Palatino Linotype", Palatino, "Book Antiqua", serif;
	--font-story: "Palatino Linotype", Palatino, "Book Antiqua", serif;
	--font-display: "Palatino Linotype", Palatino, "Book Antiqua", serif;
}
html[data-font="times"] {
	--font-body: "Times New Roman", Times, serif;
	--font-story: "Times New Roman", Times, serif;
	--font-display: "Times New Roman", Times, serif;
}
html[data-font="garamond"] {
	--font-body: Garamond, "EB Garamond", serif;
	--font-story: Garamond, "EB Garamond", serif;
	--font-display: Garamond, "EB Garamond", serif;
}
html[data-font="verdana"] {
	--font-body: Verdana, Geneva, sans-serif;
	--font-story: Verdana, Geneva, sans-serif;
	--font-display: Verdana, Geneva, sans-serif;
}
html[data-font="helvetica"] {
	--font-body: Helvetica, Arial, sans-serif;
	--font-story: Helvetica, Arial, sans-serif;
	--font-display: Helvetica, Arial, sans-serif;
}
html[data-font="trebuchet"] {
	--font-body: "Trebuchet MS", sans-serif;
	--font-story: "Trebuchet MS", sans-serif;
	--font-display: "Trebuchet MS", sans-serif;
}
html[data-font="gillsans"] {
	--font-body: "Gill Sans", "Gill Sans MT", Calibri, sans-serif;
	--font-story: "Gill Sans", "Gill Sans MT", Calibri, sans-serif;
	--font-display: "Gill Sans", "Gill Sans MT", Calibri, sans-serif;
}
html[data-font="courier"] {
	--font-body: "Courier New", Courier, monospace;
	--font-story: "Courier New", Courier, monospace;
	--font-display: "Courier New", Courier, monospace;
}
html[data-font="system"] {
	--font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
	--font-story: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
	--font-display: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
.bg-set.clouds .skyglow {
	position: absolute;
	inset: 0;
	background:
		radial-gradient(120% 80% at 50% 108%, rgba(224, 163, 200, 0.22), transparent 60%),
		radial-gradient(90% 70% at 20% -10%, rgba(124, 140, 240, 0.22), transparent 65%);
}
.bg-set.clouds .cloud-band {
	position: absolute;
	inset: 0;
	overflow: hidden;
}
.bg-set.clouds .cloud {
	position: absolute;
	left: 0;
	top: 0;
	width: var(--cw, 380px);
	height: var(--ch, 120px);
	opacity: var(--op, 0.5);
	filter: blur(var(--bl, 6px));
	will-change: transform;
	/* Seven overlapping lobes, all of them rounder and taller than the last
	   pass's four, with a flat-ish base — that is what makes a cloud poofy
	   rather than lumpy. The blur ties them into one body, so it reads as a
	   formation and not as circles travelling in convoy. */
	background-image:
		radial-gradient(circle at 17% 66%, var(--cc) 0 20%, transparent 21%),
		radial-gradient(circle at 33% 44%, var(--cc) 0 24%, transparent 25%),
		radial-gradient(circle at 50% 34%, var(--cc) 0 27%, transparent 28%),
		radial-gradient(circle at 66% 46%, var(--cc) 0 23%, transparent 24%),
		radial-gradient(circle at 83% 68%, var(--cc) 0 19%, transparent 20%),
		radial-gradient(ellipse 40% 34% at 40% 76%, var(--cc) 0 99%, transparent 100%),
		radial-gradient(ellipse 38% 32% at 64% 76%, var(--cc) 0 99%, transparent 100%);
}
/* Stars behind the weather: three sheets of them, each blurred a little more
   than the last and twinkling out of step, so the sky has depth even where no
   cloud is passing. They sit before the bands in the DOM, so every cloud drifts
   in front of them. */
.bg-set.clouds .sparks {
	position: absolute;
	inset: 0;
	background-repeat: no-repeat;
}
.bg-set.clouds .s1 {
	--tw-hi: 0.9;
	filter: blur(0.6px);
	opacity: 0.9;
	background-image:
		radial-gradient(circle 1.6px at 12% 18%, rgba(255, 255, 255, 0.95) 60%, transparent 61%),
		radial-gradient(circle 1.2px at 31% 9%, rgba(226, 232, 255, 0.9) 60%, transparent 61%),
		radial-gradient(circle 1.8px at 58% 22%, rgba(255, 255, 255, 0.9) 60%, transparent 61%),
		radial-gradient(circle 1.3px at 77% 12%, rgba(240, 226, 255, 0.9) 60%, transparent 61%),
		radial-gradient(circle 1.5px at 90% 30%, rgba(255, 255, 255, 0.85) 60%, transparent 61%);
}
.bg-set.clouds .s2 {
	--tw-hi: 0.7;
	filter: blur(1.4px);
	opacity: 0.7;
	background-image:
		radial-gradient(circle 2.2px at 21% 38%, rgba(224, 163, 200, 0.85) 60%, transparent 61%),
		radial-gradient(circle 1.8px at 44% 15%, rgba(255, 255, 255, 0.8) 60%, transparent 61%),
		radial-gradient(circle 2.4px at 68% 41%, rgba(255, 255, 255, 0.75) 60%, transparent 61%),
		radial-gradient(circle 1.9px at 86% 20%, rgba(180, 196, 255, 0.8) 60%, transparent 61%);
}
.bg-set.clouds .s3 {
	--tw-hi: 0.5;
	filter: blur(2.6px);
	opacity: 0.5;
	background-image:
		radial-gradient(circle 3.4px at 8% 46%, rgba(255, 255, 255, 0.7) 60%, transparent 61%),
		radial-gradient(circle 3px at 37% 28%, rgba(224, 163, 200, 0.7) 60%, transparent 61%),
		radial-gradient(circle 3.6px at 62% 8%, rgba(255, 255, 255, 0.6) 60%, transparent 61%),
		radial-gradient(circle 3px at 95% 44%, rgba(180, 196, 255, 0.65) 60%, transparent 61%);
}
@media (prefers-reduced-motion: no-preference) {
	.bg-set.clouds .sparks {
		animation: spark-twinkle 5.5s ease-in-out infinite;
	}
	.bg-set.clouds .s2 {
		animation-duration: 8s;
		animation-delay: -2.4s;
	}
	.bg-set.clouds .s3 {
		animation-duration: 11s;
		animation-delay: -5s;
	}
}
@keyframes spark-twinkle {
	0%,
	100% {
		opacity: var(--tw-hi, 0.9);
	}
	42% {
		opacity: calc(var(--tw-hi, 0.9) * 0.28);
	}
	70% {
		opacity: var(--tw-hi, 0.9);
	}
}
/* the no-GSAP fallback: straight travel, no wave — theme.js adds .gsap to a
   band it is driving itself, and this rule steps aside for it */
@media (prefers-reduced-motion: no-preference) {
	.bg-set.clouds .cloud-band:not(.gsap) .cloud {
		animation: cloud-drift var(--dur, 90s) linear infinite;
		animation-delay: var(--delay, 0s);
	}
}
@keyframes cloud-drift {
	from {
		transform: translateX(-40vw);
	}
	to {
		transform: translateX(110vw);
	}
}

/* ---- swatches for the new pack ---- */
.sw-hawkinslab {
	background: linear-gradient(120deg, #ff2e2e, #d8c23a);
}
.sw-castlebyers {
	background: linear-gradient(120deg, #ffb84d, #6f9a5a);
}
.sw-vecna {
	background: linear-gradient(120deg, #e02f2f, #c98f4e);
}
.sw-void {
	background: linear-gradient(to top, #ff4040, #160a0e 55%, #060407);
}
.sw-video {
	background: linear-gradient(120deg, #ff4fd8, #29d3c7);
}
.sw-hellfire {
	background: linear-gradient(120deg, #e02f2f, #b7b7c9);
}
.sw-rink {
	background: linear-gradient(90deg, #ff3355, #ffe040 35%, #4fe35f 60%, #2fd6ff 80%, #b45cff);
}
.sw-cleradin {
	background: linear-gradient(120deg, #8b3a4e, #6d4b96);
}
.sw-bunker {
	background: linear-gradient(120deg, #d33f3f, #ffb000);
}
.sw-clouds {
	background: linear-gradient(120deg, #7c8cf0, #e0a3c8);
}

/* ==========================================================================
   Theme peek: the sticky 👁 button (sitting just above the ko-fi "Support me"
   bubble, bottom-left) toggles body.ui-peek, which hides every UI layer so
   the theme background shows through. The game chat dock stays visible.
   ========================================================================== */
/* ---------- The foot bar ----------
   Theme peek and the tip jar are site furniture, not anybody's task, so they
   get a thin strip along the bottom instead of two floating chips parked over
   the page (where they covered modals and the corner of every layout). One
   line of small text; --footbar-h is what everything else pinned to the bottom
   clears itself by. */
.foot-bar {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	height: var(--footbar-h);
	z-index: 100;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 4px;
	background: var(--panel);
	border-top: 1px solid var(--panel-brd);
	backdrop-filter: blur(10px);
	-webkit-backdrop-filter: blur(10px);
}
.foot-btn {
	min-width: 0;
	width: auto;
	flex: 0 0 auto;
	padding: 4px 10px;
	border: 0;
	border-radius: 8px;
	background: none;
	box-shadow: none;
	color: var(--muted);
	font: 600 0.72rem/1 var(--font-body);
	letter-spacing: 0.02em;
	cursor: pointer;
}
.foot-btn:hover {
	color: var(--ink);
	background: var(--chip);
}
.foot-dot {
	color: var(--line);
	font-size: 0.72rem;
}
/* Peeking hides everything but the way back out of it. */
body.ui-peek .foot-bar {
	background: none;
	border-top: 0;
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
}
body.ui-peek #kofiBtn,
body.ui-peek .foot-dot {
	display: none;
}

/* Ko-fi's own embeddable panel, in our modal. The floating widget script is
   gone — it drew an iframe button at a size and z-index we didn't control.
   This iframe we do control: it needs ~300px of width to lay out, so the card
   is sized for it rather than squeezed. */
.kofi-card {
	max-width: 380px;
	padding: 14px;
}
.kofi-head {
	display: flex;
	align-items: center;
	gap: 8px;
}
.kofi-head h3 {
	margin: 0;
	flex: 1 1 auto;
	text-align: left;
}
.kofi-x {
	flex: 0 0 auto;
	width: 34px;
	min-width: 0;
	height: 34px;
	padding: 0;
	line-height: 1;
}
#kofiFrame {
	width: 100%;
	/* Ko-fi's panel is ~560 tall before it scrolls itself; give it as much of
	   that as the window can spare so the tip button isn't below the fold. */
	height: min(560px, 72vh);
	height: min(560px, 72svh);
	margin-top: 10px;
	border: 0;
	border-radius: var(--r-md);
	background: #fff; /* ko-fi's panel is a light page; don't flash the theme through it */
}
.kofi-out {
	display: inline-block;
	margin-top: 10px;
	font-size: 0.78rem;
}

body.ui-peek .wrap,
body.ui-peek .topbar,
body.ui-peek .hamburger,
body.ui-peek .nav-drawer,
body.ui-peek .nav-scrim,
body.ui-peek .toasts,
body.ui-peek .join-reqs,
body.ui-peek .turn-toast {
	visibility: hidden !important;
}

/* ---------- Solo writes (/writes listing + /write editor) ---------- */
/* The shelf's two halves — what you write, and what you were asked to read.
   The heading names the relationship; the grid under it behaves as before. */
.doc-group + .doc-group {
	margin-top: 28px;
	padding-top: 22px;
	border-top: 1px solid var(--line);
}
.doc-group-head {
	display: flex;
	align-items: center;
	gap: 10px;
	margin: 0;
	font-family: var(--font-display);
	font-size: 1.05rem;
}
.doc-group-count {
	font-family: var(--font-mono);
	font-size: 0.72rem;
	color: var(--muted);
	background: var(--chip);
	border: 1px solid var(--chip-brd);
	border-radius: var(--r-lg);
	padding: 2px 9px;
}
.doc-group-blurb {
	margin: 4px 0 0;
	text-align: left;
	font-size: 0.84rem;
}
.doc-group .doc-grid {
	margin-top: 12px;
}
.doc-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
	gap: 14px;
	margin-top: 16px;
}
.doc-card {
	background: var(--panel);
	position: relative; /* anchors the .doc-date corner chip */
	border-radius: var(--r-lg);
	padding: 16px;
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(16px);
	text-align: left;
}
.doc-card-title {
	margin: 0 0 6px;
	font-family: var(--font-display);
	font-size: 1.15rem;
	color: var(--ink);
}
.doc-card-meta {
	margin: 0 0 10px;
	color: var(--muted);
	font-size: 0.86rem;
}
.doc-card-tags {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	margin: 0 0 10px;
}
.doc-pill {
	font-size: 0.76rem;
	padding: 3px 9px;
	border-radius: 999px;
	background: var(--chip);
	border: 1px solid var(--line);
	color: var(--muted);
}
.doc-pill.on {
	color: var(--ink);
	border-color: var(--accent-2);
}
/* Open and Delete are one pair of matched buttons on the left of the card,
   not a stretched row: they're the same weight of action on the same object,
   so they get the same width and sit together instead of pushing apart. */
.doc-card-actions {
	display: flex;
	justify-content: flex-start;
	gap: 8px;
	margin-top: 12px;
}
.doc-card-actions > * {
	flex: 0 0 auto;
	width: 96px;
	min-width: 0; /* the global button min-width would break the match */
	min-height: 38px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 8px 10px;
	border-radius: var(--r-md);
	text-decoration: none;
	font-size: 0.85rem;
}

/* editor page */
/* ---- The write page is full-bleed ----
   A drafting surface is the whole app while you're in it, so it gets the whole
   window rather than a centred 1180px card floating on the theme: the sticky
   head/toolbar slab runs edge to edge with square corners (a full-width bar
   with rounded ends reads as a card that overflowed), and only the prose row
   keeps a gutter so the text never touches the bezel. */
.write-inner {
	max-width: none;
	margin: 0;
	text-align: left;
}
body.write-page {
	padding-top: 0;
}
.write-page .doc-shell {
	top: 0;
	margin: 0 0 14px;
	border-radius: 0;
	border-left: 0;
	border-right: 0;
	border-top: 0;
}
.write-page .doc-main {
	padding: 0 14px;
}
/* Square everything the slab touches — mixed radii inside a full-bleed page
   look like leftovers rather than a decision. */
.write-page .doc-editor,
.write-page .doc-source,
.write-page .doc-side-card,
.write-page .restore-bar {
	border-radius: 0;
}
.doc-title-input {
	flex: 1;
	min-width: 140px;
	background: transparent;
	border: 1px solid transparent;
	border-radius: var(--r-md);
	padding: 8px 12px;
	color: var(--ink);
	font-family: var(--font-display);
	font-size: 1.4rem;
}
.doc-title-input:hover:not(:disabled) {
	border-color: var(--line);
}
.doc-title-input:focus {
	outline: none;
	border-color: var(--accent);
}
.presence-row {
	display: inline-flex;
	align-items: center;
	gap: 4px;
}
.presence-row:not(:empty)::before {
	content: "·";
	color: var(--muted);
	margin-right: 6px;
}
.presence-av {
	display: inline-flex;
	border-radius: 50%;
	box-shadow: 0 0 0 2px var(--accent-2);
}
/* ---------- Solo-write: sticky head + toolbar ---------- */
/* One slab that stays put while a long chapter scrolls under it. */
.doc-shell {
	position: sticky;
	top: 10px;
	z-index: 45;
	margin: 0 0 14px;
	border: 1px solid var(--panel-brd);
	border-radius: var(--r-lg);
	background: color-mix(in srgb, var(--panel) 88%, transparent);
	backdrop-filter: blur(16px);
	-webkit-backdrop-filter: blur(16px);
	box-shadow: var(--panel-glow);
	overflow: visible;
}
.doc-head {
	display: flex;
	align-items: center;
	gap: 14px;
	padding: 10px 14px;
	border-bottom: 1px solid var(--line);
}
.doc-back {
	flex: 0 0 auto;
	color: var(--muted);
	text-decoration: none;
	font-size: 0.92rem;
	padding: 8px 10px;
	border-radius: var(--r-md);
	white-space: nowrap;
}
.doc-back:hover {
	color: var(--ink);
	background: var(--chip);
}
.doc-head-main {
	flex: 1 1 auto;
	min-width: 0;
}
.doc-head-main .doc-title-input {
	display: block;
	width: 100%;
	font-size: 1.5rem;
	padding: 2px 8px;
	margin: 0 0 1px -8px;
}
/* the status line lives UNDER the title, as a caption, not as its own bar */
.doc-head-meta {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
	font-size: 0.85rem;
	color: var(--muted);
	padding-left: 0;
}
.doc-head-right {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	gap: 10px;
}
/* the shared chrome is fixed everywhere else; in here it's just content */
body.doc-page .topbar,
body.doc-page .hamburger {
	position: static;
	top: auto;
	right: auto;
	z-index: auto;
}
body.doc-page .topbar {
	align-items: center;
}
.doc-save {
	flex: 0 0 auto;
	min-width: 0;
	padding: 10px 22px;
	font-size: 1rem;
}

.doc-toolbar-row {
	position: relative;
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
	padding: 7px 12px;
}
/* The view preferences are their own strip beside the formatting toolbar, so
   hiding the toolbar (a beta reader has nothing to format) leaves them. */
.doc-view-prefs {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	gap: 4px;
	background: none;
	border: 0;
	box-shadow: none;
	padding: 0;
}
/* On a reader's page it is the only strip left — let it start the row rather
   than sit where the toolbar's tail used to be. */
#docToolbar.hidden + .doc-view-prefs {
	margin-right: auto;
}
.doc-toolbar-row .toolbar {
	flex: 1 1 auto;
	margin: 0;
	background: none;
	border: 0;
	box-shadow: none;
	padding: 0;
	gap: 0; /* the groups space themselves with their own hairline */
	align-items: center;
}
/* p / h1 / h2 / h3 / "" — as narrow as the other glyph selects */
.doc-toolbar-row #blockFormat {
	max-width: 64px;
	padding-right: 18px;
}
.doc-toolbar-row .stepper {
	padding: 0 2px;
	gap: 1px;
}
.doc-toolbar-row .step-input {
	width: 30px;
	font-size: 0.85rem;
}
.doc-toolbar-row .step-btn {
	padding: 2px 5px;
	font-size: 0.85rem;
}
.doc-toolbar-row .step-value,
.doc-toolbar-row .step-label {
	font-size: 0.82rem;
}
/* One continuous strip of controls. The buttons carry no border and no
   chrome of their own — they are glyphs on the toolbar slab, and only hover
   and the pressed state paint anything. Logical groups are separated by a
   hairline rule rather than by boxing each one, so the row reads as a single
   toolbar instead of seven little pills. */
.tb-group {
	display: inline-flex;
	align-items: center;
	gap: 0;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: none;
}
/* the hairline lives between groups, never before the first or after the last */
.tb-group + .tb-group {
	margin-left: 5px;
	padding-left: 5px;
	border-left: 1px solid var(--line);
}
.tb-group > button.ghost,
.tb-group > select.ghost {
	border: 0;
	background: none;
	box-shadow: none;
	padding: 5px 7px;
	min-width: 26px;
	font-size: 0.85rem;
	line-height: 1.15;
	border-radius: 8px;
}
.tb-group > button.ghost:hover,
.tb-group > select.ghost:hover,
.tb-group > button.ghost:focus-visible,
.tb-group > select.ghost:focus-visible {
	background: var(--chip);
}
/* a pressed button (bold on, list on) is the only thing that stays filled */
.tb-group > button.ghost.on {
	background: var(--chip);
	color: var(--ink);
}
.tb-group .stepper {
	border: 0;
	background: none;
	margin: 0;
	padding: 0;
}
.toolbar-right {
	display: flex;
	align-items: center;
	gap: 6px;
	margin-left: auto;
}
/* The right-hand icons match the left-hand ones: a glyph, no box. The
   rich-text/HTML/comment segmented control is the exception — it shows WHICH
   mode is on, so it keeps its track and its filled segment. */
.icon-btn {
	min-width: 0;
	padding: 6px 9px;
	font-size: 0.9rem;
	text-decoration: none;
	line-height: 1.15;
}
.toolbar-right > .icon-btn {
	border: 0;
	background: none;
	box-shadow: none;
	border-radius: 8px;
}
.toolbar-right > .icon-btn:hover,
.toolbar-right > .icon-btn:focus-visible {
	background: var(--chip);
}
.doc-toolbar-row .mode-switch .mode-btn {
	padding: 6px 11px;
	font-size: 0.82rem;
}

/* ⋯ overflow */
.more-menu {
	position: absolute;
	top: calc(100% - 4px);
	left: 12px;
	z-index: 60;
	min-width: 230px;
	padding: 6px;
	border: 1px solid var(--panel-brd);
	border-radius: var(--r-md);
	background: var(--panel);
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(14px);
	-webkit-backdrop-filter: blur(14px);
}
.more-item {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	width: 100%;
	padding: 9px 10px;
	border: 0;
	border-radius: var(--r-md);
	background: none;
	color: var(--ink);
	font: inherit;
	font-size: 0.9rem;
	text-align: left;
	cursor: pointer;
}
.more-item:hover {
	background: var(--chip);
}
.more-item.as-row {
	cursor: default;
}
.more-item.as-row:hover {
	background: none;
}
.more-item kbd {
	color: var(--muted);
	font-family: var(--font-mono);
	font-size: 0.85rem;
}
.more-sep {
	height: 1px;
	margin: 5px 6px;
	background: var(--line);
}

/* While the size box has focus the browser's selection has moved to it, so
   paint the words the resize will land on rather than showing nothing. */
.doc-editor.hold-sel ::selection {
	background: color-mix(in srgb, var(--accent) 45%, transparent);
}
/* Same job, for browsers that drop the selection outright on focus change:
   the remembered range is painted through the Custom Highlight API. */
::highlight(fs-sel) {
	background: color-mix(in srgb, var(--accent) 45%, transparent);
}

.doc-statusbar {
	display: flex;
	align-items: center;
	gap: 10px;
	flex-wrap: wrap;
	margin: 12px 0;
}
.doc-statusbar button {
	flex: 0 0 auto;
	min-width: 0;
	padding: 8px 14px;
}
.doc-spacer {
	flex: 1;
}
.doc-save-state {
	font-size: 0.85rem;
	color: var(--good);
}
.doc-save-state.unsaved {
	color: var(--accent);
}
.doc-dot {
	color: var(--muted);
}
/* Inside the sticky shell, under the toolbar: unsaved work from a previous
   session must not scroll away unnoticed. */
.restore-bar {
	display: flex;
	align-items: center;
	gap: 10px;
	flex-wrap: wrap;
	padding: 8px 12px;
	border-top: 1px solid var(--line);
	border-radius: 0 0 17px 17px;
	background: var(--chip);
	font-size: 0.9rem;
}
.restore-bar button {
	flex: 0 0 auto;
	min-width: 0;
	padding: 6px 12px;
}
/* The second column is the comments DRAWER, and its width is the writer's:
   --doc-side-w is written by write.html from the saved editor prefs. Closed,
   the column is gone entirely and the prose gets the whole page. */
.doc-main {
	display: grid;
	grid-template-columns: minmax(0, 1fr) var(--doc-side-w, 300px);
	gap: 18px;
	align-items: start;
}
.doc-main.side-closed {
	grid-template-columns: minmax(0, 1fr);
}
.doc-main.side-closed .doc-side {
	display: none;
}
/* The FIRST column is the chapter panel — a plain grid column, not a second
   drawer (the drawer owns --doc-side-w and side-closed). chap-closed drops
   the track; under 860px the panel becomes a dropdown under the head row. */
.doc-main:not(.chap-closed) {
	grid-template-columns: 220px minmax(0, 1fr) var(--doc-side-w, 300px);
}
.doc-main.side-closed:not(.chap-closed) {
	grid-template-columns: 220px minmax(0, 1fr);
}
.doc-main.chap-closed .doc-chapters {
	display: none;
}
.doc-chapters {
	position: sticky;
	top: calc(var(--doc-sticky, 0px) + 18px);
	min-width: 0;
	max-height: calc(100vh - var(--doc-sticky, 0px) - var(--footbar-h, 0px) - 36px);
	overflow-y: auto;
	padding: 6px;
	border: 1px solid var(--panel-brd);
	border-radius: var(--r-md);
	background: var(--panel);
}
.chap-head {
	padding: 2px 4px 6px;
	margin-bottom: 4px;
	border-bottom: 1px solid var(--line);
}
.chap-head h3 {
	font-size: 0.95rem;
}
.chap-list {
	display: flex;
	flex-direction: column;
	gap: 2px;
}
.chap-row {
	display: flex;
	align-items: stretch;
	gap: 2px;
	border-radius: var(--r-md);
}
.chap-row.open {
	background: var(--chip);
}
.chap-row:hover {
	background: var(--panel-2);
}
.chap-open {
	flex: 1 1 auto;
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	grid-template-areas: "n title" "n meta";
	column-gap: 8px;
	min-width: 0;
	padding: 6px 8px;
	border: 0;
	background: none;
	color: var(--ink);
	font: inherit;
	text-align: left;
	cursor: pointer;
}
.chap-open:hover {
	transform: none;
}
.chap-n {
	grid-area: n;
	align-self: start;
	min-width: 1.4em;
	font-family: var(--font-mono);
	font-size: 0.78rem;
	color: var(--muted);
	padding-top: 2px;
}
.chap-row.open .chap-n {
	color: var(--accent-2);
}
.chap-title {
	grid-area: title;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font-weight: 600;
	font-size: 0.92rem;
}
.chap-meta {
	grid-area: meta;
	display: flex;
	gap: 8px;
	font-size: 0.76rem;
	color: var(--muted);
}
.chap-notes {
	color: var(--accent-2);
}
.chap-acts {
	display: none;
	flex: 0 0 auto;
	align-items: center;
	gap: 0;
	padding-right: 4px;
}
.chap-row:hover .chap-acts,
.chap-row.open .chap-acts,
.chap-row:focus-within .chap-acts {
	display: inline-flex;
}
.chap-acts button {
	min-width: 0;
	padding: 2px 5px;
	border: 0;
	background: none;
	color: var(--muted);
	font-size: 0.82rem;
	line-height: 1;
	cursor: pointer;
}
.chap-acts button:hover:not(:disabled) {
	color: var(--ink);
	transform: none;
}
.chap-acts button:disabled {
	opacity: 0.3;
	cursor: default;
}
.chap-acts .chap-del[data-armed] {
	color: var(--danger, #e5484d);
	font-size: 0.74rem;
}
.chap-rename-input {
	grid-area: title;
	min-width: 0;
	padding: 1px 4px;
	font-size: 0.92rem;
}
.chap-foot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 6px;
	margin-top: 6px;
	padding: 6px 8px 2px;
	border-top: 1px solid var(--line);
	font-size: 0.78rem;
	color: var(--muted);
}
.chap-foot .chap-add {
	padding: 3px 9px;
	font-size: 0.8rem;
}
/* Prev / Next at the foot of the chapter */
.chap-nav {
	display: grid;
	grid-template-columns: 1fr auto 1fr;
	align-items: center;
	gap: 10px;
	margin: 18px 0 8px;
}
.chap-nav .chap-prev {
	justify-self: start;
}
.chap-nav .chap-next {
	justify-self: end;
}
.chap-nav .chap-prev,
.chap-nav .chap-next {
	min-width: 0;
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.chap-pos {
	font-size: 0.82rem;
	color: var(--muted);
	white-space: nowrap;
}
/* 860 is about COLUMNS ONLY, and is deliberately not the same number as the
   760 phone block at the end of this file. An iPad in portrait (768–834px)
   lands in the gap on purpose: it wants one column, but it keeps the sticky
   single-row toolbar, because it has the vertical room and doesn't hit the iOS
   keyboard/sticky-caret bug. Don't unify the two. */
@media (max-width: 860px) {
	.doc-main,
	.doc-main:not(.chap-closed),
	.doc-main.side-closed:not(.chap-closed) {
		grid-template-columns: 1fr;
	}
	/* the chapter panel is a dropdown under the sticky head row here — the
	   chip opens it, a pick / Escape / an outside click closes it */
	.doc-chapters {
		position: fixed;
		left: 8px;
		right: 8px;
		top: calc(var(--doc-sticky, 0px) + 6px);
		z-index: 70;
		max-height: 60vh;
		box-shadow: var(--panel-glow);
		/* a dropdown over prose is unreadable on a translucent panel — the
		   phone's chapter list stands on the solid page ground */
		background: var(--bg-2);
	}
	.doc-main.chap-closed .doc-chapters {
		display: none;
	}
}
.doc-col {
	min-width: 0;
}
/* a document, not a single turn — much taller than the game editor */
.doc-editor {
	min-height: 58vh;
	max-height: none;
	font-size: 16px; /* the ladder's default — unstyled text really is 16 */
	/* Highlighting the words is how you comment on them, and in comment mode
	   the surface isn't editable — so say out loud that the prose is
	   selectable, including the long-press callout iOS otherwise suppresses on
	   a non-editable box inside an app-like page. */
	-webkit-user-select: text;
	user-select: text;
	-webkit-touch-callout: default;
}
.doc-source {
	font-family: var(--font-mono);
	font-size: 0.92rem;
	white-space: pre-wrap;
	line-height: 1.55;
}
.toolbar.dimmed {
	opacity: 0.4;
	pointer-events: none;
}
.doc-editor ul,
.doc-editor ol,
.doc-read ul,
.doc-read ol,
.story ul,
.story ol,
.editor ul,
.editor ol,
.live ul,
.live ol {
	padding-left: 1.4em;
	margin: 0.5em 0;
}
.doc-editor blockquote,
.doc-read blockquote,
.story blockquote,
.editor blockquote,
.live blockquote {
	margin: 0.6em 0;
	padding: 0.3em 0 0.3em 1em;
	border-left: 3px solid var(--accent-2);
	color: var(--muted);
	font-style: italic;
}
.doc-editor a,
.doc-read a {
	color: var(--accent);
}
.doc-img {
	max-width: 100%;
	border-radius: var(--r-md);
}
/* read-only view beta readers comment on */
.doc-read {
	padding: 16px;
	border-radius: var(--r-md);
	background: var(--panel-2);
	border: 1px solid var(--line);
	font-family: var(--font-story);
	font-size: 1.1rem;
	line-height: 1.75;
}
/* Comment anchors: the underline IS the comment's presence in the prose.
   The span lives in the saved html, so it travels with the words. */
.cmt {
	border-bottom: 2px solid color-mix(in srgb, var(--accent-2) 70%, transparent);
	background: color-mix(in srgb, var(--accent-2) 10%, transparent);
	cursor: pointer;
	transition:
		background 0.15s,
		border-color 0.15s;
}
.cmt:hover {
	background: color-mix(in srgb, var(--accent-2) 20%, transparent);
}
/* the pair the reader is looking at, in the prose and in the rail */
.cmt.active,
.doc-comment.active {
	background: color-mix(in srgb, var(--accent) 22%, transparent);
	border-color: var(--accent);
}
.doc-comment.active {
	box-shadow: 0 0 0 2px var(--accent);
}
/* comment mode: the text stops being a writing surface and starts being a
   thing you point at */
.doc-editor.commenting {
	cursor: text;
	background: color-mix(in srgb, var(--accent-2) 4%, transparent);
}
/* icon-only pickers: the glyph says it, the tooltip names it */
.align-select {
	width: auto;
	min-width: 0;
	padding-right: 20px !important;
	text-align: center;
}

/* comments rail */
/* The column has to be as tall as the prose beside it, or its sticky child
   has nowhere to travel and simply scrolls away (grid items don't stretch
   under align-items: start). */
.doc-side {
	align-self: stretch;
}

/* The comments pane sticks directly UNDER the sticky head — --doc-sticky is
   the live height of the shell (title + toolbar + the unsaved-draft bar when
   it's showing), written by write.html, so the pane never slides beneath the
   toolbar and never has to be scrolled back to. It scrolls inside itself when
   there are more comments than viewport. */
.doc-side-card {
	position: sticky;
	top: calc(var(--doc-sticky, 120px) + 18px);
	max-height: calc(100vh - var(--doc-sticky, 120px) - 34px);
	display: flex;
	flex-direction: column;
	min-height: 0;
	text-align: left;
	padding-left: 20px; /* room for the grip, which sits on the inner edge */
}

/* ---- the drawer's own furniture ----
   A grip you drag, a ✕ that closes, and a tab that brings it back. The grip is
   a real focusable separator (arrow keys resize it) because a control that
   only answers to a drag is a control some people don't have. */
.doc-side-grip {
	position: absolute;
	left: 0;
	top: 0;
	bottom: 0;
	width: 14px;
	cursor: col-resize;
	border-radius: var(--r-lg) 0 0 var(--r-lg);
	touch-action: none;
	display: flex;
	align-items: center;
	justify-content: center;
}
.doc-side-grip::before {
	content: "";
	width: 3px;
	height: 42px;
	border-radius: 3px;
	background: var(--line);
	transition:
		background 0.15s ease,
		height 0.15s ease;
}
.doc-side-grip:hover::before,
.doc-side-grip:focus-visible::before,
body.resizing-side .doc-side-grip::before {
	background: var(--accent-2);
	height: 72px;
}
.doc-side-grip:focus-visible {
	outline: 2px solid var(--accent-2);
	outline-offset: -2px;
}
/* A drag must not select the prose it's dragging past. */
body.resizing-side {
	user-select: none;
	cursor: col-resize;
}
.doc-side-head {
	display: flex;
	align-items: center;
	gap: 8px;
}
.doc-side-head h3 {
	margin: 0;
	flex: 1 1 auto;
}
/* An icon button, so it takes an icon's room — buttons here carry a min-width
   for tap targets, which on a ✕ just makes a wide empty slab. */
.doc-side-close {
	flex: 0 0 auto;
	width: 34px;
	min-width: 0;
	/* a dismissal, not a framed button: no border, no shadow, no focus ring */
	border: 0 !important;
	box-shadow: none !important;
	background: none;
	outline: none;
	height: 34px;
	padding: 0;
	font-size: 0.85rem;
	line-height: 1;
}
/* The way back in: a tab on the right edge carrying the comment count. */
.doc-side-tab {
	position: fixed;
	right: 0;
	top: 50%;
	transform: translateY(-50%);
	z-index: 40;
	border: 1px solid var(--panel-brd);
	border-right: 0;
	background: var(--panel);
	color: var(--ink);
	border-radius: var(--r-md) 0 0 12px;
	padding: 12px 10px;
	cursor: pointer;
	box-shadow: var(--panel-glow);
	font-size: 0.9rem;
}
.doc-side-tab:hover {
	background: var(--chip);
	transform: translateY(-50%); /* the global button lift would replace the centring */
}
.doc-side-tab.hidden {
	display: none;
}
/* The note you're writing is pinned; only the comments below it scroll. */
#commentComposer {
	flex: 0 0 auto;
}
#commentComposer:not(:empty) {
	margin-bottom: 10px;
	padding-bottom: 10px;
	border-bottom: 1px solid var(--line);
}
#commentPane {
	flex: 1 1 auto;
	min-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain;
}
/* One column, so the drawer comes up from the BOTTOM instead of in from the
   side: a rail narrow enough to sit beside the prose here would be too narrow
   to read a comment in. The same saved number is now a height, dragged with
   the grip that has moved to the top edge. A sheet is the one place a nested
   scroller is right — it's pinned to the viewport, so the page behind it isn't
   competing for the gesture. */
@media (max-width: 860px) {
	.doc-side {
		position: fixed;
		left: 0;
		right: 0;
		bottom: var(--footbar-h);
		z-index: 60;
		align-self: auto;
	}
	.doc-side-card {
		position: static;
		height: var(--doc-side-w, 300px);
		max-height: 85vh;
		margin: 0;
		padding: 12px 14px 14px;
		border-radius: var(--r-lg) var(--r-lg) 0 0;
		/* the sheet lies over the story: solid, so the prose never shows
		   through the comments (the desktop rail keeps the glass panel) */
		background: var(--bg-2);
	}
	.doc-side-grip {
		left: 0;
		right: 0;
		top: 0;
		bottom: auto;
		width: auto;
		height: 18px;
		cursor: row-resize;
		border-radius: var(--r-lg) var(--r-lg) 0 0;
	}
	.doc-side-grip::before {
		width: 44px;
		height: 3px;
	}
	.doc-side-grip:hover::before,
	.doc-side-grip:focus-visible::before,
	body.resizing-side .doc-side-grip::before {
		width: 72px;
		height: 3px;
	}
	body.resizing-side {
		cursor: row-resize;
	}
	.doc-side-head {
		margin-top: 6px;
	}
	/* the sheet covers the foot of the page — give the prose somewhere to end */
	.doc-main:not(.side-closed) .doc-col {
		padding-bottom: calc(var(--doc-side-w, 300px) + 20px);
	}
	.doc-side-tab {
		top: auto;
		bottom: calc(var(--footbar-h) + 16px);
		border-radius: var(--r-md) 0 0 12px;
	}
}
.dc-group {
	margin-bottom: 14px;
}
/* Jumping to a comment must not park its words under the sticky head. */
.doc-editor span.cmt {
	scroll-margin-top: calc(var(--doc-sticky, 120px) + 30px);
	scroll-margin-bottom: 30px;
}
/* A jump you asked for should be visible when you arrive. */
@keyframes cmt-ping {
	from {
		background: var(--chip);
		box-shadow: 0 0 0 4px var(--chip);
	}
	to {
		background: transparent;
		box-shadow: 0 0 0 0 transparent;
	}
}
.doc-editor span.cmt.active {
	animation: cmt-ping 1.1s ease-out;
}
.dc-anchor {
	margin: 0 0 6px;
	font-size: 0.8rem;
	color: var(--muted);
	border-left: 2px solid var(--accent-2);
	padding-left: 8px;
}
.dc-list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: 8px;
}
.doc-comment {
	background: var(--panel-2);
	border: 1px solid var(--line);
	border-radius: var(--r-md);
	padding: 10px;
}
.doc-comment.resolved {
	opacity: 0.55;
}
.doc-comment.resolved .dc-text {
	text-decoration: line-through;
}
.dc-who {
	display: flex;
	align-items: center;
	gap: 6px;
	font-size: 0.82rem;
}
.dc-when {
	color: var(--muted);
	margin-left: auto;
	font-size: 0.75rem;
}
.dc-text {
	margin: 6px 0;
	font-size: 0.9rem;
	white-space: pre-wrap;
	word-break: break-word;
}
/* Card actions. These are BUTTONS wearing .linky, so the global button rules
   (pill background, min-width, form padding) were fighting the link styling
   and winning — hence the squashed, half-clipped lozenges. Reset them into
   plain text actions: no fill, no border, a hover tint, and Delete in the
   danger colour so the destructive one reads as destructive. */
.dc-actions {
	display: flex;
	flex-wrap: wrap; /* Accept + Reject + Delete must not widen the rail */
	align-items: center;
	gap: 4px;
	margin-top: 8px;
}
.dc-actions button {
	flex: 0 0 auto;
	min-width: 0;
	width: auto;
	padding: 4px 8px;
	border: 0;
	border-radius: 8px;
	background: none;
	box-shadow: none;
	color: var(--accent-2);
	font: inherit;
	font-size: 0.78rem;
	font-weight: 600;
	line-height: 1.2;
	text-decoration: none;
	cursor: pointer;
}
.dc-actions button:hover {
	background: var(--chip);
	text-decoration: none;
}
.dc-actions .dc-accept {
	color: var(--good);
}
.dc-actions .dc-del,
.dc-actions .dc-reject {
	color: var(--accent);
}
.dc-actions button:focus-visible {
	outline: 2px solid var(--accent-2);
	outline-offset: 1px;
}
.dc-orphan-note {
	font-size: 0.8rem;
	color: var(--muted);
	margin: 0 0 6px;
}

/* a proposed rewrite: what it says now, what it would say */
.dc-suggest {
	margin: 6px 0;
	font-size: 0.88rem;
	line-height: 1.5;
	word-break: break-word;
}
.dc-suggest s {
	color: var(--muted);
}
.dc-suggest ins {
	text-decoration: none;
	color: var(--accent);
	font-weight: 600;
}
.dc-arrow {
	color: var(--muted);
	margin: 0 2px;
}
.dc-quote {
	margin: 6px 0;
	font-size: 0.82rem;
	color: var(--muted);
	border-left: 2px solid var(--accent-2);
	padding-left: 8px;
	word-break: break-word;
}
.dc-verdict {
	margin: 4px 0 0;
	font-size: 0.78rem;
	color: var(--muted);
}
.dc-tag {
	font-size: 0.68rem;
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--muted);
	border: 1px solid var(--line);
	border-radius: 6px;
	padding: 0 4px;
}
.doc-comment.orphaned {
	border-style: dashed;
}
.doc-comment {
	cursor: pointer;
}
.dc-suggest-toggle {
	display: flex;
	align-items: center;
	gap: 6px;
	font-size: 0.76rem;
	color: var(--muted);
	margin: 8px 0 6px;
	cursor: pointer;
}
/* Room to actually write in, at a size that suits a side rail rather than
   the prose. resize:vertical so a long note can have more. */
.dc-new textarea {
	width: 100%;
	min-height: 116px;
	resize: vertical;
	border-radius: var(--r-md);
	padding: 9px 10px;
	background: var(--panel-2);
	border: 1px solid var(--accent-2);
	color: var(--ink);
	font-family: inherit;
	font-size: 0.84rem;
	line-height: 1.45;
}
.dc-new textarea::placeholder {
	font-size: 0.84rem;
}
.dc-new .row {
	margin-top: 6px;
}
.dc-new button {
	padding: 6px 12px;
	min-width: 0;
}

/* beta reader chips */
/* ---- the beta-reader modal ----
   Two sections with their own headings, so "who is reading this" and "who do
   I add" stop being one undifferentiated stack of controls. */
.share-card {
	text-align: left;
	max-width: 460px;
}
.share-card h3 {
	margin: 0 0 4px;
}
.share-blurb {
	text-align: left;
	margin: 0 0 4px;
}
.share-warn {
	text-align: left;
	margin: 10px 0 0;
	padding: 8px 10px;
	border-left: 2px solid var(--accent-2);
	background: var(--chip);
	border-radius: 0 10px 10px 0;
}
.share-sec {
	margin-top: 16px;
}
.share-h {
	margin: 0 0 8px;
	font-family: var(--font-mono);
	font-size: 0.72rem;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--muted);
}
.share-foot {
	justify-content: flex-end;
	margin-top: 18px;
}
.share-foot button {
	flex: 0 0 auto;
}
/* The "nobody yet" line belongs with the section it's under, not centred in
   the middle of a left-aligned card. */
.reader-chips .subtle {
	text-align: left;
	margin: 0;
}

/* ---- the writer picker ----
   Every writer is listed and the list is filtered as you type; friends sort to
   the top and wear a chip, because they're the only ones invitable. */
.user-picker input {
	width: 100%;
}
.pick-list {
	margin-top: 8px;
	max-height: 210px;
	overflow-y: auto;
	overscroll-behavior: contain;
	border: 1px solid var(--line);
	border-radius: var(--r-md);
	background: var(--panel-2);
}
.pick-row {
	display: flex;
	align-items: center;
	gap: 8px;
	width: 100%;
	padding: 8px 10px;
	background: none;
	border: 0;
	border-bottom: 1px solid var(--line);
	color: var(--ink);
	font-size: 0.9rem;
	text-align: left;
	cursor: pointer;
}
.pick-row:last-child {
	border-bottom: 0;
}
.pick-row:hover:not([disabled]) {
	background: var(--chip);
}
.pick-name {
	font-weight: 600;
}
.pick-chip {
	margin-left: auto;
	padding: 2px 8px;
	border-radius: 999px;
	background: var(--chip);
	border: 1px solid var(--line);
	font-family: var(--font-mono);
	font-size: 0.68rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
}
/* Listed but not offerable: the answer to "why can't I pick them?" belongs on
   the row itself, not in an error after the click. */
.pick-row.not-friend {
	cursor: default;
}
.pick-row.not-friend > :not(.add-friend):not(.requested) {
	opacity: 0.55;
}
.pick-note {
	margin-left: auto;
	font-size: 0.72rem;
	color: var(--muted);
}
.pick-row .pick-note + .pick-chip,
.pick-row .pick-note + .add-friend {
	margin-left: 0;
}
.pick-row .add-friend {
	flex: 0 0 auto;
	min-width: 0;
	padding: 4px 10px;
	font-size: 0.75rem;
}
.pick-empty,
.pick-foot {
	text-align: left;
	margin: 8px 2px 0;
	font-size: 0.78rem;
}
.pick-empty {
	margin: 10px;
}

.reader-chip {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 4px 10px;
	margin: 0 6px 6px 0;
	border-radius: 999px;
	background: var(--chip);
	border: 1px solid var(--line);
	font-size: 0.85rem;
}
.reader-x {
	background: none;
	border: none;
	color: var(--muted);
	cursor: pointer;
	padding: 0 2px;
	min-width: 0;
	font-size: 1rem;
}
.reader-x:hover {
	color: var(--accent);
}
.share-toggle {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 10px 0;
	font-size: 0.9rem;
	cursor: pointer;
}
.share-toggle input {
	width: auto;
}

/* ---------- "/" reference palette ---------- */
.slash-palette {
	position: absolute;
	z-index: 2600;
	width: 290px;
	max-height: 300px;
	overflow: hidden;
	display: flex;
	flex-direction: column;
	background: var(--panel);
	border: 1px solid var(--accent-2);
	border-radius: var(--r-md);
	box-shadow: 0 18px 44px rgba(0, 0, 0, 0.45);
	backdrop-filter: blur(16px);
	text-align: left;
}
.slash-palette.hidden {
	display: none;
}
.sp-head {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 8px 12px;
	border-bottom: 1px solid var(--line);
}
.sp-crumb {
	font-size: 0.82rem;
	color: var(--ink);
	font-weight: 600;
}
.sp-foot {
	border-top: 1px solid var(--line);
	padding: 6px 10px;
}
.sp-thes {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 4px 4px;
	border-radius: 8px;
	font-size: 0.8rem;
	color: var(--ink);
	text-decoration: none;
}
.sp-thes:hover {
	background: var(--panel-2);
}
.sp-thes .sp-hint {
	margin-left: auto;
}
.sp-keys {
	margin-left: auto;
	font-size: 0.68rem;
	color: var(--muted);
}
.sp-list {
	list-style: none;
	margin: 0;
	padding: 4px;
	overflow-y: auto;
}
.sp-row {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 7px 10px;
	border-radius: 9px;
	cursor: pointer;
	font-size: 0.9rem;
}
.sp-row:hover {
	background: var(--chip);
}
.sp-row.sel {
	background: var(--chip);
	box-shadow: inset 2px 0 0 var(--accent);
}
.sp-hint {
	margin-left: auto;
	color: var(--muted);
	font-size: 0.74rem;
	font-family: var(--font-mono);
}
.sp-empty {
	padding: 12px;
	color: var(--muted);
	font-size: 0.85rem;
	text-align: center;
}

/* These set display:*, which outranks the global .hidden by source order —
   without re-declaring it here they could never be hidden (same reason
   .confirm-modal.hidden exists above). */
.restore-bar.hidden,
.doc-statusbar.hidden,
.presence-row.hidden,
.toolbar.hidden,
.chat-row.hidden,
.sess-acts.hidden,
.guided-controls.hidden,
.guided-controls label.hidden,
.player-chip.hidden,
.prompt-roll-result.hidden,
.scoreboard.hidden,
.doc-main.hidden {
	display: none;
}

/* ---------- Who can see this: the visibility chip + menu ---------- */
/* The chip names the CURRENT state, so it reads rather than asks. Three
   levels can't be a switch, so the menu is a radio list with each option's
   consequence spelled out under its label. */
.vis-wrap {
	position: relative;
	display: inline-flex;
}
/* Head chips: the controls that say who else is in this document (readers)
   and how you're working in it (comment mode). Same shape as the visibility
   chip beside them, so the three read as one row of state. */
.head-chip {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	flex: 0 0 auto;
	min-width: 0;
	padding: 2px 9px;
	border: 1px solid var(--line);
	border-radius: 999px;
	background: var(--panel-2);
	color: var(--ink);
	font: inherit;
	font-size: 0.76rem;
	line-height: 1.5;
	white-space: nowrap;
	cursor: pointer;
}
.head-chip:hover {
	border-color: var(--accent-2);
}
.head-chip.on {
	background: var(--chip);
	border-color: var(--accent);
	color: var(--ink);
}

.vis-chip {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	padding: 2px 8px;
	border: 1px solid var(--line);
	border-radius: 999px;
	background: var(--panel-2);
	color: var(--ink);
	font: inherit;
	font-size: 0.76rem;
	cursor: pointer;
	white-space: nowrap;
}
.vis-chip:hover {
	border-color: var(--accent-2);
}
.vis-chip[data-vis="public"] {
	border-color: var(--accent);
}
.vis-chip .chev {
	color: var(--muted);
	font-size: 0.7rem;
}
.vis-menu {
	display: none;
	position: absolute;
	top: calc(100% + 6px);
	left: 0;
	z-index: 70;
	min-width: 260px;
	padding: 6px;
	border: 1px solid var(--panel-brd);
	border-radius: var(--r-md);
	background: var(--panel);
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(14px);
	-webkit-backdrop-filter: blur(14px);
}
.vis-menu.open {
	display: block;
}
.vis-opt {
	display: flex;
	flex-direction: column;
	gap: 2px;
	width: 100%;
	padding: 8px 10px;
	border: 0;
	border-radius: var(--r-md);
	background: none;
	color: var(--ink);
	font: inherit;
	text-align: left;
	cursor: pointer;
}
.vis-opt:hover {
	background: var(--panel-2);
}
.vis-opt.on {
	background: var(--chip);
}
.vis-opt-label {
	font-size: 0.86rem;
	font-weight: 600;
}
.vis-opt-blurb {
	font-size: 0.74rem;
	color: var(--muted);
}

/* ---------- Rich text / HTML mode switch ---------- */
/* A segmented control, not a toggle button: the LIT half is the mode you are
   currently in, so the control states rather than asks. */
.mode-switch {
	display: inline-flex;
	flex: 0 0 auto;
	padding: 3px;
	gap: 3px;
	border-radius: var(--r-md);
	background: var(--panel-2);
	border: 1px solid var(--line);
}
.mode-switch.hidden {
	display: none;
}
.mode-switch .mode-btn {
	flex: 0 0 auto;
	min-width: 0;
	padding: 6px 12px;
	border: none;
	border-radius: 9px;
	background: transparent;
	color: var(--muted);
	font-size: 0.84rem;
	font-weight: 600;
	cursor: pointer;
	transition:
		background 0.15s,
		color 0.15s;
}
.mode-switch .mode-btn:hover {
	color: var(--ink);
}
.mode-switch .mode-btn.on {
	background: var(--accent);
	color: var(--btn-ink);
	box-shadow: 0 1px 6px rgba(0, 0, 0, 0.18);
}
.mode-switch .mode-btn#modeHtml {
	font-family: var(--font-mono);
}
/* the source pane reads as "code", reinforcing the active mode */
.doc-source {
	border-color: var(--muted);
	border-style: dashed;
}

/* ---------- Font-size ladder (document content) ---------- */
/* A closed set of classes, mirroring FONT_SIZES in src/sanitize.js. Sizes ride
   in the saved html, so beta readers and exports see them too. */
.doc-editor .fs-6,
.doc-read .fs-6,
.story .fs-6,
.editor .fs-6,
.live .fs-6 {
	font-size: 6px;
}
.doc-editor .fs-8,
.doc-read .fs-8,
.story .fs-8,
.editor .fs-8,
.live .fs-8 {
	font-size: 8px;
}
.doc-editor .fs-10,
.doc-read .fs-10,
.story .fs-10,
.editor .fs-10,
.live .fs-10 {
	font-size: 10px;
}
.doc-editor .fs-12,
.doc-read .fs-12,
.story .fs-12,
.editor .fs-12,
.live .fs-12 {
	font-size: 12px;
}
.doc-editor .fs-14,
.doc-read .fs-14,
.story .fs-14,
.editor .fs-14,
.live .fs-14 {
	font-size: 14px;
}
.doc-editor .fs-16,
.doc-read .fs-16,
.story .fs-16,
.editor .fs-16,
.live .fs-16 {
	font-size: 16px;
}
.doc-editor .fs-18,
.doc-read .fs-18,
.story .fs-18,
.editor .fs-18,
.live .fs-18 {
	font-size: 18px;
}
.doc-editor .fs-20,
.doc-read .fs-20,
.story .fs-20,
.editor .fs-20,
.live .fs-20 {
	font-size: 20px;
}
.doc-editor .fs-24,
.doc-read .fs-24,
.story .fs-24,
.editor .fs-24,
.live .fs-24 {
	font-size: 24px;
}
.doc-editor .fs-28,
.doc-read .fs-28,
.story .fs-28,
.editor .fs-28,
.live .fs-28 {
	font-size: 28px;
}
.doc-editor .fs-32,
.doc-read .fs-32,
.story .fs-32,
.editor .fs-32,
.live .fs-32 {
	font-size: 32px;
}
.doc-editor .fs-36,
.doc-read .fs-36,
.story .fs-36,
.editor .fs-36,
.live .fs-36 {
	font-size: 36px;
}
.doc-editor .fs-48,
.doc-read .fs-48,
.story .fs-48,
.editor .fs-48,
.live .fs-48 {
	font-size: 48px;
}

/* A heading's size is the heading's to decide. An fs-* span inside one wins on
   nearest-ancestor, so a pasted or leftover 12px span could render an H1 at
   12px — the same span the toolbar strips when you APPLY a heading. This is
   the standing rule for anything that arrives another way (a paste, an older
   document, a story line written before the toolbar had sizes). */
.doc-editor :is(h1, h2, h3) [class*="fs-"],
.doc-read :is(h1, h2, h3) [class*="fs-"],
.story :is(h1, h2, h3) [class*="fs-"],
.editor :is(h1, h2, h3) [class*="fs-"],
.live :is(h1, h2, h3) [class*="fs-"] {
	font-size: inherit;
}

/* ---------- Typeface (this writer's preference only) ---------- */
/* --doc-font is set by doc-prefs.js on the write page's ROOT, so the choice
   dresses the WHOLE page — title, toolbar, cards, comments — not just the
   prose. Unset, the theme's own faces show through untouched. Like paper and
   line spacing it is a VIEW preference and never reaches the html. */
.doc-editor,
.doc-source {
	font-family: var(--doc-font, var(--font-story));
}
.doc-source {
	font-family: var(--doc-font, var(--font-mono));
}
/* The page follows the choice too. Most of this page hard-codes a family
   (display for headings, mono for meta), so those are told to inherit —
   scoped to .write-inner, which keeps the nav drawer and the toasts outside
   it on the site's own faces. */
.write-page .write-inner {
	font-family: var(--doc-font, var(--font-body));
}
.write-page
	.write-inner
	:is(
		h1,
		h2,
		h3,
		h4,
		p,
		span,
		b,
		i,
		em,
		strong,
		li,
		label,
		a,
		button,
		select,
		input,
		textarea,
		.doc-title-input,
		.doc-card-title,
		.doc-card-meta,
		.doc-comment,
		.subtle
	) {
	font-family: inherit;
}
/* ...except the things whose whole job is to be a different face: the html
   source view (code is mono), and the size stepper's numerals. */
.write-page .write-inner :is(.doc-source, .step-input, .step-value) {
	font-family: var(--doc-font, var(--font-mono));
}

/* ---------- Paper colour (this writer's preference only) ---------- */
/* data-paper is set from doc-prefs.js on the editing surfaces. Like line
   spacing it is a VIEW preference: it never enters the html, so a beta
   reader and an export are unaffected by what you draft against. The
   default ("theme") sets nothing and leaves the site's own panel colour. */
.doc-editor[data-paper="light"] {
	background: #ffffff;
	color: #14161c;
	border-color: #d7dbe4;
}
.doc-editor[data-paper="dark"] {
	background: #000000;
	color: #f2f3f7;
	border-color: #2a2d36;
}
/* the placeholder and the comment underline have to follow the paper */
.doc-editor[data-paper="light"]:empty:before {
	color: #6b7180;
}
.doc-editor[data-paper="dark"]:empty:before {
	color: #8d93a3;
}
.doc-editor[data-paper="light"] ::selection {
	background: #b9d4ff;
}
.doc-editor[data-paper="dark"] ::selection {
	background: #33405c;
}

/* ---------- Line spacing (this reader's preference only) ---------- */
/* Driven by --doc-line from doc-prefs.js. Never written into the document. */
.doc-editor,
.doc-read {
	line-height: var(--doc-line, 1.7);
}

/* ---------- "− value +" steppers ---------- */
.stepper {
	display: inline-flex;
	align-items: center;
	gap: 2px;
	flex: 0 0 auto;
	padding: 2px;
	border-radius: var(--r-md);
	background: var(--panel-2);
	border: 1px solid var(--line);
}
.stepper .step-btn {
	flex: 0 0 auto;
	min-width: 0;
	width: 24px;
	height: 24px;
	padding: 0;
	border: none;
	border-radius: 7px;
	background: transparent;
	color: var(--muted);
	font-size: 0.95rem;
	line-height: 1;
	cursor: pointer;
}
.stepper .step-btn:hover {
	background: var(--chip);
	color: var(--ink);
}
.stepper .step-input {
	width: 34px;
	padding: 2px;
	border: none;
	background: transparent;
	color: var(--ink);
	text-align: center;
	font-family: var(--font-mono);
	font-size: 0.82rem;
}
.stepper .step-input:focus {
	outline: none;
	background: var(--chip);
	border-radius: 6px;
}
.stepper .step-value {
	min-width: 26px;
	text-align: center;
	color: var(--ink);
	font-family: var(--font-mono);
	font-size: 0.8rem;
}
.stepper .step-label {
	padding: 0 2px 0 6px;
	color: var(--muted);
	font-size: 0.8rem;
}
/* the preference stepper reads as chrome, not as a formatting tool */
.pref-stepper {
	background: transparent;
	border-style: dashed;
}

/* ==========================================================================
   Phones (≤760px): the solo-write pages
   --------------------------------------------------------------------------
   Three breakpoints, three jobs, and the gap between them is deliberate:

     860  write-specific COLUMNS only — .doc-main goes to one column and the
          comments pane stops being pinned. An iPad in portrait (768–834px)
          lives here: it wants one column but keeps the sticky, single-row
          toolbar, because it has the vertical room and doesn't have the
          keyboard bug below.
     760  the site's PHONE breakpoint (already where .topbar, .chat-dock and
          .game-cols reflow). Everything in this block lives here.
     600  a very narrow squeeze (already hides .user-chip).

   Don't "unify" 860 and 760 — they mean different things.
   ========================================================================== */
@media (max-width: 760px) {
	/* ---- The head stops being pinned ----
	   On a 390×844 phone with the keyboard up the visual viewport is ~390×390.
	   A sticky slab would leave almost no prose. Worse, iOS Safari resolves
	   position:sticky against the LAYOUT viewport while the keyboard shrinks
	   the VISUAL one, and WebKit's caret-following scroll inside a
	   contenteditable doesn't account for sticky overlap — so the caret ends up
	   typing underneath the toolbar. There's no CSS fix for that; letting the
	   head scroll away with the prose is the fix. Scrolling back one screen to
	   reach the toolbar is cheap: on a phone, formatting is punctuated, not
	   continuous. See stickyH() in write.html — --doc-sticky goes to 0 here.

	   relative, NOT static: un-pinning it is the point, but `z-index: 45` only
	   applies to a positioned box, and going static would drop the whole head
	   below the editor — the visibility menu would render *under* the prose. */
	.doc-shell {
		position: relative;
		top: auto;
		margin-bottom: 10px;
		border-radius: 0;
	}

	/* ---- ~20 controls become ONE strip you swipe, not five wrapped rows ----
	   This is what makes losing the sticky head cheap: nothing is hidden and
	   nothing is folded behind a toggle — the whole toolbar is still there,
	   sideways. No edge fade: the strip is ~1400px of controls in ~350px, so a
	   glyph is always visibly cut off at the edge, and that IS the affordance.
	   A permanent gradient mask would instead dim whichever control you had
	   just scrolled to. */
	.doc-toolbar-row {
		flex-wrap: nowrap;
		overflow-x: auto;
		overflow-y: hidden;
		overscroll-behavior-x: contain;
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
		gap: 6px;
		padding: 5px 10px;
	}
	.doc-toolbar-row::-webkit-scrollbar {
		display: none;
	}
	.doc-toolbar-row .toolbar {
		flex: 0 0 auto;
		flex-wrap: nowrap;
	}
	.doc-toolbar-row .toolbar-right {
		flex: 0 0 auto;
		margin-left: 6px;
	}

	/* ---- The head reflows into three rows, with no markup change ----
	   Row 1: ← All writes … [theme][Save][☰]  (the shared chrome is moved in
	   here at runtime by write.html, so the right cluster is heavier than the
	   markup suggests). Row 2: the title, full width. Row 3: the chips.
	   DOM order is already back / main / right, so one `order` does it. */
	.doc-head {
		flex-wrap: wrap;
		align-items: center;
		gap: 6px 10px;
		padding: 8px 10px;
	}
	.doc-back {
		order: 1;
		display: inline-flex;
		align-items: center;
		min-height: 40px;
		padding: 8px 4px;
		font-size: 0.82rem;
	}
	/* Row 1 is back-link + [theme][Save][☰], and at 390px it fits by a hair —
	   so the pieces are trimmed to keep it ONE row rather than two. */
	.doc-head-right {
		order: 2;
		margin-left: auto;
		gap: 5px;
		min-width: 0;
	}
	body.doc-page .hamburger {
		width: 38px;
		height: 38px;
	}
	.doc-head-main {
		order: 3;
		flex: 1 1 100%;
	}
	.doc-head-main .doc-title-input {
		font-size: 1.15rem;
		padding: 4px 8px;
		margin: 0 0 1px -8px;
	}
	.doc-save {
		padding: 9px 14px;
		font-size: 0.92rem;
	}
	body.doc-page .topbar {
		gap: 5px;
		min-width: 0;
	}
	/* The theme name can be long ("Rink-O-Mania"); let the switch give up
	   width rather than push Save onto its own row. */
	body.doc-page .theme-switch {
		min-width: 0;
		max-width: 108px;
	}
	body.doc-page .theme-switch .theme-toggle {
		min-width: 0;
		max-width: 100%;
	}
	body.doc-page .theme-switch #themeCurLabel {
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
		min-width: 0;
	}

	/* ---- Chips: same row of state, grown to a thumb ----
	   The word labels stay. Glyph-only would be narrower but would cost anyone
	   who doesn't read the icons; two chips wrapping to a second row at 360px
	   is the better trade. */
	.doc-head-meta {
		gap: 6px;
		row-gap: 6px;
		font-size: 0.82rem;
	}
	.head-chip,
	.vis-chip {
		min-height: 32px;
		padding: 6px 11px;
		font-size: 0.8rem;
		line-height: 1.25;
	}

	/* ---- The writing surface ----
	   svh is the SMALL viewport height, so the box doesn't overshoot while the
	   iOS URL bar is expanded; the vh line above it is the fallback. */
	.doc-editor {
		min-height: 52vh;
		min-height: 52svh;
		padding: 14px 12px;
		border-radius: var(--r-md);
	}
	#editorHint {
		font-size: 0.8rem;
	}

	/* Anything under 16px makes iOS Safari zoom the page on focus, and it never
	   zooms back. These four are the write page's offenders. */
	.doc-source,
	.doc-toolbar-row .step-input,
	.doc-side-card textarea,
	.confirm-card input {
		font-size: 16px;
	}
	.doc-toolbar-row .step-input {
		width: 34px; /* 16px digits need the extra room */
	}

	/* ---- Comments ----
	   The drawer is already a bottom sheet at 860 (see there); here it just
	   gets phone padding. */
	.doc-side-card {
		padding: 12px 12px 14px;
	}
	.dc-actions button {
		padding: 7px 10px;
		font-size: 0.82rem;
	}

	/* ---- Popovers clamp to the viewport ----
	   The visibility chip sits mid-row (after "Saved · N words"), so a menu
	   anchored to the chip with left:0 hangs off the right edge and makes the
	   whole page scroll sideways. Anchor it to the chips ROW instead: it then
	   spans the row's full width and drops below the chips, both edges on
	   screen no matter where the chip itself landed after wrapping. */
	.doc-head-meta {
		position: relative;
	}
	.vis-wrap {
		position: static;
	}
	.vis-menu {
		left: 0;
		right: 0;
		width: auto;
		min-width: 0;
		max-height: 70vh;
		overflow-y: auto;
		/* --panel is half-transparent and used to be legible because the menu
		   sat over the shell's own slab. Here it hangs past the shell onto bare
		   prose, so paint the opaque page colour under the panel tint. */
		background-color: var(--bg);
		background-image: linear-gradient(var(--panel), var(--panel));
	}
	.slash-palette {
		width: calc(100vw - 20px);
		max-width: 320px;
		max-height: min(300px, 42svh);
		/* same reason as .vis-menu: it floats over bare prose here */
		background-color: var(--bg);
		background-image: linear-gradient(var(--panel), var(--panel));
	}

	/* The 16px page gutter on .wrap > .archive-inner is unconditional now
	   (see there) — narrow windows need nothing extra. */
	.archive-head {
		flex-wrap: wrap;
		gap: 10px;
		margin: 4px 0 16px;
	}
	.doc-grid {
		gap: 12px;
	}
	.doc-card {
		padding: 14px;
	}
	.doc-card-actions > * {
		min-height: 42px;
	}

	/* ---- Modals ----
	   .confirm-card is already fluid. The problem is the keyboard: a centred
	   flex item on a shrunk viewport clips its own top with no way to reach it,
	   so the modal top-aligns and scrolls instead. */
	.confirm-modal {
		align-items: flex-start;
		overflow-y: auto;
		padding: 16px 12px 48px;
	}
	.confirm-card {
		padding: 18px 16px;
	}

	/* ---- Touch targets ----
	   44px is the guideline, but it would make the strip three screens long.
	   40px is the practical floor for a swipeable strip where a mis-tap is
	   cheap and undoable. */
	.tb-group > button.ghost,
	.tb-group > select.ghost {
		min-width: 40px;
		min-height: 40px;
		padding: 8px 9px;
		font-size: 0.95rem;
	}
	.tb-group + .tb-group {
		margin-left: 6px;
		padding-left: 6px;
	}
	.doc-toolbar-row #blockFormat {
		max-width: 74px;
	}
	.doc-toolbar-row .step-btn {
		min-width: 32px;
		min-height: 32px;
		padding: 4px 7px;
		font-size: 1rem;
	}
	.doc-toolbar-row .mode-switch .mode-btn {
		min-height: 36px;
		padding: 8px 12px;
	}
}

@media (max-width: 600px) {
	/* The interpuncts separate a single line of meta. Once the row wraps they
	   just cost width and point at nothing. */
	.doc-head-meta .doc-dot {
		display: none;
	}
	/* At 360px the head's first row misses fitting by ~13px, which costs a
	   whole row. The theme name is the one label that can be shortened without
	   losing anything — the swatch beside it already says which theme it is. */
	body.doc-page .theme-switch {
		max-width: 84px;
	}
}

/* A hover lift becomes a STUCK state after a tap on touch — the button looks
   jammed rather than pressed. */
@media (hover: none) {
	.toolbar button:hover,
	.doc-card:hover,
	.head-chip:hover,
	.vis-chip:hover {
		transform: none;
	}
}

/* Prompt-generation mode picker on the vote card: curated vs guided, and the
   guided knobs (host-only) that redeal the ballot. */
.mode-row {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 0 0 12px;
	flex-wrap: wrap;
}
.guided-controls {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
	gap: 10px 14px;
	margin: 0 0 16px;
	padding: 14px;
	border: 1px solid var(--line);
	border-radius: var(--r-md);
	background: var(--panel-2);
}
.guided-controls label {
	display: flex;
	flex-direction: column;
	gap: 4px;
	font-size: 0.82rem;
	color: var(--muted);
	margin: 0;
}
.guided-controls label.gc-check {
	flex-direction: row;
	align-items: center;
	gap: 8px;
	align-self: end;
}
.guided-controls select {
	width: 100%;
	/* opaque, like the typeface menu: a translucent control over a themed
	   background makes its own text hard to read, and these are choices you
	   read before you make them */
	background: color-mix(in srgb, var(--bg-2) 92%, var(--ink) 8%);
	color: var(--ink);
	border-color: var(--chip-brd);
}
.guided-controls select option {
	background: var(--bg-2);
	color: var(--ink);
}
/* an option the generator would refuse beside the other choices */
.guided-controls select option:disabled {
	color: var(--muted);
	opacity: 0.5;
}
.mode-hint {
	font-size: 0.78rem;
}
/* Advanced needs the component library; without it the chip is plainly off. */
.mode-row .head-chip:disabled {
	opacity: 0.4;
	cursor: not-allowed;
}
/* The component chips under a guided option — the scene it was built from. */
.opt-chips {
	text-indent: 0; /* the option's hanging indent is for prose, not chips */
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	margin-top: 10px;
}
.opt-chip {
	font-family: var(--font-mono);
	font-size: 0.7rem;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--muted);
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
	padding: 3px 9px;
}

/* A theme you haven't earned yet: still listed (it's a reward you can see
   coming), plainly out of reach, and inert on click. */
.theme-menu button.locked {
	opacity: 0.45;
	cursor: not-allowed;
}
.theme-menu button.locked:hover {
	background: none;
}
.theme-menu button.locked .sw {
	filter: grayscale(1);
}

/* ==========================================================================
   Gimmicks: the dice layer (public/js/components/gimmick-dice.js) and its
   die (components/d20-die.js). The dice tumble OVER the live game UI — the
   layer sits over everything at z 60, above the chat dock (45), under the
   foot bar (100) and the turn toast (66) — never inside .bg-layers. It takes
   no pointer events; only MY die and the HUD glass do, so the page under it
   stays usable and the writer keeps writing while dice land on their editor.
   ========================================================================== */
.gd-layer {
	position: fixed;
	inset: 0 0 var(--footbar-h) 0;
	z-index: 60;
	pointer-events: none;
}
.gd-layer .glass {
	pointer-events: auto;
	border-radius: var(--r-md);
	background: color-mix(in srgb, var(--panel) 42%, transparent);
	border: 1px solid color-mix(in srgb, var(--panel-brd) 70%, transparent);
	backdrop-filter: blur(6px) saturate(1.1);
	-webkit-backdrop-filter: blur(6px) saturate(1.1);
	box-shadow: var(--panel-glow);
}
.gd-hud {
	position: absolute;
	bottom: 12px;
	left: 16px;
	display: flex;
	flex-direction: column;
	gap: 6px;
	padding: 10px 14px;
	max-width: min(360px, calc(100vw - 32px));
	font: 500 0.82rem/1.3 var(--font-body);
	color: var(--muted);
}
.gd-hud b#gdTitle {
	font: 700 1rem/1.2 var(--font-display);
	color: var(--ink);
}
#gdRead {
	display: flex;
	align-items: baseline;
	gap: 8px;
	min-height: 1.4em;
}
.gd-result {
	font: 700 1.6rem/1 var(--font-display);
	color: var(--ink);
	font-variant-numeric: tabular-nums;
}
.gd-result.crit {
	color: #ffcf7d;
	text-shadow: 0 0 18px rgba(255, 180, 100, 0.55);
}
.gd-result.fumble {
	color: var(--muted);
}
.gd-hint {
	font-size: 0.78rem;
}
.gd-steal {
	font-size: 0.78rem;
	color: var(--ink);
	cursor: pointer;
	user-select: none;
}
/* the die itself: an absolutely placed button, moved by transform */
.gd-die {
	position: absolute;
	left: 0;
	top: 0;
	pointer-events: auto;
	cursor: grab;
	touch-action: none;
	will-change: left, top;
}
.gd-die.grabbing {
	cursor: grabbing;
}
.gd-die.grabbing .d20-lift {
	transform: translate3d(0, -14px, 0) !important;
}
/* everyone else's dice: inert, named */
.gd-die.remote {
	pointer-events: none;
	cursor: default;
}
.gd-die-tag {
	position: absolute;
	left: 50%;
	top: calc(100% + 18px);
	transform: translateX(-50%);
	white-space: nowrap;
	font: 700 0.78rem/1 var(--font-body);
	text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
	pointer-events: none;
}
.gd-away {
	min-width: 0;
	width: auto;
	flex: 0 0 auto;
	align-self: flex-start;
	padding: 6px 10px;
	font-size: 0.78rem;
}
/* a server refusal, said over the table */
.gd-note {
	position: absolute;
	left: 50%;
	bottom: calc(var(--footbar-h) + 64px);
	transform: translateX(-50%);
	padding: 6px 12px;
	border-radius: var(--r-md);
	background: var(--panel);
	border: 1px solid var(--panel-brd);
	color: var(--ink);
	font-size: 0.82rem;
	pointer-events: none;
	display: block;
}
/* the dropdown above the foot bar */
.gd-menu {
	position: fixed;
	left: 50%;
	bottom: calc(var(--footbar-h) + 8px);
	transform: translateX(-50%);
	z-index: 101;
	min-width: 220px;
	max-width: min(92vw, 380px);
	display: flex;
	flex-direction: column;
	gap: 3px;
	padding: 6px;
	border-radius: var(--r-md);
	background: var(--panel);
	border: 1px solid var(--panel-brd);
	box-shadow: var(--panel-glow);
	backdrop-filter: blur(14px);
	-webkit-backdrop-filter: blur(14px);
}
.gd-menu-item {
	width: 100%;
	text-align: left;
	min-width: 0;
}
.gd-menu-item.locked {
	opacity: 0.7;
	cursor: not-allowed;
}
.gd-menu-note {
	padding: 8px 10px;
	font-size: 0.82rem;
	color: var(--muted);
}

/* ---------- the d20 (components/d20-die.js) ---------- */
.d20-stage {
	--die-size: 150px;
	position: relative;
	flex: 0 0 auto; /* the global button rule's flex:1 would zero its height in a column */
	display: block;
	width: var(--die-size);
	height: var(--die-size);
	perspective: 900px;
	border: 0;
	padding: 0;
	background: none;
	box-shadow: none;
	outline: none;
	min-width: 0;
	pointer-events: none;
	-webkit-tap-highlight-color: transparent;
}
button.d20-stage {
	pointer-events: auto;
	cursor: pointer;
}
button.d20-stage:hover,
button.d20-stage:active {
	transform: none; /* the die moves by its own transforms, not the button hover lift */
}
button.d20-stage:focus-visible .d20-flare {
	opacity: 0.3;
}
button.d20-stage.rolling {
	cursor: progress;
}
.d20-lift,
.d20-die {
	position: absolute;
	inset: 0;
	transform-style: preserve-3d;
	will-change: transform;
}
.d20-face {
	position: absolute;
	left: 50%;
	top: 50%;
	backface-visibility: hidden;
}
.d20-face-in {
	position: absolute;
	inset: 0;
	display: grid;
	place-items: center;
	backface-visibility: hidden;
	transform: scale(0.9);
}
.d20-pip {
	font-family: var(--font-display);
	font-size: calc(var(--die-size) * 0.155);
	font-weight: 700;
	letter-spacing: -0.02em;
	font-variant-numeric: tabular-nums;
	line-height: 1;
	transform: translateY(-6%);
}
.d20-pip u {
	text-decoration: none;
	border-bottom: 0.09em solid currentColor;
	padding-bottom: 0.08em;
}
.d20-shadow {
	position: absolute;
	left: 50%;
	bottom: -26px;
	width: 78%;
	height: 26px;
	border-radius: 50%;
	background: radial-gradient(closest-side, rgba(0, 0, 0, 0.66), rgba(0, 0, 0, 0));
	filter: blur(3px);
	pointer-events: none;
}
.d20-flare {
	position: absolute;
	left: 50%;
	top: 50%;
	width: 160%;
	height: 160%;
	translate: -50% -50%;
	border-radius: 50%;
	background: radial-gradient(closest-side, rgba(255, 168, 92, 0.5), rgba(255, 120, 60, 0) 70%);
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.45s ease;
}
.d20-flare.on {
	opacity: 1;
}
.d20-stage.fumble {
	filter: saturate(0.6) brightness(0.85);
}
/* ---------- Palace Arcade Galaga (components/galaga-game.js) ----------
   The arcade theme's pixel sprites (see the .bg-set.arcade blocks above)
   promoted from scenery to a SHARED battle fought over the live game, the
   dice's way: full screen at z 60, no pointer events (the page under it
   stays usable — only the HUD glass takes the pointer), one .gg-battle per
   player, each ship re-inked in its owner's palette colour (inline
   box-shadow from shipShadow()). Remote sprites ease between relayed
   positions so an 80ms trickle reads as flight. */
.gg-layer {
	position: fixed;
	inset: 0 0 var(--footbar-h) 0;
	z-index: 60;
	pointer-events: none;
	display: block;
}
.gg-battle {
	position: absolute;
	inset: 0;
	pointer-events: none;
}
/* the bee: the arcade background's sprite grid, verbatim */
.gg-enemy {
	position: absolute;
	width: 4px;
	height: 4px;
	margin: -12px 0 0 -2px; /* left/top land on the sprite's centre */
	background: transparent;
	box-shadow:
		-8px 0 0 #4fa8ff,
		8px 0 0 #4fa8ff,
		-4px 4px 0 #ff4f79,
		0 4px 0 #ff4f79,
		4px 4px 0 #ff4f79,
		-8px 8px 0 #ffd23f,
		-4px 8px 0 #ffd23f,
		0 8px 0 #ffd23f,
		4px 8px 0 #ffd23f,
		8px 8px 0 #ffd23f,
		-12px 12px 0 #ffd23f,
		-4px 12px 0 #ffd23f,
		0 12px 0 #ffd23f,
		4px 12px 0 #ffd23f,
		12px 12px 0 #ffd23f,
		-4px 16px 0 #ff4f79,
		4px 16px 0 #ff4f79,
		-8px 20px 0 #4fa8ff,
		8px 20px 0 #4fa8ff;
	filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.45));
}
.gg-enemy.dive {
	filter: brightness(1.35) drop-shadow(0 0 6px rgba(255, 210, 63, 0.7));
}
.gg-enemy.hit {
	transition:
		transform 0.24s ease,
		opacity 0.24s ease;
	transform: scale(2.2);
	opacity: 0;
}
/* the player ship: the pixel triangle; its colours come inline (shipShadow) */
.gg-ship {
	position: absolute;
	width: 4px;
	height: 4px;
	margin: -2px 0 0 -2px;
	background: transparent;
	filter: drop-shadow(0 0 5px rgba(0, 0, 0, 0.5));
}
.gg-ship-tag {
	position: absolute;
	transform: translateX(-50%);
	font: 600 0.68rem/1 var(--font-body);
	text-shadow: 0 1px 4px rgba(0, 0, 0, 0.7);
	white-space: nowrap;
}
.gg-shot {
	position: absolute;
	width: 3px;
	height: 14px;
	margin-left: -1px;
	box-shadow: 0 0 10px rgba(255, 255, 255, 0.6);
}
/* While any Galaga battle is live, the ARCADE THEME's own ambient fleet,
   ship and shot (scenery in .bg-set.arcade) are put away — the game being
   played is the only Galaga on screen. The transition lives on the base
   elements so they fade back in when the battle ends. */
.bg-set.arcade .fleet,
.bg-set.arcade .ship,
.bg-set.arcade .shot {
	transition: opacity 0.6s ease;
}
html.gg-live .bg-set.arcade .fleet,
html.gg-live .bg-set.arcade .ship,
html.gg-live .bg-set.arcade .shot {
	opacity: 0;
}
/* a remote battle's sprites glide between relayed positions */
.gg-battle.remote .gg-ship,
.gg-battle.remote .gg-ship-tag,
.gg-battle.remote .gg-enemy,
.gg-battle.remote .gg-shot {
	transition:
		left 0.09s linear,
		top 0.09s linear;
}
/* my HUD: the one thing on the layer that takes the pointer */
.gg-hud {
	position: absolute;
	bottom: 12px;
	left: 16px;
	pointer-events: auto;
	display: flex;
	flex-direction: column;
	gap: 6px;
	padding: 10px 14px;
	max-width: min(360px, calc(100vw - 32px));
	border-radius: var(--r-md);
	background: color-mix(in srgb, var(--panel) 42%, transparent);
	border: 1px solid color-mix(in srgb, var(--panel-brd) 70%, transparent);
	backdrop-filter: blur(6px) saturate(1.1);
	-webkit-backdrop-filter: blur(6px) saturate(1.1);
	box-shadow: var(--panel-glow);
	font: 500 0.8rem/1.3 var(--font-body);
	color: var(--muted);
}
.gg-title {
	font: 700 1rem/1.2 var(--font-display);
	color: var(--ink);
}
.gg-row {
	display: flex;
	align-items: baseline;
	gap: 10px;
}
.gg-row button {
	flex: 0 0 auto;
}
.gg-score {
	font: 700 1.15rem/1 var(--font-display);
	color: #ffd23f;
	font-variant-numeric: tabular-nums;
}
.gg-meta {
	flex: 1;
	min-width: 0;
}
.gg-time {
	font-variant-numeric: tabular-nums;
	color: var(--ink);
}
.gg-over {
	display: flex;
	flex-direction: column;
	gap: 6px;
	padding: 6px 0;
}
.gg-final {
	font: 700 2rem/1 var(--font-display);
	color: var(--ink);
	font-variant-numeric: tabular-nums;
}
.gg-final.win {
	color: #ffd23f;
	text-shadow: 0 0 18px rgba(255, 210, 63, 0.55);
}

/* ---------- Starcourt Milkshake (components/milkshake-spill.js) ----------
   The jazz-milkshake reference as a shared gimmick: a paper cup per player,
   tinted to its owner, dragged and tipped over the live game; two canvases
   carry the falling drops and the merged puddles along the bottom of the
   screen (over the chat dock — that's the point). Layer contract as the
   dice: fixed at z 60, pointer events only on MY cup and the HUD glass. */
.ms-layer {
	position: fixed;
	inset: 0 0 var(--footbar-h) 0;
	z-index: 60;
	pointer-events: none;
	display: block;
}
.ms-layer canvas {
	position: absolute;
	inset: 0;
	pointer-events: none;
}
.ms-cup {
	position: absolute;
	width: 150px;
	height: 213px;
	border: 0;
	padding: 0;
	background: none;
	box-shadow: none;
	outline: none;
	min-width: 0;
	cursor: grab;
	pointer-events: auto;
	touch-action: none;
	-webkit-tap-highlight-color: transparent;
	filter: drop-shadow(0 10px 14px rgba(0, 0, 0, 0.35));
}
button.ms-cup:hover,
button.ms-cup:active {
	transform: none; /* the cup tips by its own transforms, not the button hover lift */
}
.ms-cup.grabbing {
	cursor: grabbing;
}
.ms-cup svg {
	display: block;
	width: 100%;
	height: 100%;
	overflow: visible;
	transform-origin: 50% 86%;
}
.ms-cup.remote {
	pointer-events: none;
}
.ms-cup.remote,
.ms-cup.remote svg {
	transition:
		left 0.09s linear,
		top 0.09s linear;
}
.ms-cup.remote svg {
	transition: transform 0.12s linear;
}
.ms-cup-tag {
	position: absolute;
	left: 50%;
	bottom: -4px;
	transform: translateX(-50%);
	font: 600 0.68rem/1 var(--font-body);
	text-shadow: 0 1px 4px rgba(0, 0, 0, 0.7);
	white-space: nowrap;
}
.ms-hud {
	position: absolute;
	bottom: 12px;
	left: 16px;
	pointer-events: auto;
	display: flex;
	flex-direction: column;
	gap: 6px;
	padding: 10px 14px;
	max-width: min(380px, calc(100vw - 32px));
	border-radius: var(--r-md);
	background: color-mix(in srgb, var(--panel) 42%, transparent);
	border: 1px solid color-mix(in srgb, var(--panel-brd) 70%, transparent);
	backdrop-filter: blur(6px) saturate(1.1);
	-webkit-backdrop-filter: blur(6px) saturate(1.1);
	box-shadow: var(--panel-glow);
	font: 500 0.8rem/1.3 var(--font-body);
	color: var(--muted);
}
.ms-title {
	font: 700 1rem/1.2 var(--font-display);
	color: var(--ink);
}
.ms-meter {
	height: 8px;
	border-radius: 4px;
	background: color-mix(in srgb, var(--panel-brd) 60%, transparent);
	overflow: hidden;
}
.ms-meter i {
	display: block;
	height: 100%;
	width: 100%;
	background: #ff8fb1;
	transition: width 0.2s ease;
}
.ms-meter i.low {
	background: #e8384f;
}
.ms-row {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
}
.ms-row button {
	flex: 0 0 auto;
}

/* ---------- Rink-O-Mania Disco Ball (components/disco-ball.js) ----------
   A mirrored ball per player, hung by a chain to the top edge, dragged
   anywhere over the live game; a spin sweeps colored light spots across the
   whole page for one show. Same layer contract as the dice and the cups:
   fixed at z 60, pointer events only on MY ball and the HUD glass. */
.db-layer {
	position: fixed;
	inset: 0 0 var(--footbar-h) 0;
	z-index: 60;
	pointer-events: none;
	display: block;
}
/* The show's lights, straight from the reference: small blurred dots orbiting
   the owner's ball (size and brightness come per-frame from JS), and a fan of
   blurred beams turning under it. */
.db-spot {
	position: absolute;
	left: 0;
	top: 0;
	width: 26px;
	height: 26px;
	border-radius: 50%;
	opacity: 0;
	filter: blur(3px);
	pointer-events: none;
	will-change: transform, opacity;
	mix-blend-mode: screen;
}
.db-beams {
	position: absolute;
	left: 0;
	top: 0;
	width: 0;
	height: 0;
	opacity: 0;
	mix-blend-mode: screen;
	filter: blur(7px);
	pointer-events: none;
	will-change: transform, opacity;
}
.db-beam {
	position: absolute;
	left: 0;
	top: 0;
	display: block;
	margin-left: -30px;
	transform-origin: 50% 0;
	background: linear-gradient(rgba(214, 232, 255, 0.3), rgba(190, 215, 255, 0.1) 40%, transparent 78%);
	clip-path: polygon(46% 0%, 54% 0%, 100% 100%, 0% 100%);
}
.db-ball {
	position: absolute;
	width: 76px;
	height: 76px;
	margin: -38px 0 0 -38px; /* positioned by its CENTER (the chain hangs from it) */
	border: 0;
	padding: 0;
	background: none;
	box-shadow: none;
	outline: none;
	min-width: 0;
	cursor: grab;
	pointer-events: auto;
	touch-action: none;
	-webkit-tap-highlight-color: transparent;
	filter: drop-shadow(0 10px 14px rgba(0, 0, 0, 0.35));
}
button.db-ball:hover,
button.db-ball:active {
	transform: none; /* the ball hangs still — no button hover lift */
}
.db-ball.grabbing {
	cursor: grabbing;
}
/* The ball itself is a CSS-3D sphere (see ballHtml in disco-ball.js):
   a perspective stage holds the dark core and the tile sphere, which turns
   forever; the shade/spec overlays are lighting and stay put while it turns.
   A spin doesn't start the rotation — it hurries it. */
.db-ball3d {
	position: absolute;
	inset: 0;
	display: block;
	pointer-events: none;
}
.db-halo {
	position: absolute;
	inset: -26px;
	border-radius: 50%;
	background: radial-gradient(circle, rgba(206, 228, 255, 0.3) 0%, rgba(150, 180, 255, 0.1) 34%, transparent 66%);
	mix-blend-mode: screen;
	animation: db-halo 2.4s ease-in-out infinite alternate;
}
@keyframes db-halo {
	to {
		opacity: 0.55;
	}
}
.db-core {
	position: absolute;
	inset: 3px;
	border-radius: 50%;
	background: radial-gradient(circle at 34% 28%, #263049 0%, #12172a 60%, #070a14 100%);
}
.db-stage {
	position: absolute;
	inset: 0;
	display: block;
	perspective: 300px;
}
.db-facets {
	position: absolute;
	left: 50%;
	top: 50%;
	width: 0;
	height: 0;
	display: block;
	transform-style: preserve-3d;
	will-change: transform;
	animation: db-turn 14s linear infinite;
}
.db-ball3d.spinning .db-facets {
	animation-duration: 2.4s;
}
@keyframes db-turn {
	from {
		transform: rotateX(-8deg) rotateY(0deg);
	}
	to {
		transform: rotateX(-8deg) rotateY(360deg);
	}
}
.db-facet {
	position: absolute;
	left: 0;
	top: 0;
	display: block;
	border-radius: 1px;
	backface-visibility: hidden;
	background: linear-gradient(150deg, #e8f3ff 0%, #8ea6c8 46%, #3d4c6b 100%);
	box-shadow: inset 0 0 0 0.5px rgba(6, 10, 22, 0.55);
	overflow: hidden;
}
.db-facet::after {
	content: "";
	position: absolute;
	inset: 0;
	background: #fff;
	opacity: 0;
	animation: db-glint var(--dur, 5s) linear var(--delay, 0s) infinite;
}
@keyframes db-glint {
	0%,
	88%,
	100% {
		opacity: 0;
	}
	93% {
		opacity: 0.95;
	}
}
/* lighting overlays that stay put while the ball turns */
.db-shade {
	position: absolute;
	inset: 1px;
	border-radius: 50%;
	background: radial-gradient(
		circle at 33% 27%,
		rgba(0, 0, 0, 0) 26%,
		rgba(4, 5, 14, 0.52) 68%,
		rgba(3, 4, 10, 0.92) 100%
	);
	mix-blend-mode: multiply;
}
.db-spec {
	position: absolute;
	inset: 1px;
	border-radius: 50%;
	background:
		radial-gradient(circle at 32% 26%, rgba(255, 255, 255, 0.55) 0%, rgba(214, 234, 255, 0.16) 20%, transparent 46%),
		radial-gradient(circle at 74% 78%, rgba(126, 230, 255, 0.16) 0%, transparent 34%);
	mix-blend-mode: screen;
}
@media (prefers-reduced-motion: reduce) {
	.db-facets,
	.db-facet::after,
	.db-halo {
		animation: none;
	}
}
.db-chain {
	position: absolute;
	left: 50%;
	bottom: 100%;
	width: 2px;
	transform: translateX(-50%);
	background: repeating-linear-gradient(to bottom, #8a92a6 0 5px, transparent 5px 8px);
	pointer-events: none;
}
.db-ball.remote {
	pointer-events: none;
	transition:
		left 0.09s linear,
		top 0.09s linear;
}
.db-tag {
	position: absolute;
	left: 50%;
	bottom: -16px;
	transform: translateX(-50%);
	font: 600 0.68rem/1 var(--font-body);
	text-shadow: 0 1px 4px rgba(0, 0, 0, 0.7);
	white-space: nowrap;
}
.db-hud {
	position: absolute;
	bottom: 12px;
	left: 16px;
	pointer-events: auto;
	display: flex;
	flex-direction: column;
	gap: 6px;
	padding: 10px 14px;
	max-width: min(380px, calc(100vw - 32px));
	border-radius: var(--r-md);
	background: color-mix(in srgb, var(--panel) 42%, transparent);
	border: 1px solid color-mix(in srgb, var(--panel-brd) 70%, transparent);
	backdrop-filter: blur(6px) saturate(1.1);
	-webkit-backdrop-filter: blur(6px) saturate(1.1);
	box-shadow: var(--panel-glow);
	font: 500 0.8rem/1.3 var(--font-body);
	color: var(--muted);
}
.db-title {
	font: 700 1rem/1.2 var(--font-display);
	color: var(--ink);
}
.db-row {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
}
.db-row button {
	flex: 0 0 auto;
}

/* The notice when a friendly switch sweeps the gimmicks off the table */
.gimmick-off-toast {
	position: fixed;
	left: 50%;
	bottom: calc(var(--footbar-h) + 18px);
	transform: translateX(-50%);
	z-index: 102;
	padding: 10px 16px;
	border-radius: var(--r-md);
	background: var(--panel);
	border: 1px solid var(--panel-brd);
	box-shadow: var(--panel-glow);
	font: 600 0.85rem/1.3 var(--font-body);
	color: var(--ink);
	pointer-events: none;
	white-space: nowrap;
	max-width: calc(100vw - 24px);
}

/* ============================================================
   EDITORIAL REVAMP — recompose the big screens around the page
   grid instead of nested cards. The page is the container:
   dividers + typography + spacing do the structure, borders only
   where they mark a real boundary (the editor you're operating,
   the prompt's red rule). Radii come from --r-* at :root.
   ============================================================ */

/* ---- the game is a writing console, not a card ---- */
body.game-page .main {
	max-width: 1240px;
}
body.game-page #game.card,
body.game-page #over.card {
	background: none;
	border: 0;
	box-shadow: none;
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
	padding: 8px 4px 20px;
}
/* the console's title row: story name in the story face, one hairline under
   the whole row — code + game actions ride the far end */
body.game-page .session-bar {
	padding-bottom: 12px;
	border-bottom: 1px solid var(--line);
	margin-bottom: 22px;
}
body.game-page .session-bar .sess-name {
	font-family: var(--font-story);
	font-style: italic;
	font-size: 1.35rem;
}
/* the prompt: dark fill behind the red left rule, no card around it */
.prompt-banner {
	background: var(--story-bg);
	border: 0;
	border-left: 4px solid var(--accent);
	border-radius: 0 var(--r-sm) var(--r-sm) 0;
	font-size: 1.15rem;
	line-height: 1.55;
}
/* the story reads as fiction on the page, not text inside a form control */
#game .story {
	background: none;
	border: 0;
	border-radius: 0;
	border-bottom: 1px solid var(--line);
	padding: 24px 2px;
	line-height: 1.7;
}
#game .story > * {
	max-width: 720px;
}
/* the turn row is a game mechanic: the mono call on the left, big digits right */
#game .turnbar {
	margin-top: 20px;
	align-items: baseline;
}
#game .who-turn {
	font-family: var(--font-mono);
	text-transform: uppercase;
	letter-spacing: 0.14em;
	font-size: 0.95rem;
	color: var(--accent-2);
}
#game .timer {
	font-size: 2.4rem;
	font-weight: 800;
	line-height: 1;
}
/* the toolbar groups by whitespace, not by boxes */
#game .toolbar {
	gap: 2px;
}
#game .toolbar button {
	min-width: 34px;
	padding: 7px 9px;
	background: none;
	border: 1px solid transparent;
	box-shadow: none;
}
#game .toolbar button:hover {
	background: var(--chip);
	transform: none;
}
#game .toolbar button.on {
	background: var(--chip);
	color: var(--accent-2);
}
#game .toolbar select {
	background: none;
	border-color: transparent;
}
#game .toolbar select:hover {
	border-color: var(--line);
}
/* the editor stays the one obviously ACTIVE rectangle on the screen */
#game .editor {
	min-height: 180px;
	margin-top: 4px;
}
/* the console speaks mono in its margins */
#game .hint {
	font-family: var(--font-mono);
	text-transform: uppercase;
	letter-spacing: 0.06em;
	font-size: 0.68rem;
	line-height: 1.7;
}
/* ADD LINE is the console's one big control: full width, the gradient */
#game #submitBtn {
	width: 100%;
	font-family: var(--font-mono);
	text-transform: uppercase;
	letter-spacing: 0.18em;
	font-size: 0.95rem;
	border-radius: var(--r-md);
}
/* the right column is a rail, not a card zone: one hairline is the boundary */
#game .game-side {
	margin-top: 0;
	border-left: 1px solid var(--line);
	padding-left: 22px;
}
#game .side-sec {
	background: none;
	border: 0;
	box-shadow: none;
	padding: 0 0 4px;
}

/* ---- the open menu is navigation, not a stack of CTAs ---- */
.nav-scrim {
	background: rgba(0, 0, 0, 0.55);
}
.nav-drawer {
	gap: 2px;
}
.nav-drawer .nav-sep {
	width: 100%;
	border: 0;
	border-top: 1px solid var(--line);
	margin: 8px 0;
}
.nav-drawer a {
	background: none;
	border: 0;
	border-left: 3px solid transparent;
	border-radius: 0 var(--r-sm) var(--r-sm) 0;
	padding: 11px 12px;
}
.nav-drawer a:hover,
.nav-drawer a:focus-visible {
	background: color-mix(in srgb, var(--ink) 6%, transparent);
	border-color: transparent;
	border-left-color: transparent;
}
/* the current page: faint fill + the accent bar, not a ring */
.nav-drawer a[aria-current="page"] {
	background: color-mix(in srgb, var(--ink) 5%, transparent);
	border-left-color: var(--accent);
	color: var(--ink);
}
.nav-drawer button {
	background: none;
	border: 1px solid transparent;
}
.nav-drawer button:hover,
.nav-drawer button:focus-visible {
	border-color: var(--line);
}

/* Every gimmick box above (.gd-layer/.gd-menu/.gd-hud/.gd-die/.gd-note, and
   the Galaga layer's .gg-layer/.gg-battle/.gg-hud/.gg-over, and the milkshake's .ms-layer/.ms-cup/.ms-hud) sets display:*,
   so (per the .hidden source-order rule) the hidden state is re-declared
   AFTER them, here. */
.gd-layer.hidden,
.gd-menu.hidden,
.gd-hud.hidden,
.gd-die.hidden,
.gd-note.hidden,
.gg-layer.hidden,
.gg-battle.hidden,
.gg-hud.hidden,
.gg-over.hidden,
.ms-layer.hidden,
.ms-cup.hidden,
.ms-hud.hidden,
.db-layer.hidden,
.db-ball.hidden,
.db-hud.hidden {
	display: none;
}

/* ---- Will's Art Room (components/art-room.js): paint over the live game.
   Same layer contract as the dice/disco: fixed over the page above the chat
   dock, taking no pointer — only the painter's own invisible catcher and the
   HUD glass do, so painting never costs another writer their caret. */
.ar-layer {
	position: fixed;
	inset: 0 0 var(--footbar-h) 0;
	z-index: 60;
	pointer-events: none;
	display: block;
}
.ar-layer canvas {
	/* fixed to the FULL viewport, not the layer's footbar-inset box — the
	   bitmap is sized to innerWidth/innerHeight, so any mismatch between the
	   element and the bitmap would squash the drawing and make strokes drift
	   away from the pointer toward the bottom of the page */
	position: fixed;
	inset: 0;
	width: 100vw;
	height: 100vh;
	pointer-events: none;
}
#arCatch {
	position: absolute;
	inset: 0;
	pointer-events: auto;
	/* the pointer IS a paintbrush while the room is open */
	cursor:
		url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><text y='24' font-size='24'>🖌</text></svg>")
			3 26,
		crosshair;
	display: block;
}
.ar-brush {
	position: absolute;
	transform: translate(-4px, -18px);
	font-size: 20px;
	pointer-events: none;
	text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
}
.ar-tag {
	position: absolute;
	left: 50%;
	top: 24px;
	transform: translateX(-50%);
	font: 600 0.68rem/1 var(--font-body);
	text-shadow: 0 1px 4px rgba(0, 0, 0, 0.7);
	white-space: nowrap;
}
.ar-hud {
	position: absolute;
	bottom: 12px;
	left: 16px;
	pointer-events: auto;
	display: flex;
	flex-direction: column;
	gap: 6px;
	padding: 10px 14px;
	max-width: min(380px, calc(100vw - 32px));
	border-radius: var(--r-md);
	background: color-mix(in srgb, var(--panel) 42%, transparent);
	border: 1px solid color-mix(in srgb, var(--panel-brd) 70%, transparent);
	backdrop-filter: blur(6px) saturate(1.1);
	-webkit-backdrop-filter: blur(6px) saturate(1.1);
	box-shadow: var(--panel-glow);
	font: 500 0.8rem/1.3 var(--font-body);
	color: var(--muted);
}
.ar-title {
	font: 700 1rem/1.2 var(--font-display);
	color: var(--ink);
}
.ar-row {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
}
.ar-row button {
	flex: 0 0 auto;
}
.ar-swatches {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	flex-wrap: wrap;
}
.ar-swatch {
	/* true circles: pin both axes and opt out of the global button box */
	flex: 0 0 22px;
	width: 22px;
	height: 22px;
	min-width: 0;
	min-height: 0;
	box-sizing: border-box;
	padding: 0;
	line-height: 1;
	font-size: 13px;
	border-radius: 50%;
	border: 2px solid transparent;
	cursor: pointer;
}
.ar-swatch.on {
	border-color: var(--ink);
}
.ar-eraser {
	background: color-mix(in srgb, var(--panel) 70%, transparent);
	border-color: var(--line);
	display: inline-flex;
	align-items: center;
	justify-content: center;
}
/* the brush sizes: each button previews its width as a dot of that size */
.ar-sizes {
	display: inline-flex;
	align-items: center;
	gap: 6px;
}
.ar-size {
	flex: 0 0 26px;
	width: 26px;
	height: 26px;
	min-width: 0;
	min-height: 0;
	box-sizing: border-box;
	padding: 0;
	border-radius: 50%;
	border: 2px solid transparent;
	background: color-mix(in srgb, var(--panel) 70%, transparent);
	cursor: pointer;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}
.ar-size i {
	display: block;
	border-radius: 50%;
	background: var(--ink);
}
.ar-size.on {
	border-color: var(--ink);
}
.ar-swatches input[type="color"] {
	width: 26px;
	height: 26px;
	padding: 0;
	border: 1px solid var(--line);
	border-radius: 6px;
	background: none;
	cursor: pointer;
}
/* the art room's boxes set display:* above, so (per the .hidden source-order
   rule) their hidden state is re-declared AFTER them, here */
.ar-layer.hidden,
#arCatch.hidden,
.ar-hud.hidden {
	display: none;
}

/* ---- The gimmick dock (components/gimmick-dock.js): every OPEN gimmick
   gets an edge tab on the left of the screen — the host drawer's
   .doc-side-tab, mirrored — and only one panel is ever open: a tab click
   brings its HUD to the stage and folds the rest; the open tab folds its
   own. The toys stay out either way. */
.gk-tabs {
	position: fixed;
	left: 0;
	top: 50%;
	transform: translateY(-50%);
	z-index: 61;
	display: flex;
	flex-direction: column;
	gap: 8px;
	pointer-events: none;
}
.gk-tab {
	pointer-events: auto;
	border: 1px solid var(--panel-brd);
	border-left: 0;
	background: var(--panel);
	color: var(--ink);
	border-radius: 0 var(--r-md) 12px 0;
	padding: 12px 10px;
	cursor: pointer;
	box-shadow: var(--panel-glow);
	font-size: 0.95rem;
	line-height: 1;
}
.gk-tab:hover {
	transform: none; /* an edge tab stays put on hover */
}
.gk-tab:hover {
	background: var(--chip);
}
.gk-tab.on {
	background: var(--chip);
	border-color: color-mix(in srgb, var(--accent) 55%, var(--panel-brd));
}
/* the −/× in every HUD glass's top-right corner (hudCtlHtml) */
.gk-hud-ctl {
	position: absolute;
	top: 6px;
	right: 6px;
	display: flex;
	gap: 2px;
	z-index: 1;
}
.gk-hud-ctl button {
	width: 22px;
	height: 22px;
	min-width: 0;
	padding: 0;
	border: 0;
	border-radius: 6px;
	background: transparent;
	color: var(--muted);
	font-size: 15px;
	line-height: 1;
	cursor: pointer;
	box-shadow: none;
}
.gk-hud-ctl button:hover {
	transform: none; /* no global button lift inside a HUD */
	background: var(--chip);
	color: var(--ink);
}
/* ---- The SuperSoaker (components/super-soaker.js): a water gun over the
   live game. Same layer contract as the dice: fixed, z-60, no pointer —
   only my gun and the HUD glass take it. */
.sk-layer {
	position: fixed;
	inset: 0 0 var(--footbar-h) 0;
	z-index: 60;
	pointer-events: none;
	display: block;
}
/* the water pools on the milkshake's ground line — just above the foot bar,
   over the chat dock: the canvas is viewport-tall and overflows the layer,
   exactly like .ms-layer canvas */
.sk-layer canvas {
	position: absolute;
	inset: 0;
	pointer-events: none;
}
.sk-gunbtn {
	position: absolute;
	width: 64px;
	height: 44px;
	padding: 0;
	border: 0;
	background: none;
	transform: translate(-50%, -50%);
	pointer-events: auto;
	cursor: grab;
}
.sk-gunbtn.remote {
	pointer-events: none;
}
/* the global button:hover lift would replace the centring translate and
   throw the gun half a box away from the cursor: hover keeps it in place */
button.sk-gunbtn:hover {
	transform: translate(-50%, -50%);
}
.sk-gun3d {
	position: relative;
	display: block;
	width: 64px;
	height: 44px;
	transition: transform 0.15s ease-out;
}
/* the gun is the water-pistol emoji (green everywhere modern); the glyph
   points left and the component's setPos() mirrors/rotates it to the aim */
.sk-emoji {
	position: absolute;
	inset: 0;
	display: grid;
	place-items: center;
	font-size: 40px;
	line-height: 1;
	filter: drop-shadow(0 6px 8px rgba(0, 0, 0, 0.35));
}
.sk-tag {
	position: absolute;
	left: 50%;
	bottom: -16px;
	transform: translateX(-50%);
	font: 600 0.68rem/1 var(--font-body);
	text-shadow: 0 1px 4px rgba(0, 0, 0, 0.7);
	white-space: nowrap;
}
.sk-drop {
	position: absolute;
	left: 0;
	top: 0;
	width: 6px;
	height: 9px;
	border-radius: 50% 50% 60% 60%;
	opacity: 0;
	filter: blur(0.5px);
	pointer-events: none;
	will-change: transform, opacity;
}
.sk-hud {
	position: absolute;
	bottom: 12px;
	left: 16px;
	pointer-events: auto;
	display: flex;
	flex-direction: column;
	gap: 6px;
	padding: 10px 14px;
	max-width: min(380px, calc(100vw - 32px));
	border-radius: var(--r-md);
	background: color-mix(in srgb, var(--panel) 42%, transparent);
	border: 1px solid color-mix(in srgb, var(--panel-brd) 70%, transparent);
	backdrop-filter: blur(6px) saturate(1.1);
	-webkit-backdrop-filter: blur(6px) saturate(1.1);
	box-shadow: var(--panel-glow);
	font: 500 0.8rem/1.3 var(--font-body);
	color: var(--muted);
}
.sk-title {
	font: 700 1rem/1.2 var(--font-display);
	color: var(--ink);
}
.sk-row {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
}

/* ---- Vecna's Curse (components/vecna-curse.js): the toy aimed at a
   person. The veil greys the VICTIM's page under red mist — pointer-events
   none, nothing blocked — while their chips pulse red on every screen. */
.vcx-layer {
	position: fixed;
	inset: 0 0 var(--footbar-h) 0;
	z-index: 60;
	pointer-events: none;
	display: block;
}
.vcx-veil {
	position: fixed;
	inset: 0;
	pointer-events: none;
	display: block;
	backdrop-filter: grayscale(0.85) brightness(0.8);
	-webkit-backdrop-filter: grayscale(0.85) brightness(0.8);
	background: radial-gradient(
		120% 120% at 50% 50%,
		transparent 42%,
		rgba(140, 10, 16, 0.42) 82%,
		rgba(90, 4, 8, 0.62) 100%
	);
	animation: vcx-close-in 1.6s ease-out;
}
@keyframes vcx-close-in {
	from {
		opacity: 0;
	}
	to {
		opacity: 1;
	}
}
.vcx-mote {
	position: absolute;
	bottom: -14px;
	width: 7px;
	height: 7px;
	border-radius: 2px;
	background: rgba(200, 170, 150, 0.35);
	animation: vcx-rise 7s linear infinite;
}
.vcx-mote.m1 {
	left: 12%;
	animation-delay: 0s;
}
.vcx-mote.m2 {
	left: 30%;
	animation-delay: 1.4s;
	width: 10px;
	height: 10px;
}
.vcx-mote.m3 {
	left: 47%;
	animation-delay: 2.8s;
}
.vcx-mote.m4 {
	left: 62%;
	animation-delay: 0.9s;
	width: 5px;
	height: 5px;
}
.vcx-mote.m5 {
	left: 78%;
	animation-delay: 2.1s;
}
.vcx-mote.m6 {
	left: 90%;
	animation-delay: 3.5s;
	width: 9px;
	height: 9px;
}
@keyframes vcx-rise {
	from {
		transform: translateY(0) rotate(0deg);
		opacity: 0;
	}
	12% {
		opacity: 1;
	}
	to {
		transform: translateY(-105vh) rotate(200deg);
		opacity: 0;
	}
}
.vcx-word {
	position: absolute;
	left: 50%;
	bottom: 14%;
	transform: translateX(-50%);
	font: 700 1.1rem/1 var(--font-mono);
	letter-spacing: 0.3em;
	color: rgba(255, 120, 120, 0.8);
	text-shadow: 0 0 18px rgba(255, 40, 40, 0.6);
}
.vcx-hud {
	position: absolute;
	bottom: 12px;
	left: 16px;
	pointer-events: auto;
	display: flex;
	flex-direction: column;
	gap: 6px;
	padding: 10px 14px;
	max-width: min(380px, calc(100vw - 32px));
	border-radius: var(--r-md);
	background: color-mix(in srgb, var(--panel) 42%, transparent);
	border: 1px solid color-mix(in srgb, var(--panel-brd) 70%, transparent);
	backdrop-filter: blur(6px) saturate(1.1);
	-webkit-backdrop-filter: blur(6px) saturate(1.1);
	box-shadow: var(--panel-glow);
	font: 500 0.8rem/1.3 var(--font-body);
	color: var(--muted);
}
.vcx-title {
	font: 700 1rem/1.2 var(--font-display);
	color: var(--ink);
}
.vcx-row {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
}
/* the target picker is a LIST: one writer per row, full width */
#vcxTargets {
	flex-direction: column;
	align-items: stretch;
	gap: 4px;
	max-height: 40vh;
	overflow-y: auto;
}
.vcx-target {
	font-weight: 700;
	text-align: left;
	width: 100%;
	padding: 8px 12px;
	border: 1px solid var(--line);
	border-radius: 8px;
	background: color-mix(in srgb, var(--panel-2) 60%, transparent);
}
.vcx-target:not(:disabled):hover {
	border-color: rgba(220, 40, 40, 0.6);
	background: rgba(120, 12, 16, 0.25);
}
.vcx-target:disabled {
	opacity: 0.45;
	cursor: default;
}
/* the TAKEN page runs backwards: the whole document mirrors, text and all —
   the Upside Down's own reading order. Cosmetic: hit-testing follows the
   transform, so every click still lands where it looks like it should. */
html.vcx-taken body {
	transform: scaleX(-1);
}
/* the veil's own instruction stays readable — it's the way out */
html.vcx-taken .vcx-word {
	transform: translateX(-50%) scaleX(-1);
}
/* the victim, seen from outside: their chips pulse red on every screen
   (doubled class names outrank .player-chip.now's border) */
.player-chip.vcx-cursed,
[data-user].vcx-cursed.vcx-cursed {
	animation: vcx-pulse 1.2s ease-in-out infinite alternate;
	border-color: rgba(220, 40, 40, 0.7);
}
@keyframes vcx-pulse {
	from {
		box-shadow: 0 0 4px rgba(220, 40, 40, 0.3);
	}
	to {
		box-shadow: 0 0 14px rgba(220, 40, 40, 0.75);
	}
}
/* the vecna theme's own clock is the tell: while a curse is live the
   pendulum hurries and the dimmed clockface sky glows red */
html.vcx-live[data-theme="vecna"] .clockface {
	filter: drop-shadow(0 0 26px rgba(220, 30, 30, 0.5)) brightness(1.2);
}
html.vcx-live[data-theme="vecna"] #vc-pend {
	animation-duration: 0.9s;
}

/* both set display:* above, so (the .hidden source-order rule) hidden and
   folded states are re-declared AFTER them, here */
.gk-tab.hidden,
.gd-hud.gk-minned,
.gg-hud.gk-minned,
.ms-hud.gk-minned,
.db-hud.gk-minned,
.ar-hud.gk-minned,
.sk-hud.gk-minned,
.vcx-hud.gk-minned,
.sk-layer.hidden,
.sk-gunbtn.hidden,
.sk-hud.hidden,
.vcx-layer.hidden,
.vcx-veil.hidden,
.vcx-hud.hidden,
.head-chip.hidden {
	display: none;
}

/* ---- Ranks & Unlocks (/ranks, public/js/ranks-view.js): the whole ladder,
   theme screenshots, gimmick rundowns and word badges on one page. All of it
   is data-driven; these styles only give the data a shape. */
.rk-section {
	margin: 26px 0 10px;
	text-align: left;
	font-family: var(--font-display);
}
.rk-progress {
	margin: 6px 0 2px;
}
.rk-progress-line {
	margin: 0 0 6px;
	text-align: left;
}
.rk-bar {
	max-width: 420px;
}
.rk-ladder {
	display: grid;
	gap: 12px;
}
.rk-tier {
	text-align: left;
	padding: 14px 16px;
	border-radius: var(--r-md);
	border: 1px solid var(--line);
	background: color-mix(in srgb, var(--panel) 55%, transparent);
	opacity: 0.82;
}
.rk-tier.earned {
	opacity: 1;
	border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
}
.rk-tier.current {
	box-shadow:
		0 0 0 1px color-mix(in srgb, var(--accent) 60%, transparent),
		var(--panel-glow);
}
.rk-tier-head {
	display: flex;
	align-items: baseline;
	gap: 10px;
	flex-wrap: wrap;
}
.rk-badge {
	font-size: 1.05rem;
}
.rk-words {
	color: var(--muted);
	font-size: 0.85rem;
}
.rk-earned {
	margin-left: auto;
	color: var(--accent);
	font-size: 0.8rem;
	font-weight: 700;
	flex: none;
}
.rk-desc {
	margin: 6px 0 0;
	color: var(--muted);
	font-size: 0.9rem;
}
/* theme screenshots: small cards, the shot on top, the label under it */
.rk-themes {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
	margin-top: 10px;
}
.rk-theme {
	margin: 0;
	width: 190px;
	border: 1px solid var(--line);
	border-radius: 10px;
	overflow: hidden;
	background: color-mix(in srgb, var(--panel) 70%, transparent);
	cursor: pointer;
}
.rk-theme img {
	display: block;
	width: 100%;
	aspect-ratio: 16 / 9;
	object-fit: cover;
}
/* no screenshot shipped (a fandom pack without images): the card stays,
   just label-only on a swatch of the page's own panel colour */
.rk-theme.noshot::before {
	content: "";
	display: block;
	aspect-ratio: 16 / 9;
	background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 30%, var(--panel)), var(--panel));
}
.rk-theme figcaption {
	padding: 6px 9px;
	font-size: 0.78rem;
	color: var(--muted);
}
.rk-theme.unlocked figcaption {
	color: var(--ink);
}
.rk-gimmicks {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin-top: 10px;
}
.rk-gimmick-chip {
	padding: 4px 10px;
	border-radius: 999px;
	border: 1px solid var(--line);
	font-size: 0.8rem;
	color: var(--muted);
}
.rk-gimmick-chip.unlocked {
	color: var(--ink);
	border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
}
.rk-free {
	margin-top: 0;
}
/* the gimmick rundown: one card per toy */
.rk-gimmick-grid,
.rk-usage-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
	gap: 12px;
}
.rk-gimmick,
.rk-usage {
	text-align: left;
	padding: 12px 14px;
	border-radius: var(--r-md);
	border: 1px solid var(--line);
	background: color-mix(in srgb, var(--panel) 55%, transparent);
}
.rk-gimmick.earned,
.rk-usage.earned {
	border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
}
.rk-gimmick header {
	display: flex;
	align-items: center;
	gap: 8px;
}
.rk-gicon {
	font-size: 1.3rem;
}
.rk-gate {
	margin: 8px 0 0;
	font-size: 0.78rem;
	color: var(--muted);
}
.rk-usage b {
	font-size: 0.95rem;
}
.rk-usage p {
	margin: 6px 0 0;
	font-size: 0.85rem;
	color: var(--muted);
}
.rk-usage.mystery p {
	font-style: italic;
}
.rk-usage .rk-earned {
	display: inline-block;
	margin: 8px 0 0;
}
.rk-signin {
	text-align: left;
}

/* Advanced-prompt clauses open "Category: choice"; promptHtml (util.js) wraps
   the category name so each one wears the same colour everywhere a prompt is
   shown — banner, ballot, archive. */
.prompt-grid {
	display: grid;
	grid-template-columns: max-content 1fr;
	gap: 2px 10px;
	align-items: baseline;
	text-align: left;
	white-space: normal;
	text-indent: 0;
	padding-left: 0;
}
/* a wide card carries the choices in two column pairs */
@media (min-width: 720px) {
	.option .prompt-grid,
	.prompt-banner .prompt-grid {
		grid-template-columns: max-content 1fr max-content 1fr;
	}
}
.pc {
	font-weight: 700;
	cursor: help;
	font-size: 0.82em;
	letter-spacing: 0.02em;
	text-transform: uppercase;
}
.pc-val {
	min-width: 0;
}
.pc-season {
	color: #e0a84a;
}
.pc-canon {
	color: #7fb3ff;
}
.pc-place {
	color: #6fd39a;
}
.pc-relationship {
	color: #ff8fb1;
}
.pc-situation {
	color: #c9a0ff;
}
.pc-trope {
	color: #5fd0d6;
}
.pc-tone {
	color: #ffd166;
}
.pc-rating {
	color: #ff6b6b;
}
.pc-kinks {
	color: #ff9e7a;
}

/* The admin prompt-library editor: one <details> per pool, rows of inputs. */
.pe-pool {
	border: 1px solid var(--line);
	border-radius: var(--r-md);
	padding: 8px 12px;
	margin: 0 0 8px;
	background: var(--panel-2);
	text-align: left;
}
.pe-pool summary {
	cursor: pointer;
	font-weight: 700;
}
.pe-pool .subtle {
	margin: 6px 0 8px;
	font-size: 0.8rem;
}
.pe-table {
	width: 100%;
	border-collapse: collapse;
	font-size: 0.85rem;
}
.pe-table th {
	text-align: left;
	font-weight: 600;
	color: var(--muted);
	padding: 2px 6px 6px;
}
.pe-table td {
	padding: 2px 4px;
	vertical-align: middle;
}
.pe-table input,
.pe-table select {
	width: 100%;
	font: inherit;
	font-size: 0.85rem;
	padding: 4px 6px;
}
.pe-table td:nth-child(2) {
	width: 72px;
}
.pe-table td:last-child {
	width: 36px;
}
.pe-rules {
	font-family: var(--font-mono);
	font-size: 0.78rem !important;
}
.pe-lines {
	width: 100%;
	font: inherit;
	font-size: 0.85rem;
	line-height: 1.45;
}
.pe-add {
	margin-top: 8px;
}
.pe-foot {
	display: flex;
	align-items: center;
	gap: 12px;
	margin: 12px 0 0;
}
.pe-doc h4 {
	margin: 12px 0 4px;
	font-size: 0.9rem;
}
.pe-doc-table {
	border-collapse: collapse;
	font-size: 0.82rem;
	width: 100%;
}
.pe-doc-table td {
	padding: 3px 8px 3px 0;
	vertical-align: top;
	border-top: 1px solid var(--line);
}
.pe-doc-table td:first-child {
	white-space: nowrap;
	width: 1%;
}
.pe-doc-list {
	font-size: 0.85rem;
	padding-left: 20px;
	margin: 4px 0;
}
.pe-doc code {
	font-family: var(--font-mono);
	font-size: 0.8em;
}
/* the host's per-option reroll, in the card's corner opposite the vote count */
.option {
	position: relative;
}
/* the host's per-option reroll sits BESIDE the vote count, on the same
   centred line to its left, never over it (a one-line Simple card put the two
   in the same corner); the card that carries it pads its right for both */
.opt-reroll {
	position: absolute;
	top: 50%;
	right: 84px;
	transform: translateY(-50%);
	padding: 4px 10px;
	border-radius: var(--r-sm, 6px);
	border: 1px solid var(--line);
	background: var(--panel-2);
	color: var(--muted);
	font: inherit;
	font-size: 0.8rem;
	line-height: 1.2;
	cursor: pointer;
	white-space: nowrap;
	text-indent: 0;
}
.option.has-reroll {
	padding-right: 176px;
}
.opt-remove:hover {
	color: var(--accent);
	border-color: var(--accent);
}
.opt-reroll:hover {
	color: var(--ink);
	border-color: var(--chip-brd);
}
.guided-controls .pm-reroll {
	align-self: end;
}
/* the writers' reference editor on /admin: one block per category */
.re-cat {
	margin: 0 0 10px;
}
.re-cat-head {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 0 0 4px;
	font-size: 0.85rem;
}
.re-cat-head .re-del {
	margin-left: auto;
	padding: 2px 8px;
	font-size: 0.8rem;
}
.re-addrow {
	display: flex;
	gap: 8px;
	align-items: center;
	margin: 6px 0;
}
.re-addrow input {
	flex: 1;
	font: inherit;
	font-size: 0.85rem;
	padding: 6px 8px;
}
/* AU worlds on /admin: a head row of label · weight · rules, then two place lists */
.au-head {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
	align-items: end;
	margin: 6px 0 8px;
	font-size: 0.85rem;
}
.au-head label {
	display: flex;
	flex-direction: column;
	gap: 2px;
}
.au-head input {
	font: inherit;
	font-size: 0.85rem;
	padding: 4px 6px;
}
.au-head label:nth-child(3) {
	flex: 1;
	min-width: 220px;
}
.au-head .au-del {
	margin-left: auto;
}
.au-lbl {
	display: block;
	font-size: 0.8rem;
	color: var(--muted);
	margin: 8px 0 3px;
}
.au-world {
	margin-left: 0;
}

/* /admin accounts: a search row over a list that scrolls in place once it
   outgrows the box, so a long directory never pushes the prompt library
   off the bottom of the page. */
.admin-search {
	gap: 10px;
	align-items: center;
	margin: 0 0 6px;
}
.admin-search input {
	flex: 1;
	min-width: 200px;
	margin: 0;
}
.admin-search .gc-meta {
	flex: none;
}
.admin-scroll {
	max-height: 60vh;
	overflow-y: auto;
	overscroll-behavior: contain;
	padding-right: 4px;
}

/* /announcements: the admin's blog — one article per post, the shared
   WYSIWYG composer (admin only) above the list. The post's own first
   heading is its title, so the head row is just the date and Delete. */
.ann-composer {
	margin: 0 0 22px;
	padding: 12px 14px;
	border: 1px solid var(--line);
	border-radius: 14px;
}
.ann-editor {
	min-height: 180px;
	width: 100%;
	font-family: var(--font-mono);
	resize: vertical;
}
.ann-editbox {
	margin-top: 12px;
	padding: 12px 14px;
	border: 1px solid var(--line);
	border-radius: 14px;
}
.ann-editbox.hidden {
	display: none;
}
.ann-edited {
	margin-left: 8px;
	font-style: italic;
}
/* Each post sits on the story surface, like a story line: readable over any theme's background. */
.ann-post {
	padding: 1rem;
	margin: 0 0 14px;
	background: var(--story-bg);
	border-radius: 12px;
}
/* on a desktop column the text needs air from the panel edge: 1rem reads as
   hugging once the line is 800px long */
@media (min-width: 700px) {
	.ann-post {
		padding: 1.4rem 1.8rem;
	}
}
.ann-head {
	display: flex;
	align-items: baseline;
	gap: 12px;
	margin: 0 0 6px;
}
.ann-head .ann-acts {
	flex: none;
	margin-left: auto;
	display: flex;
	gap: 6px;
}
.ann-head .ann-acts button {
	padding: 6px 12px;
	font-size: 0.85rem;
	white-space: nowrap;
	min-width: auto;
	width: auto;
}
.ann-body {
	text-align: left;
}
.ann-body :is(h1, h2, h3) {
	margin: 0 0 0.4em;
}
.ann-body p {
	margin: 0 0 0.8em;
}
.ann-body > :last-child {
	margin-bottom: 0;
}

/* /ranks, admin only: the trigger words under a badge's description */
.rk-usage .rk-recipe {
	font-size: 0.8rem;
	color: var(--muted);
	margin: 4px 0 0;
}

/* dashboard: the newest announcement, one compact card above Welcome */
.ann-card {
	padding: 12px 16px;
}
.ann-glimpse {
	display: grid;
	gap: 4px;
	text-align: left;
}
.ann-glimpse-tag {
	font-size: 0.75rem;
	color: var(--muted);
	letter-spacing: 0.02em;
}
.ann-glimpse-title {
	font-size: 1.02rem;
}
.ann-glimpse-text {
	margin: 0;
	color: var(--muted);
	font-size: 0.9rem;
	line-height: 1.4;
}
.ann-glimpse-more {
	font-size: 0.85rem;
	font-weight: 600;
	white-space: nowrap;
	margin-left: 0.4em;
}

/* /games: a coming-soon placeholder until there is something to play */
.soon-card {
	text-align: center;
	padding: 48px 24px;
}
.soon-mark {
	font-size: 3rem;
	margin: 0 0 8px;
}
.soon-title {
	font-family: var(--font-display);
	font-size: 1.8rem;
	margin: 0 0 8px;
}
.soon-sub {
	margin: 0;
}
.dnav-tag {
	margin-left: auto;
	font-size: 0.7rem;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--muted);
	border: 1px solid var(--line);
	border-radius: 99px;
	padding: 1px 7px;
}

/* Announcement border (components/tendril-border.js): the card's frame
   draws itself in the theme's accent, then a gradient of accent-2 and ink
   sweeps along the stroke while the glow flashes with it. */
/* the card itself is only the frame's box (the stroke sits 1px in); the content
   block carries the panel colour and its own padding, rounded to the frame's
   inner radius and clipped, so the colour never shows outside the stroke */
.card.tendril {
	position: relative;
	isolation: isolate;
	border-radius: 14px;
	border-color: transparent;
	background: transparent;
	padding: 2px;
	overflow: hidden;
}
.card.tendril > *:not(.vine) {
	position: relative;
	z-index: 1;
	background: var(--panel);
	padding: 1rem;
	border-radius: 12px;
	overflow: hidden;
}
.card.tendril > .vine {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	overflow: visible;
	pointer-events: none;
	z-index: 0;
}
.vine .vine-line {
	fill: none;
	stroke: var(--accent);
	stroke-width: 1.4;
	stroke-linecap: round;
}
.vine .vine-glow {
	fill: none;
	stroke: var(--accent);
	stroke-width: 3;
	stroke-linecap: round;
	opacity: 0;
	filter: blur(5px);
}
.vine .vine-shine {
	fill: none;
	stroke-width: 1.8;
	stroke-linecap: round;
	opacity: 0;
}
@media (min-width: 700px) {
	.card.tendril > *:not(.vine) {
		padding: 1.25rem 1.6rem;
	}
	.card.tendril > .vine {
		margin-left: -0.5rem;
	}
}
.ann-glimpse-tag {
	display: flex;
	align-items: center;
	gap: 6px;
}

/* a guided knob's label row: the name and, beside it, the checkbox that
   leaves that part out of the prompt entirely */
.guided-controls .pm-lab {
	display: inline-flex;
	align-items: center;
	gap: 6px;
}
/* a native checkbox with no appearance reset inherits the site's input
   styling (padding, border-radius) and its mark drifts off-centre; drawn
   from scratch it is a box with the mark centred by grid placement */
.guided-controls .pm-off {
	appearance: none;
	-webkit-appearance: none;
	margin: 0;
	padding: 0;
	width: 14px;
	height: 14px;
	flex: none;
	border: 1px solid var(--line);
	border-radius: 3px;
	background: var(--panel);
	display: inline-grid;
	place-content: center;
	cursor: pointer;
	vertical-align: middle;
}
.guided-controls .pm-off::before {
	content: "";
	width: 8px;
	height: 8px;
	border-radius: 2px;
	background: var(--accent);
	transform: scale(0);
	transition: transform 0.12s ease;
}
.guided-controls .pm-off:checked {
	border-color: var(--accent);
}
.guided-controls .pm-off:checked::before {
	transform: scale(1);
}
.guided-controls .pm-off:focus-visible {
	outline: 2px solid var(--accent-2);
	outline-offset: 2px;
}
.guided-controls select:disabled {
	opacity: 0.45;
}

/* the solo editor's prompt roller: the picker's knobs wrap inside the card,
   the rolled scenario reads like a ballot card, Insert appears with it */
.prompt-roll-card {
	width: min(720px, calc(100vw - 32px));
	max-width: none;
	max-height: calc(100vh - 32px);
	overflow-y: auto;
	text-align: left;
	box-sizing: border-box;
}
/* the rolled card is a single list of category | value rows, whatever the
   width (the ballot's two-pair layout would overflow the modal), and a long
   value wraps rather than pushing the card wider */
.prompt-roll-card .prompt-roll-result .prompt-grid {
	grid-template-columns: max-content 1fr;
}
.prompt-roll-card .prompt-roll-result {
	white-space: normal;
	overflow-wrap: anywhere;
	min-width: 0;
	max-width: 100%;
	text-indent: 0;
	padding: 14px 16px;
}
.prompt-roll-card .prompt-roll-result .pc-val {
	overflow-wrap: anywhere;
}
.prompt-roll-card .guided-controls {
	margin-top: 8px;
}
.prompt-roll-row {
	align-items: center;
	gap: 12px;
	margin: 12px 0 0;
}
.prompt-roll-result {
	display: block;
	margin: 12px 0 0;
	cursor: default;
}
.prompt-roll-card .share-foot {
	justify-content: flex-end;
	gap: 8px;
}

/* the kink count beside the Kink menu: a narrow select on the same row */
.guided-controls label:has(> .pm-count) {
	display: grid;
	grid-template-columns: 1fr auto;
	column-gap: 6px;
}
.guided-controls label:has(> .pm-count) > .pm-lab {
	grid-column: 1 / -1;
}
.guided-controls .pm-count {
	width: 3.4em;
	min-width: 0;
	padding-left: 6px;
	padding-right: 6px;
}

/* iOS Safari zooms the page in when a field under 16px takes focus — and the
   zoom outlives the page, so the next route opens "zoomed in". On touch
   devices every field is at least 16px, so focusing one never zooms; people
   who WANT to zoom still can (no maximum-scale). */
@media (hover: none) and (pointer: coarse) {
	input,
	select,
	textarea,
	[contenteditable="true"] {
		font-size: max(1em, 16px);
	}
}

/* ---- the scoreboard of a non-friendly game: a words chip on each player
   and a ranked list on the reveal ---- */
.player-chip .chip-words {
	font-family: var(--font-mono);
	font-size: 0.7rem;
	color: var(--muted);
	background: var(--chip);
	padding: 1px 6px;
	border-radius: 999px;
}
.scoreboard {
	display: flex;
	flex-direction: column;
	gap: 6px;
	margin: 0 0 14px;
	padding: 12px 14px;
	border: 1px solid var(--panel-brd);
	border-radius: var(--r-md);
	background: var(--chip);
}
.scoreboard .sb-title {
	font-size: 0.78rem;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--muted);
}
.scoreboard .sb-row {
	display: flex;
	align-items: center;
	gap: 8px;
}
.scoreboard .sb-row.lead {
	font-weight: 700;
}
.scoreboard .sb-rank {
	width: 1.4em;
	color: var(--muted);
	font-family: var(--font-mono);
}
.scoreboard .sb-words {
	margin-left: auto;
	font-family: var(--font-mono);
	font-size: 0.8rem;
	color: var(--muted);
}

/* The archive's story page sits on the story surface, like the game's column. */
.arch-detail {
	background: var(--story-bg);
	padding: 1rem;
	border-radius: 12px;
}
.arch-detail.hidden {
	display: none;
}

/* ---- Only the worn theme's background exists ------------------------------
   Every theme's .bg-set is mounted on every page (chrome.js). An inactive set
   at opacity: 0 is still LAID OUT, COMPOSITED and ANIMATED — ~60 infinite
   animations, dozens of blur filters, mix-blend layers and a 420vh tower for
   themes nobody is wearing — which is what made iOS Safari/Chrome kill the
   tab. display: none takes them out of the render tree entirely; the theme
   swap becomes a cut instead of a 0.7s cross-fade, which is the right trade. */
.bg-set {
	display: none;
}
[data-theme="arcade"] .bg-set.arcade,
[data-theme="aurora"] .bg-set.aurora,
[data-theme="bunker"] .bg-set.bunker,
[data-theme="castlebyers"] .bg-set.castlebyers,
[data-theme="cerebro"] .bg-set.cerebro,
[data-theme="cleradin"] .bg-set.cleradin,
[data-theme="clouds"] .bg-set.clouds,
[data-theme="hawkinslab"] .bg-set.hawkinslab,
[data-theme="hellfire"] .bg-set.hellfire,
[data-theme="ink"] .bg-set.ink,
[data-theme="neon"] .bg-set.neon,
[data-theme="rink"] .bg-set.rink,
[data-theme="snowball"] .bg-set.snowball,
[data-theme="starcourt"] .bg-set.starcourt,
[data-theme="upside"] .bg-set.upside,
[data-theme="vecna"] .bg-set.vecna,
[data-theme="video"] .bg-set.video,
[data-theme="void"] .bg-set.void,
[data-theme="wall"] .bg-set.wall {
	display: block;
}

/* On touch devices even the worn theme stays lighter: backdrop blur is the
   single most expensive effect on a mobile GPU (re-blurs whatever scrolls
   beneath it every frame), and the ambient set drops to a still picture. */
@media (hover: none) and (pointer: coarse) {
	.bg-set *,
	.bg-set {
		animation: none !important;
	}
	.bg-set * {
		filter: none !important;
	}
	* {
		backdrop-filter: none !important;
		-webkit-backdrop-filter: none !important;
	}
}

/* Ranks & unlocks: every card sits on the story surface, like a story line
   and an announcement, so the text reads over any theme's background. */
.rk-tier,
.rk-gimmick,
.rk-usage,
.rk-progress {
	background: var(--story-bg);
	padding: 1rem;
	border-radius: 12px;
}

/* Signup: the Byler Offscreen membership line — the site's checkline, the
   box aligned to the first line of a question that may wrap. */
.su-member {
	display: flex;
	align-items: flex-start;
	gap: 8px;
	text-align: left;
	font-size: 0.9rem;
	cursor: help;
}
.su-member input[type="checkbox"] {
	flex: none;
	margin-top: 3px;
}

/* announcement embed images + the composer's url list */
.ann-images { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
.ann-images img { max-width: 100%; max-height: 360px; border-radius: 10px; display: block; }
.ann-imgs { margin-top: 10px; }
.ann-imgs-head { display: flex; align-items: center; gap: 8px; }
.ann-img-row { display: flex; gap: 6px; margin-top: 6px; align-items: center; }
.ann-img-row input { flex: 1; }

/* Gradient emojis, like an <h1>'s: the theme gradient fills the glyph. Used on
   nav/dashboard/rail emojis — pure-emoji icon spans get it directly, and a
   leading emoji beside a label is wrapped in .emoji-grad by gradEmojisIn(). */
.emoji-grad,
.dnav-ico,
.dtab-ico {
	background: var(--grad-1);
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-text-fill-color: transparent;
	color: transparent;
}

/* archive: read-only tag chips beside a borderless ＋ that opens the tag modal */
.arch-tagrow { display: flex; align-items: center; justify-content: flex-start; gap: 6px; flex-wrap: wrap; margin: 0 0 10px; text-align: left; }
.arch-tagrow .tag-chips { display: inline-flex; flex-wrap: wrap; gap: 6px; margin: 0; width: auto; }
.arch-tagrow .tag-edit,
.arch-tagrow .tag-edit:hover { all: unset; display: inline-block; cursor: pointer; font-size: 15px; line-height: 1; padding: 2px 4px; color: var(--accent-2); opacity: 0.8; transform: none; }
.arch-tagrow .tag-edit:hover { opacity: 1; }
#tagModal .tag-label { display: none; }
