/**
 * sonnydev.online - public site.
 *
 * Design language: "liquid glass / neon gradient" - deep near-black canvas, an
 * ambient constellation mesh behind everything (mesh.js), frosted-glass panels,
 * one signature purple->blue gradient, colored glows for depth, pill geometry,
 * and hover states that lift. Recolored from the reference's pink/orange to a
 * violet/sky ramp; the exact substitution table lives in the redesign brief.
 *
 * Three rules hold it together:
 *
 *   1. **The gradient is the brand.** It appears on primary buttons, branded
 *      headings, stat numbers, badges, avatar rings, icon tiles, the nav
 *      underline and active toggle tracks - and nowhere else. Body copy and
 *      small labels never wear it.
 *
 *   2. **Glass, not boxes.** Surfaces are translucent fills with hairline
 *      borders and backdrop blur. Depth comes from colored box-shadows, not
 *      from neutral black drop shadows.
 *
 *   3. **Monospace is still structure.** IBM Plex Mono stays for IDs, labels,
 *      timestamps and status - the one deliberate divergence from the
 *      reference aesthetic, kept because it is genuinely good here.
 *
 * Fonts are self-hosted in /fonts - no third-party request on page load.
 */

/* --- faces --------------------------------------------------------------- */

@font-face {
	font-family: 'Outfit';
	src: url('/fonts/outfit-500.woff2') format('woff2');
	font-weight: 500;
	font-display: swap;
}

@font-face {
	font-family: 'Outfit';
	src: url('/fonts/outfit-600.woff2') format('woff2');
	font-weight: 600;
	font-display: swap;
}

@font-face {
	font-family: 'Outfit';
	src: url('/fonts/outfit-700.woff2') format('woff2');
	font-weight: 700;
	font-display: swap;
}

@font-face {
	font-family: 'Inter';
	src: url('/fonts/inter-400.woff2') format('woff2');
	font-weight: 400;
	font-display: swap;
}

@font-face {
	font-family: 'Inter';
	src: url('/fonts/inter-500.woff2') format('woff2');
	font-weight: 500;
	font-display: swap;
}

@font-face {
	font-family: 'Inter';
	src: url('/fonts/inter-600.woff2') format('woff2');
	font-weight: 600;
	font-display: swap;
}

@font-face {
	font-family: 'IBM Plex Mono';
	src: url('/fonts/plexmono-400.woff2') format('woff2');
	font-weight: 400;
	font-display: swap;
}

@font-face {
	font-family: 'IBM Plex Mono';
	src: url('/fonts/plexmono-500.woff2') format('woff2');
	font-weight: 500;
	font-display: swap;
}

/* --- tokens -------------------------------------------------------------- */

:root {
	/* Tells the browser to render NATIVE form controls (select option popups,
	   date pickers, scrollbars) in dark mode too - without this, a <select>'s
	   own styling is dark but the OS-drawn dropdown popup it opens defaults to
	   light, which is unreadable against the site's palette. */
	color-scheme: dark;

	/* Surfaces */
	--bg: #070912;
	--bg-raised: #0d1020;
	--bg-sunken: #04050c;
	--bg-hover: #151a33;
	--border: #1d2240;
	--border-soft: #141833;

	/* Glass */
	--glass: rgba(255, 255, 255, 0.045);
	--glass-strong: rgba(255, 255, 255, 0.075);
	--glass-border: rgba(255, 255, 255, 0.08);
	--glass-border-hi: rgba(255, 255, 255, 0.16);
	--glass-blur: blur(22px);
	--glass-blur-sm: blur(12px);

	/* Brand ramp (purple -> blue) */
	--brand-1: #a855f7;
	--brand-2: #7c5cff;
	--brand-3: #5b8def;
	--brand-4: #38bdf8;

	--accent: var(--brand-2);
	--accent-dim: #5a45c4;
	--accent-wash: rgba(124, 92, 255, 0.10);

	/* Gradients */
	/* Composed from the 4 brand-N variables above rather than hardcoded a
	   second time - a Branding-panel color change (public/home.js sets
	   --brand-1..4 as inline overrides once fetched) cascades into both
	   gradients automatically instead of only flat var(--brand-N) uses. */
	--grad: linear-gradient(135deg, var(--brand-1) 0%, var(--brand-2) 22%, var(--brand-3) 55%, var(--brand-4) 100%);
	--grad-90: linear-gradient(90deg, var(--brand-1), var(--brand-2) 40%, var(--brand-3) 70%, var(--brand-4));
	--grad-sheen: linear-gradient(135deg, rgba(255, 255, 255, 0.30), transparent);

	/* Glow */
	--glow-sm: 0 4px 15px rgba(124, 92, 255, 0.30);
	--glow-md: 0 8px 25px rgba(124, 92, 255, 0.40);
	--glow-lg: 0 12px 40px rgba(124, 92, 255, 0.35);
	--glow-xl: 0 25px 70px rgba(124, 92, 255, 0.50), 0 0 80px rgba(56, 189, 248, 0.20), inset 0 0 60px rgba(56, 189, 248, 0.10);

	/* Text */
	--text: #e8ecf6;
	--text-dim: #8f97b2;
	--text-mute: #5d6584;

	/* Status only. Never brand colours. */
	--ok: #3ecf8e;
	--warn: #f2a93b;
	--bad: #e5484d;

	--font-display: 'Outfit', system-ui, sans-serif;
	--font-body: 'Inter', system-ui, sans-serif;
	--font-mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;

	--fs-mono: 0.75rem;
	--fs-sm: 0.875rem;
	--fs-body: 1rem;
	--fs-lead: 1.125rem;
	--fs-h3: 1.375rem;
	--fs-h2: 2rem;
	--fs-h1: 2.75rem;
	--fs-hero: clamp(2.25rem, 5.5vw, 3.75rem);

	/* Geometry: rounded. Pills, panels and buttons each get their own step. */
	--radius: 12px;
	--radius-lg: 16px;
	--radius-sm: 8px;
	--radius-pill: 999px;
	--maxw: 1200px;
	--gutter: 26px;

	/* Motion */
	--t-fast: 0.25s ease;
	--t-base: 0.3s ease;
	--t-slow: 0.5s ease;

	--primary: var(--accent);
}

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

body {
	margin: 0;
	min-height: 100vh;
	min-height: 100dvh;
	display: flex;
	flex-direction: column;
	background: var(--bg);
	color: var(--text);
	font-family: var(--font-body);
	font-size: var(--fs-body);
	line-height: 1.65;
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

/* Belt-and-suspenders with color-scheme:dark above - some browsers only fully
   dark-theme a <select>'s native option popup when the options themselves
   carry explicit colors, not just the parent. */
option {
	background: var(--bg-raised);
	color: var(--text);
}

h1,
h2,
h3 {
	font-family: var(--font-display);
	font-weight: 700;
	letter-spacing: -0.02em;
	line-height: 1.15;
	margin: 0;
}

a {
	color: var(--accent);
	text-decoration: none;
}

a:hover {
	text-decoration: underline;
}

/* Every focusable thing gets the same visible ring. Never removed. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
	outline: 2px solid var(--brand-2);
	outline-offset: 2px;
}

/* Anything that is data rather than prose. */
.mono {
	font-family: var(--font-mono);
	font-size: var(--fs-mono);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-mute);
}

.muted {
	color: var(--text-dim);
}

.loading {
	padding: 60px 0;
	font-family: var(--font-mono);
	font-size: var(--fs-mono);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	text-align: center;
	color: var(--text-mute);
}

/* The generic whole-page entrance for routes with no animation of their own
   (Account, Support, FAQ, Terms, Privacy, 404) - toggled onto #view itself
   by render() in home.js on every navigation. Same grid-card-in keyframe
   reused as-is, just on the page's outer container instead of a grid of
   cards. */
.view-transition {
	animation: grid-card-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) backwards;
}

/* Branded headings. Guard: never applied below 16px text. */
.grad-text {
	background: var(--grad);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

/* The small pill label that opens a section or a hero. Solid accent rather
   than gradient text - it is under 14px, where gradient fills go illegible. */
.eyebrow {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	padding: 0.35rem 0.85rem;
	border-radius: var(--radius-pill);
	background: var(--glass);
	border: 1px solid var(--glass-border);
	font-family: var(--font-display);
	font-size: 0.72rem;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--brand-3);
}

/* --- background stack ----------------------------------------------------- */

/* Three fixed layers behind the whole app: the drifting mesh canvas (or a
   custom image/video in its place - .stars-bg-media, shown by home.js's
   applyBrand() when an admin has set one), a radial fade that kills
   whichever one is showing behind content, and an edge vignette. */
.mesh-canvas,
.stars-bg-media,
.mesh-fade,
.mesh-vignette {
	position: fixed;
	inset: 0;
	pointer-events: none;
	z-index: 0;
}

.stars-bg-media {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Full-bleed sizing for a YouTube background embed (see youtubeVideoId() in
   home.js) - an iframe can't be object-fit:cover'd the way an <img>/<video>
   can, so this is the standard alternative: oversize by the embed's 16:9
   aspect ratio and center it, so whichever of width/height is the
   *constraining* dimension for the current viewport still fully covers it
   (177.78vh = 16:9's width when height is 100vh; 56.25vw = 16:9's height
   when width is 100vw; the min-width/min-height pair is what makes
   whichever constraint actually wins). Applied directly here since
   .stars-bg-media is position:fixed - the viewport itself clips whatever
   spills past those oversized dimensions, no extra wrapper needed.

   #stars-bg-youtube-target (not .bg-youtube itself) carries the actual
   sizing: home.js mounts the embed via the YouTube IFrame Player API,
   which replaces that div IN PLACE with its own iframe - same id, but as
   a sibling of the div's old parent, not nested inside a surviving div -
   so the sizing has to target the id directly, not "#id iframe" (that
   descendant never exists; got this wrong the first time, which is why
   the embed collapsed back to a small corner box at its default intrinsic
   size - inset:0 alone doesn't stretch a replaced element with no
   explicit width/height). */
.stars-bg-media.bg-youtube {
	/* inset:auto MUST come before top/left, not after - it's shorthand for
	   all four sides (top/right/bottom/left), so declaring it later would
	   silently wipe out the top/left values just set below. Got this
	   backwards the first time: with top/left left at .stars-bg-media's own
	   inset:0 → back to auto once inset:auto ran last, position:fixed falls
	   back to the element's static position - which reads as "stuck in the
	   corner", exactly the bug this fixes. */
	inset: auto;
	top: 50%;
	left: 50%;
	width: 177.78vh;
	height: 56.25vw;
	min-width: 100%;
	min-height: 100%;
	transform: translate(-50%, -50%);
}

.stars-bg-media.bg-youtube #stars-bg-youtube-target {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	display: block;
	border: 0;
}

.mesh-fade {
	background: radial-gradient(60% 50% at 50% 45%, rgba(7, 9, 18, 0.82) 0, rgba(0, 0, 0, 0) 100%);
}

.mesh-vignette {
	background: radial-gradient(88% 78%, rgba(0, 0, 0, 0) 28%, rgba(7, 9, 18, 0.92) 100%);
}

/* :where() keeps this at zero specificity so positioned chrome (the sticky
   topbar, the fixed chat widget) can still win with its own position rules. */
body > :where(*:not(.mesh-canvas):not(.mesh-fade):not(.mesh-vignette)) {
	position: relative;
	z-index: 1;
}

/* --- keyframes ------------------------------------------------------------ */

@keyframes flowBorder {
	0% { background-position: 0% 50%; }
	50% { background-position: 100% 50%; }
	100% { background-position: 0% 50%; }
}

@keyframes apexBulge {
	0%, 100% {
		box-shadow: 0 20px 60px rgba(124, 92, 255, 0.40), 0 0 80px rgba(56, 189, 248, 0.20),
			inset 0 0 60px rgba(56, 189, 248, 0.10), -15px 0 40px rgba(124, 92, 255, 0.60);
	}
	25% {
		box-shadow: 0 20px 60px rgba(124, 92, 255, 0.40), 0 0 80px rgba(56, 189, 248, 0.20),
			inset 0 0 60px rgba(56, 189, 248, 0.10), 0 -15px 40px rgba(124, 92, 255, 0.60);
	}
	50% {
		box-shadow: 0 20px 60px rgba(124, 92, 255, 0.40), 0 0 80px rgba(56, 189, 248, 0.20),
			inset 0 0 60px rgba(56, 189, 248, 0.10), 15px 0 40px rgba(124, 92, 255, 0.60);
	}
	75% {
		box-shadow: 0 20px 60px rgba(124, 92, 255, 0.40), 0 0 80px rgba(56, 189, 248, 0.20),
			inset 0 0 60px rgba(56, 189, 248, 0.10), 0 15px 40px rgba(124, 92, 255, 0.60);
	}
}

@keyframes badgePulse {
	0%, 100% { box-shadow: 0 6px 20px rgba(124, 92, 255, 0.70), 0 0 30px rgba(124, 92, 255, 0.40); }
	50% { box-shadow: 0 8px 30px rgba(124, 92, 255, 0.90), 0 0 40px rgba(124, 92, 255, 0.60); }
}

@keyframes ctaShimmer {
	0% { left: -150%; }
	60% { left: 150%; }
	100% { left: 150%; }
}

/* --- top bar ------------------------------------------------------------- */

.topbar {
	position: sticky;
	top: 0;
	z-index: 40;
	background: rgba(7, 9, 18, 0.60);
	backdrop-filter: blur(16px) saturate(140%);
	-webkit-backdrop-filter: blur(16px) saturate(140%);
	border-bottom: 1px solid var(--glass-border);
}

.brandbar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	padding: 14px var(--gutter) 13px;
	border-bottom: 1px solid var(--border-soft);
}

.topbar-right {
	position: relative;
	display: flex;
	align-items: center;
	gap: 10px;
	flex: none;
}

.brand {
	display: flex;
	align-items: center;
	gap: 13px;
	color: var(--text);
	min-width: 0;
}

.brand:hover {
	text-decoration: none;
	color: var(--text);
}

.brand img {
	width: 52px;
	height: 52px;
	flex: none;
}

.brand-text {
	display: flex;
	flex-direction: column;
	gap: 4px;
	min-width: 0;
}

.brand-name {
	font-family: var(--font-display);
	font-weight: 700;
	font-size: 1.6rem;
	line-height: 1.02;
	letter-spacing: 0.005em;
	text-transform: uppercase;
	color: var(--text);
}

/* The strapline: solid accent (too small for gradient text). */
.brand-tag {
	font-family: var(--font-mono);
	font-size: 0.8125rem;
	font-weight: 500;
	letter-spacing: 0.17em;
	text-transform: uppercase;
	color: var(--brand-3);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* --- nav band ------------------------------------------------------------ */

.navbar {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	padding: 0 var(--gutter) 12px;
	overflow-x: auto;
	scrollbar-width: none;
	/* A static edge fade as the scroll-affordance hint - scrollbar-width:none
	   above already removes the one native cue that there's more off-screen,
	   so this replaces it. Only matters on the narrow screens where the row
	   actually overflows; a subtle 24px fade at ends that already fit
	   everything is not worth branching on with JS. */
	mask-image: linear-gradient(to right, transparent 0, black 24px, black calc(100% - 24px), transparent 100%);
	-webkit-mask-image: linear-gradient(to right, transparent 0, black 24px, black calc(100% - 24px), transparent 100%);
}

.navbar::-webkit-scrollbar {
	display: none;
}

/* The signature nav treatment: dim -> white, with a gradient underline that
   grows out from the CENTER on hover/active. No more boxed active state. */
.navbar a {
	position: relative;
	padding: 9px 17px;
	border-radius: var(--radius-sm);
	font-family: var(--font-display);
	font-size: 0.9rem;
	font-weight: 500;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: rgba(255, 255, 255, 0.72);
	white-space: nowrap;
	transition: color var(--t-fast);
}

.navbar a::after {
	content: '';
	position: absolute;
	left: 14px;
	right: 14px;
	bottom: 3px;
	height: 2px;
	background: var(--grad-90);
	border-radius: 2px;
	opacity: 0;
	transform: scaleX(0.4);
	transform-origin: center;
	transition: opacity var(--t-fast), transform var(--t-fast);
}

.navbar a:hover {
	color: #fff;
	text-decoration: none;
}

.navbar a:hover::after,
.navbar a.active::after {
	opacity: 1;
	transform: scaleX(1);
}

.navbar a.active {
	color: #fff;
}

.nav-sep {
	flex: none;
	width: 1px;
	height: 18px;
	margin: 0 12px;
	background: var(--glass-border);
}

.nav-join {
	color: var(--brand-4) !important;
}

/* --- account dropdown ---------------------------------------------------- */

.account-menu {
	position: absolute;
	top: calc(100% + 9px);
	right: 0;
	min-width: 200px;
	padding: 6px;
	background: rgba(13, 16, 32, 0.92);
	backdrop-filter: var(--glass-blur-sm);
	-webkit-backdrop-filter: var(--glass-blur-sm);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius);
	box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
	z-index: 50;
}

.account-menu a,
.account-menu button {
	display: block;
	width: 100%;
	padding: 8px 11px;
	background: transparent;
	border: 0;
	border-radius: var(--radius-sm);
	color: var(--text-dim);
	font-family: var(--font-body);
	font-size: var(--fs-sm);
	text-align: left;
	cursor: pointer;
	transition: background var(--t-fast), color var(--t-fast);
}

.account-menu a:hover,
.account-menu button:hover {
	background: rgba(124, 92, 255, 0.10);
	color: var(--text);
	text-decoration: none;
}

.signin {
	padding: 7px 16px;
	border: 1px solid var(--glass-border-hi);
	border-radius: var(--radius-pill);
	background: var(--glass);
	color: var(--text-dim);
	font-family: var(--font-display);
	font-size: var(--fs-sm);
	font-weight: 500;
	transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}

.signin:hover {
	background: var(--glass-strong);
	border-color: rgba(255, 255, 255, 0.30);
	color: var(--text);
	text-decoration: none;
}

/* A 999px glass chip; the avatar sits in a small gradient ring. */
.account-btn {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 4px 12px 4px 4px;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius-pill);
	color: var(--text);
	font-family: var(--font-body);
	font-size: var(--fs-sm);
	cursor: pointer;
	transition: border-color var(--t-fast), background var(--t-fast);
}

.account-btn:hover {
	border-color: var(--glass-border-hi);
	background: var(--glass-strong);
}

.account-btn img {
	width: 26px;
	height: 26px;
	border-radius: 50%;
	object-fit: cover;
	padding: 2px;
	background: var(--grad);
}

.account-name {
	max-width: 130px;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.account-head {
	padding: 9px 11px 10px;
	border-bottom: 1px solid var(--glass-border);
	margin-bottom: 4px;
	font-size: var(--fs-sm);
	color: var(--text-dim);
}

/* --- maintenance page ---------------------------------------------------- */

.maint {
	max-width: none;
	width: 100%;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	min-height: 100vh;
	min-height: 100dvh;
	text-align: center;
}

.maint-brand {
	flex: none;
	align-self: flex-start;
	display: flex;
	align-items: center;
	gap: 11px;
	padding: 16px var(--gutter);
	color: var(--text);
}

.maint-body {
	flex: 1;
	display: grid;
	place-items: center;
	padding: 24px;
}

.maint-brand:hover {
	text-decoration: none;
}

.maint-brand img {
	width: 38px;
	height: 38px;
}

.maint-brand span {
	font-family: var(--font-display);
	font-weight: 700;
	font-size: 1rem;
	letter-spacing: 0.01em;
	text-transform: uppercase;
}

.maint-inner {
	display: flex;
	flex-direction: column;
	align-items: center;
	max-width: 46ch;
}

.maint-pill {
	display: inline-flex;
	align-items: center;
	gap: 9px;
	margin-bottom: 20px;
	padding: 7px 16px 7px 13px;
	background: rgba(229, 72, 77, 0.1);
	border: 1px solid rgba(229, 72, 77, 0.45);
	border-radius: 999px;
	font-family: var(--font-display);
	font-size: 0.8125rem;
	font-weight: 700;
	letter-spacing: 0.13em;
	text-transform: uppercase;
	color: var(--bad);
}

.maint h1 {
	font-size: 2rem;
	margin: 0 0 12px;
}

.maint-message {
	margin: 0 0 26px;
	color: var(--text-dim);
}

.maint-timer {
	align-self: stretch;
	margin: 0 0 26px;
	padding: 16px 20px;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius);
}

.maint-timer-label {
	display: block;
	font-family: var(--font-mono);
	font-size: var(--fs-mono);
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--text-mute);
}

.maint-countdown {
	display: block;
	margin: 4px 0 2px;
	font-family: var(--font-mono);
	font-size: 1.75rem;
	font-weight: 500;
	letter-spacing: 0.02em;
	color: var(--text);
	font-variant-numeric: tabular-nums;
}

.maint-eta {
	display: block;
	font-family: var(--font-mono);
	font-size: var(--fs-mono);
	letter-spacing: 0.06em;
	color: var(--text-mute);
}

/* --- site strip ---------------------------------------------------------- */

.sitebar {
	display: flex;
	align-items: center;
	justify-content: center;
	flex-wrap: wrap;
	gap: 10px;
	position: relative;
	padding: 10px calc(var(--gutter) + 28px) 10px var(--gutter);
	border-bottom: 1px solid var(--glass-border);
	background: var(--accent-wash);
	font-size: var(--fs-sm);
	color: var(--text);
	text-align: center;
}

.sitebar.is-preview {
	background: rgba(229, 72, 77, 0.09);
	border-bottom-color: rgba(229, 72, 77, 0.3);
}

.sitebar a {
	font-weight: 600;
	white-space: nowrap;
}

.sitebar-close {
	position: absolute;
	top: 50%;
	right: calc(var(--gutter) - 6px);
	transform: translateY(-50%);
	width: 26px;
	height: 26px;
	padding: 0;
	background: transparent;
	border: 0;
	border-radius: var(--radius-sm);
	color: var(--text-dim);
	font-size: 18px;
	line-height: 1;
	cursor: pointer;
}

.sitebar-close:hover {
	color: var(--text);
	background: rgba(255, 255, 255, 0.06);
}

/* .sitebar sets display:flex unconditionally above, which - being an author
   rule - beats the browser's own built-in `[hidden] { display: none }` rule
   even though the two have equal specificity, because author styles always
   outrank user-agent ones. Without this, toggling .hidden = true in home.js
   did nothing: the element stayed a visible, empty flex row showing only its
   background color and padding. */
.sitebar[hidden] {
	display: none;
}

/* Compact "viewing as" indicator, next to the account button rather than a
   full-width strip - same red as .sitebar.is-preview above, just as a pill
   instead of a banner. The real identity lives in the title tooltip, not
   on-page text, which is what keeps this short. No dismiss button by design;
   the only way out is actually stopping. */
.viewing-as {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 5px 6px 5px 12px;
	border-radius: var(--radius-pill);
	background: rgba(229, 72, 77, 0.16);
	border: 1px solid rgba(229, 72, 77, 0.45);
	font-size: var(--fs-sm);
	color: var(--text);
	white-space: nowrap;
}

.viewing-as button {
	padding: 4px 10px;
	border-radius: var(--radius-pill);
	border: 1px solid rgba(229, 72, 77, 0.5);
	background: rgba(229, 72, 77, 0.3);
	color: var(--text);
	font-weight: 600;
	font-size: 12px;
	cursor: pointer;
	white-space: nowrap;
}

.viewing-as button:hover {
	background: rgba(229, 72, 77, 0.45);
}

.viewing-as button:disabled {
	opacity: 0.6;
	cursor: default;
}

/* --- layout -------------------------------------------------------------- */

main {
	flex: 1 0 auto;
	width: 100%;
	max-width: var(--maxw);
	margin: 0 auto;
	padding: 0 var(--gutter) 36px;
}

/* --- hero ---------------------------------------------------------------- */

/* Two columns: copy left, the glass status card right. A soft violet radial
   behind the whole band stands in for the reference's hero video. */
.hero {
	position: relative;
	display: grid;
	grid-template-columns: 1.1fr 0.9fr;
	gap: 48px;
	align-items: center;
	padding: 40px 0 28px;
	text-align: left;
}

.hero::before {
	content: '';
	position: absolute;
	inset: -80px -160px 0;
	background: radial-gradient(60% 45% at 50% 30%, rgba(124, 92, 255, 0.18), transparent 70%);
	pointer-events: none;
	z-index: -1;
}

/* Optional admin-configured background (utils/brand.js's hero field) -
   the comment above on .hero used to say the violet radial glow "stands in
   for the reference's hero video"; this is that video, when one's set.
   Sits behind the glow (z-index -2 vs -1) so the existing brand-color wash
   still tints it rather than the two competing. */
.hero-bg-media {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	z-index: -2;
}

/* Only present when the hero video URL is a YouTube link (youtubeVideoId()
   in home.js) - .hero-bg-media above already gives this wrapper the right
   bounds (inset:0, matching .hero exactly), this just adds overflow:hidden
   so the oversized #hero-bg-youtube-target div inside it (same trick as
   .stars-bg-media.bg-youtube, see home.css's background-stack section for
   the full explanation) gets clipped to those bounds instead of spilling
   into the page above/below. Deliberately NOT added to .hero itself -
   .hero::before's own ambient glow relies on a negative inset to bleed
   past .hero's edges, which overflow:hidden there would cut off.

   Targets #hero-bg-youtube-target directly, not a nested "#id iframe" -
   home.js mounts the embed via the YouTube IFrame Player API, which
   replaces that div IN PLACE with its own iframe (same id, as a sibling
   of the div's old parent, not nested inside a surviving div), so the
   oversized-box sizing has to be on the id itself. */
.hero-bg-youtube-clip {
	overflow: hidden;
}

.hero-bg-youtube-clip #hero-bg-youtube-target {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 177.78vh;
	height: 56.25vw;
	min-width: 100%;
	min-height: 100%;
	transform: translate(-50%, -50%);
	display: block;
	border: 0;
}

/* Always present alongside hero-bg-media (never optional/tunable - see
   utils/brand.js's header) so hero-title/hero-sub stay legible over
   whatever's picked, with zero per-upload adjustment needed. */
.hero-scrim {
	position: absolute;
	inset: 0;
	z-index: -2;
	background: linear-gradient(100deg, rgba(4, 5, 12, 0.88) 0%, rgba(4, 5, 12, 0.68) 45%, rgba(4, 5, 12, 0.45) 100%);
}

.hero-copy .eyebrow {
	margin-bottom: 18px;
}

.hero-title {
	font-size: var(--fs-hero);
	margin: 0;
	max-width: 16ch;
}

.hero-sub {
	max-width: 56ch;
	margin: 18px 0 0;
	font-size: var(--fs-lead);
	line-height: 1.6;
	color: var(--text-dim);
}

.hero-cta {
	display: flex;
	flex-wrap: wrap;
	gap: 12px;
	margin-top: 28px;
}

/* Hero entrance: the copy column cascades in top to bottom, the status card
   follows last. Fade-up only (the grid-card-in keyframe, reused as-is) - no
   sweep here, since these are prose/copy elements rather than the grid-card
   shape the sweep was built for, and .hero-card in particular already has an
   overhanging .badge-brand that a sweep's required overflow:hidden would
   clip (same reasoning as .benefit below). backwards is safe on all of
   these: none of them have a competing hover-transition, and each
   keyframe's 100% state already matches the element's own un-animated
   default, so there's nothing to snap back to. */
.hero-copy .eyebrow,
.hero-title,
.hero-sub,
.hero-cta,
.hero-card {
	animation: grid-card-in 0.7s cubic-bezier(0.16, 1, 0.3, 1) backwards;
}

.hero-copy .eyebrow {
	animation-delay: 0ms;
}

.hero-title {
	animation-delay: 80ms;
}

.hero-sub {
	animation-delay: 160ms;
}

.hero-cta {
	animation-delay: 240ms;
}

/* --- hero status card (the flagship) -------------------------------------- */

.hero-card {
	position: relative;
	max-width: 440px;
	justify-self: end;
	width: 100%;
	padding: 1.4rem 1.4rem 1.2rem;
	border-radius: var(--radius-lg);
	background: linear-gradient(135deg, rgba(168, 85, 247, 0.08), rgba(56, 189, 248, 0.06));
	backdrop-filter: var(--glass-blur);
	-webkit-backdrop-filter: var(--glass-blur);
	border: 2px solid transparent;
	/* Two animations at once, comma-separated in one shorthand - a second
	   separate `animation:` declaration would replace apexBulge rather than
	   run alongside it, since it's a single-value property. apexBulge keeps
	   running forever as before; grid-card-in only plays once, 320ms after
	   the copy column starts, and its own backwards fill only affects
	   opacity/transform, so the two never touch the same property. */
	animation:
		apexBulge 6s ease-in-out infinite,
		grid-card-in 0.7s cubic-bezier(0.16, 1, 0.3, 1) 320ms backwards;
}

/* The animated gradient border, masked to a 2px ring. */
.hero-card::before {
	content: '';
	position: absolute;
	inset: 0;
	border-radius: var(--radius-lg);
	padding: 2px;
	background: var(--grad);
	background-size: 300% 300%;
	-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
	-webkit-mask-composite: xor;
	mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
	mask-composite: exclude;
	animation: flowBorder 6s ease-in-out infinite;
	pointer-events: none;
}

.hero-card > * {
	position: relative;
	z-index: 1;
}

.badge-brand {
	position: absolute;
	top: -12px;
	left: 50%;
	transform: translateX(-50%);
	background: var(--grad);
	color: #fff;
	font-family: var(--font-display);
	font-size: 0.62rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.08em;
	padding: 0.3rem 0.7rem;
	border-radius: var(--radius-pill);
	animation: badgePulse 3s ease-in-out infinite;
	white-space: nowrap;
}

.hero-card-kicker {
	font-family: var(--font-mono);
	font-size: 0.68rem;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--text-mute);
	text-align: center;
}

.hero-card-title {
	margin: 4px 0 10px;
	font-family: var(--font-display);
	font-size: 1.6rem;
	font-weight: 700;
	text-align: center;
}

.hero-card-value {
	font-family: var(--font-display);
	font-size: 2.5rem;
	font-weight: 700;
	line-height: 1.05;
	text-align: center;
}

.hero-card-note {
	margin-bottom: 16px;
	font-family: var(--font-mono);
	font-size: var(--fs-mono);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-mute);
	text-align: center;
}

.hero-card-list {
	list-style: none;
	margin: 0 0 14px;
	padding: 10px 0 0;
	border-top: 1px solid var(--glass-border);
	display: flex;
	flex-direction: column;
	gap: 0.35rem;
	font-size: 0.85rem;
	color: var(--text-dim);
}

.hero-card-list li::before {
	content: '✓';
	margin-right: 9px;
	color: var(--brand-4);
	font-weight: 700;
}

.hero-card-actions {
	display: flex;
	gap: 10px;
}

.hero-card-actions .btn {
	flex: 1;
	justify-content: center;
}

/* --- buttons ------------------------------------------------------------- */

.btn,
.btn-join {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	padding: 11px 20px;
	border: 1px solid transparent;
	border-radius: var(--radius-sm);
	font-family: var(--font-display);
	font-size: var(--fs-sm);
	font-weight: 600;
	letter-spacing: 0.01em;
	cursor: pointer;
	overflow: hidden;
	transition: transform var(--t-base), box-shadow var(--t-base), background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}

.btn:hover,
.btn-join:hover {
	text-decoration: none;
}

/* The canonical CTA: gradient fill, glow, sheen sweep on hover, lift. */
.btn-primary,
.btn-join {
	background: var(--grad);
	border: 0;
	color: #fff;
	box-shadow: var(--glow-sm);
}

.btn-primary::before,
.btn-join::before {
	content: '';
	position: absolute;
	top: 0;
	bottom: 0;
	left: -100%;
	width: 100%;
	background: var(--grad-sheen);
	transition: left var(--t-slow);
}

.btn-primary:hover::before,
.btn-join:hover::before {
	left: 100%;
}

.btn-primary:hover,
.btn-join:hover {
	transform: translateY(-3px) scale(1.02);
	box-shadow: var(--glow-md);
	color: #fff;
}

.btn-primary:active,
.btn-join:active {
	transform: translateY(-1px) scale(1);
}

/* The looping shimmer, reserved for the single hero CTA. */
.btn-shimmer::after {
	content: '';
	position: absolute;
	top: 0;
	bottom: 0;
	left: -150%;
	width: 60%;
	background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.18), transparent);
	transform: skewX(-20deg);
	animation: ctaShimmer 2.8s ease-in-out infinite;
	pointer-events: none;
}

.btn-ghost {
	background: var(--glass);
	border-color: var(--glass-border-hi);
	color: var(--text);
}

.btn-ghost:hover {
	background: var(--glass-strong);
	border-color: rgba(255, 255, 255, 0.30);
	transform: translateY(-2px);
	color: var(--text);
}

/* --- status dot ---------------------------------------------------------- */

.dot {
	position: relative;
	display: inline-block;
	width: 6px;
	height: 6px;
	margin-right: 7px;
	border-radius: 50%;
	vertical-align: middle;
	flex: none;
}

.dot-ok {
	background: var(--ok);
	box-shadow: 0 0 0 2px rgba(62, 207, 142, 0.18);
}

.dot-ok::after {
	content: '';
	position: absolute;
	inset: 0;
	border-radius: 50%;
	background: var(--ok);
	animation: dot-pulse 2.4s ease-out infinite;
}

@keyframes dot-pulse {
	0% {
		transform: scale(1);
		opacity: 0.5;
	}
	70% {
		transform: scale(3);
		opacity: 0;
	}
	100% {
		transform: scale(3);
		opacity: 0;
	}
}

.dot-warn {
	background: var(--warn);
	box-shadow: 0 0 0 2px rgba(242, 169, 59, 0.18);
}

.dot-bad {
	background: var(--bad);
	box-shadow: 0 0 0 2px rgba(229, 72, 77, 0.18);
}

/* --- numbered sections --------------------------------------------------- */

.sec {
	padding: 30px 0 0;
	border-top: 1px solid var(--border-soft);
	margin-top: 30px;
}

.sec-head {
	display: flex;
	align-items: baseline;
	gap: 12px;
	margin-bottom: 22px;
}

.sec-num {
	font-family: var(--font-mono);
	font-size: var(--fs-mono);
	font-weight: 500;
	letter-spacing: 0.1em;
	color: var(--brand-2);
}

.sec-label {
	font-family: var(--font-mono);
	font-size: var(--fs-mono);
	letter-spacing: 0.18em;
	text-transform: uppercase;
	color: var(--text-mute);
}

.sec h2 {
	font-size: var(--fs-h2);
	margin: 0 0 10px;
}

.sec-lead {
	max-width: 62ch;
	margin: 0 0 26px;
	color: var(--text-dim);
}

/* --- stats --------------------------------------------------------------- */

/* Glass tiles with gradient numbers, replacing the hairline strip. */
.stats {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
	gap: 14px;
	margin-top: 26px;
}

.stat {
	display: flex;
	flex-direction: column;
	gap: 3px;
	padding: 14px 18px;
	background: linear-gradient(135deg, rgba(168, 85, 247, 0.07), rgba(56, 189, 248, 0.05));
	border: 1px solid var(--glass-border);
	border-radius: var(--radius);
	transition: transform var(--t-base), box-shadow var(--t-base), border-color var(--t-base);
}

.stat:hover {
	transform: translateY(-3px);
	border-color: rgba(124, 92, 255, 0.35);
	box-shadow: var(--glow-lg);
}

.stat-label {
	font-family: var(--font-mono);
	font-size: 0.68rem;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--text-mute);
}

.stat-value {
	font-family: var(--font-display);
	font-size: 1.9rem;
	font-weight: 700;
	line-height: 1.1;
	letter-spacing: -0.02em;
	background: var(--grad);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

.stat-note {
	font-family: var(--font-mono);
	font-size: var(--fs-mono);
	letter-spacing: 0.06em;
	color: var(--text-mute);
}

/* --- benefits row --------------------------------------------------------- */

/* Four cards, each with a circular gradient icon overhanging the top edge. */
.benefits {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: 20px;
	margin-top: 38px;
}

.benefit {
	position: relative;
	margin-top: 26px;
	padding: 36px 18px 18px;
	text-align: center;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius);
	backdrop-filter: var(--glass-blur-sm);
	-webkit-backdrop-filter: var(--glass-blur-sm);
	transition: transform var(--t-base), border-color var(--t-base), box-shadow var(--t-base);
	/* Fade-up entrance, staggered per card via --card-delay (set inline in
	   home.js, same mechanism as the Team/Releases/Reviews grids). No sweep
	   pseudo here unlike those - the sweep technique needs overflow:hidden
	   for containment, which would clip .benefit-icon (positioned at
	   top:-26px, deliberately overhanging above the card's own top edge). */
	animation: grid-card-in 0.9s cubic-bezier(0.16, 1, 0.3, 1) backwards;
	animation-delay: var(--card-delay, 0ms);
}

.benefit:hover {
	transform: translateY(-3px);
	border-color: rgba(124, 92, 255, 0.35);
	box-shadow: var(--glow-lg);
}

.benefit-icon {
	position: absolute;
	/* Half the icon's own height, matching .benefit's margin-top exactly - any
	   smaller and the icon overlaps whatever sits above the benefits row. */
	top: -26px;
	left: 50%;
	transform: translateX(-50%);
	display: grid;
	place-items: center;
	width: 52px;
	height: 52px;
	border-radius: 50%;
	background: var(--grad);
	font-size: 22px;
	box-shadow: var(--glow-md);
}

.benefit h2 {
	margin: 0 0 6px;
	font-size: 1.05rem;
}

.benefit p {
	margin: 0;
	font-size: var(--fs-sm);
	color: var(--text-dim);
}

/* --- generic page -------------------------------------------------------- */

.page {
	padding: 32px 0 0;
	max-width: 760px;
}

.page .eyebrow {
	margin-bottom: 14px;
}

.page h1 {
	margin: 0 0 8px;
	font-size: var(--fs-h1);
}

/* Branded section headings inside content pages. 22px, safely above the 16px
   gradient-text floor. Legal pages opt back out below (that's .legal h3 -
   the markdown-rendered Terms/Privacy body, a separate heading level from
   these hand-written page subheadings). */
.page h2 {
	margin: 30px 0 8px;
	font-size: var(--fs-h3);
	background: var(--grad);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
	width: fit-content;
}

.page p {
	margin: 0 0 15px;
	color: var(--text-dim);
}

.page-sub {
	margin: 0 0 26px;
	padding-bottom: 18px;
	border-bottom: 1px solid var(--border-soft);
	font-size: var(--fs-lead);
	color: var(--text-dim);
}

/* --- rules list ---------------------------------------------------------- */

/* Glass cards in a two-column grid; the number is a big gradient figure. */
.rules {
	list-style: none;
	margin: 0;
	padding: 0;
	counter-reset: rule;
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 14px;
}

.rules li {
	position: relative;
	padding: 18px 20px 18px 66px;
	counter-increment: rule;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius);
	backdrop-filter: var(--glass-blur-sm);
	-webkit-backdrop-filter: var(--glass-blur-sm);
	transition: transform var(--t-base), border-color var(--t-base), box-shadow var(--t-base);
}

.rules li:hover {
	transform: translateY(-3px);
	border-color: rgba(124, 92, 255, 0.35);
	box-shadow: var(--glow-lg);
}

.rules li::before {
	position: absolute;
	left: 18px;
	top: 16px;
	content: counter(rule, decimal-leading-zero);
	font-family: var(--font-display);
	font-size: 1.6rem;
	font-weight: 700;
	line-height: 1.1;
	background: var(--grad);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

.rules strong {
	display: block;
	margin-bottom: 3px;
	font-family: var(--font-display);
	font-weight: 600;
	font-size: var(--fs-body);
	color: var(--text);
}

.rules span {
	color: var(--text-dim);
	font-size: var(--fs-sm);
}

/* --- facts (label/value pairs) -------------------------------------------- */

/* Small glass tiles rather than ruled rows. */
.facts {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
	gap: 12px;
	margin: 0 0 20px;
}

.fact {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 6px;
	padding: 14px 16px;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius);
}

.fact span:first-child {
	font-family: var(--font-mono);
	font-size: 0.68rem;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--text-mute);
}

.fact span:last-child:not(.pill) {
	font-family: var(--font-display);
	font-size: var(--fs-body);
	font-weight: 600;
	color: var(--text);
}

/* --- split panels -------------------------------------------------------- */

/* Two glass cards, each led by a gradient icon tile. */
.split {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 20px;
	margin-bottom: 28px;
}

.panel {
	padding: 22px 24px;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius);
	backdrop-filter: var(--glass-blur-sm);
	-webkit-backdrop-filter: var(--glass-blur-sm);
	transition: transform var(--t-base), border-color var(--t-base), box-shadow var(--t-base);
}

.panel:hover {
	transform: translateY(-3px);
	border-color: rgba(124, 92, 255, 0.35);
	box-shadow: var(--glow-lg);
}

.panel h3 {
	margin: 0 0 6px;
	font-size: var(--fs-h3);
}

.panel p {
	margin: 0;
	color: var(--text-dim);
}

/* The gradient rounded-square icon tile that leads a card. */
.icon-tile {
	display: grid;
	place-items: center;
	width: 40px;
	height: 40px;
	margin-bottom: 12px;
	border-radius: 10px;
	background: var(--grad);
	font-size: 20px;
	box-shadow: var(--glow-sm);
}

/* --- account ------------------------------------------------------------- */

.account-hero {
	display: flex;
	align-items: center;
	gap: 16px;
	padding: 32px 0 20px;
}

.account-hero img {
	width: 72px;
	height: 72px;
	border-radius: 50%;
	object-fit: cover;
	padding: 3px;
	background: var(--grad);
}

.account-hero h1 {
	margin: 0;
	font-size: var(--fs-h1);
}

.account-hero .page-sub {
	margin: 2px 0 0;
	padding: 0;
	border: 0;
	font-size: var(--fs-body);
}

/* Role chips: gradient-tinted outline pills. */
.role {
	display: inline-block;
	margin: 0 6px 6px 0;
	padding: 3px 11px;
	border: 1px solid rgba(124, 92, 255, 0.40);
	border-radius: var(--radius-pill);
	background: rgba(124, 92, 255, 0.06);
	font-family: var(--font-mono);
	font-size: var(--fs-mono);
	letter-spacing: 0.06em;
	color: var(--text);
}

/* --- profile settings ---------------------------------------------------- */

.profile-edit {
	display: flex;
	align-items: flex-start;
	gap: 28px;
	flex-wrap: wrap;
	padding: 22px 0 4px;
	border-top: 1px solid var(--border-soft);
}

.profile-preview {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 9px;
	flex: none;
}

.profile-preview img {
	width: 88px;
	height: 88px;
	border-radius: 50%;
	object-fit: cover;
	padding: 3px;
	background: var(--grad);
	transition: background var(--t-fast), opacity var(--t-fast);
}

/* A link that will not load says so on the picture itself: the ring goes red. */
.profile-preview img.is-bad {
	background: var(--bad);
	opacity: 0.35;
}

.profile-fields {
	flex: 1;
	min-width: 260px;
}

.profile-hint {
	margin: 8px 0 0;
	font-size: var(--fs-sm);
}

.profile-hint code {
	padding: 1px 5px;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: 4px;
	font-family: var(--font-mono);
	font-size: 0.85em;
}

.profile-actions {
	display: flex;
	align-items: center;
	gap: 14px;
	margin-top: 18px;
}

/* The avatar-URL field is the one pill input on the site. */
.profile-fields input[type='text'] {
	width: 100%;
	padding: 12px 20px;
	background: rgba(255, 255, 255, 0.04);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius-pill);
	color: var(--text);
	font-family: var(--font-body);
	font-size: var(--fs-sm);
	transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}

.profile-fields input[type='text']:focus {
	outline: none;
	border-color: var(--brand-2);
	box-shadow: 0 0 0 3px rgba(124, 92, 255, 0.18);
	background: rgba(255, 255, 255, 0.06);
}

/* --- pills --------------------------------------------------------------- */

.pill {
	display: inline-flex;
	align-items: center;
	gap: 0.35rem;
	padding: 2px 10px;
	border: 1px solid var(--glass-border);
	border-radius: var(--radius-pill);
	background: var(--glass);
	font-family: var(--font-mono);
	font-size: var(--fs-mono);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--text-dim);
}

.pill-approved {
	border-color: rgba(62, 207, 142, 0.35);
	background: rgba(62, 207, 142, 0.10);
	color: var(--ok);
}

.pill-denied {
	border-color: rgba(229, 72, 77, 0.35);
	background: rgba(229, 72, 77, 0.10);
	color: var(--bad);
}

.pill-pending {
	border-color: rgba(242, 169, 59, 0.35);
	background: rgba(242, 169, 59, 0.10);
	color: var(--warn);
}

/* --- notices ------------------------------------------------------------- */

.notice {
	margin: 0 0 20px;
	padding: 12px 16px;
	border: 1px solid rgba(124, 92, 255, 0.25);
	border-left: 2px solid var(--brand-2);
	border-radius: var(--radius-sm);
	background: var(--accent-wash);
	color: var(--text-dim);
}

/* Warning variant - mirrors the admin dashboard's own .banner/.banner-bad
   pairing. Used for the "you're suspended from deleting" notice on /reviews
   (see canModerateReviews() in home.js) - the same brand-purple .notice
   would read as neutral/informational instead of a real restriction. */
.notice-bad {
	border-color: rgba(229, 72, 77, 0.35);
	border-left-color: var(--bad);
	background: rgba(229, 72, 77, 0.08);
}

/* --- legal pages --------------------------------------------------------- */

/* A glass reading panel; prose stays neutral, headings get a small gradient
   rule instead of a gradient fill. */
.legal {
	max-width: 72ch;
	padding: 28px 32px;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius-lg);
	backdrop-filter: var(--glass-blur-sm);
	-webkit-backdrop-filter: var(--glass-blur-sm);
}

.legal h3 {
	margin: 30px 0 8px;
	font-size: var(--fs-h3);
	background: none;
	-webkit-background-clip: initial;
	background-clip: initial;
	color: var(--text);
	width: auto;
	position: relative;
	padding-bottom: 6px;
}

.legal h3::after {
	content: '';
	position: absolute;
	left: 0;
	bottom: 0;
	width: 42px;
	height: 2px;
	border-radius: 2px;
	background: var(--grad-90);
}

.legal p,
.legal li {
	color: var(--text-dim);
}

.legal code {
	padding: 1px 5px;
	background: rgba(255, 255, 255, 0.05);
	border: 1px solid var(--glass-border);
	border-radius: 4px;
	font-family: var(--font-mono);
	font-size: 0.85em;
}

.legal-list {
	margin: 10px 0 16px;
	padding-left: 18px;
}

.legal-list li {
	margin-bottom: 7px;
}

/* --- footer -------------------------------------------------------------- */

footer {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 14px;
	flex-wrap: wrap;
	padding: 16px var(--gutter) 20px;
	border-top: 1px solid var(--glass-border);
	font-family: var(--font-mono);
	font-size: var(--fs-mono);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-mute);
}

.footer-left {
	display: flex;
	align-items: center;
	gap: 16px;
	flex-wrap: wrap;
}

.footer-status {
	display: inline-flex;
	align-items: center;
	padding: 4px 12px 4px 10px;
	border: 1px solid var(--glass-border);
	border-radius: 999px;
	background: var(--glass);
	color: var(--text-dim);
	white-space: nowrap;
}

.status-icon {
	display: inline-block;
	margin-right: 6px;
	font-size: 0.85em;
	line-height: 1;
	vertical-align: -1px;
}

/* The subtle "alive" pulse the old dot had, carried over onto the icon -
   only for the healthy state, same reasoning as before: a pulse means
   "receiving a heartbeat", and an offline/maintenance icon that throbs reads
   as activity when there isn't any. */
.footer-status:not(.is-maint):not(.is-down) .status-icon {
	animation: status-pulse 2.4s ease-in-out infinite;
}

@keyframes status-pulse {
	0%,
	100% {
		opacity: 1;
		transform: scale(1);
	}
	50% {
		opacity: 0.55;
		transform: scale(0.9);
	}
}

@media (prefers-reduced-motion: reduce) {
	.status-icon {
		animation: none !important;
	}
}

.footer-status:empty {
	display: none;
}

.footer-status.is-maint {
	border-color: rgba(242, 169, 59, 0.4);
	color: var(--text);
}

.footer-status.is-down {
	border-color: rgba(229, 72, 77, 0.4);
	color: var(--text);
}

.footer-links {
	display: flex;
	gap: 20px;
}

.footer-links a {
	color: var(--text-mute);
	transition: color var(--t-fast);
}

.footer-links a:hover {
	color: #fff;
	text-decoration: none;
}

/* --- forms --------------------------------------------------------------- */

.apply-form {
	max-width: 640px;
}

.apply-form .field {
	margin-bottom: 20px;
}

.apply-form label {
	display: block;
	margin-bottom: 6px;
	font-family: var(--font-mono);
	font-size: var(--fs-mono);
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--text-dim);
}

.apply-form input,
.apply-form textarea {
	width: 100%;
	padding: 11px 14px;
	background: rgba(255, 255, 255, 0.04);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius-sm);
	color: var(--text);
	font-family: var(--font-body);
	font-size: var(--fs-sm);
	line-height: 1.55;
	resize: vertical;
	transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}

.apply-form input::placeholder,
.apply-form textarea::placeholder {
	color: var(--text-mute);
}

.apply-form input:focus,
.apply-form textarea:focus {
	outline: none;
	border-color: var(--brand-2);
	box-shadow: 0 0 0 3px rgba(124, 92, 255, 0.18);
	background: rgba(255, 255, 255, 0.06);
}

.apply-actions {
	display: flex;
	align-items: center;
	gap: 14px;
	margin-top: 26px;
}

.apply-form .error {
	margin-top: 12px;
	color: var(--bad);
	font-size: var(--fs-sm);
}

/* --- application flow: track picker + step wizard ------------------------- */
/* Shared by /apply and /beta - see applyPage() in home.js. */

/* Same technique as .hero::before - a soft radial glow sitting behind the
   content, on top of the global mesh (see the file header) - so the apply
   flow reads with the same visual weight as the homepage instead of like a
   plain .page. Both screens get it, not just the picker, so the glow
   doesn't disappear the moment a track is chosen. */
.apply-picker,
.apply-wizard {
	position: relative;
}

.apply-picker::before,
.apply-wizard::before {
	content: '';
	position: absolute;
	inset: -60px -160px 0;
	background: radial-gradient(55% 40% at 50% 10%, rgba(124, 92, 255, 0.16), transparent 70%);
	pointer-events: none;
	z-index: -1;
}

.track-cards {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
	gap: 18px;
	max-width: 640px;
	margin-top: 28px;
}

.track-card {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 10px;
	padding: 26px 24px;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius);
	backdrop-filter: var(--glass-blur-sm);
	-webkit-backdrop-filter: var(--glass-blur-sm);
	text-align: left;
	cursor: pointer;
	color: inherit;
	font-family: inherit;
	/* Staggered via each card's own inline animation-delay (set in home.js) -
	   opacity:0 is the pre-animation state, "forwards" keeps the end state
	   (opacity:1) once it finishes rather than snapping back. */
	opacity: 0;
	animation: track-card-in 0.4s ease forwards;
	transition: transform var(--t-base), border-color var(--t-base), box-shadow var(--t-base);
}

.track-card:hover {
	transform: translateY(-4px);
	border-color: rgba(124, 92, 255, 0.35);
	box-shadow: var(--glow-lg);
}

.track-card:hover .track-icon {
	transform: scale(1.08) rotate(-4deg);
}

/* A gradient-circle badge, same family as .icon-tile/.benefit-icon, just
   bigger - this is the picker's one hero-scale element, so it carries more
   visual weight than either of those. */
.track-icon {
	display: grid;
	place-items: center;
	width: 56px;
	height: 56px;
	border-radius: 50%;
	background: var(--grad);
	font-size: 26px;
	box-shadow: var(--glow-md);
	transition: transform var(--t-base);
}

.track-title {
	font-family: var(--font-display);
	font-weight: 600;
	font-size: var(--fs-lead);
	color: var(--text);
}

.track-desc {
	font-size: var(--fs-sm);
	color: var(--text-dim);
	line-height: 1.5;
}

@keyframes track-card-in {
	from {
		opacity: 0;
		transform: translateY(10px);
	}
	to {
		opacity: 1;
		transform: translateY(0);
	}
}

.step-progress {
	display: flex;
	align-items: center;
	gap: 12px;
	margin: 18px 0 28px;
	max-width: 640px;
}

.step-progress-bar {
	flex: 1;
	height: 6px;
	border-radius: 999px;
	background: var(--glass-border);
	overflow: hidden;
}

/* Width set in two steps from JS (animateStepProgress() in home.js) rather
   than straight from the template - see that function for why. */
.step-progress-fill {
	height: 100%;
	border-radius: inherit;
	background: var(--grad);
	transition: width 0.45s ease;
}

.step-progress-label {
	flex: none;
	font-family: var(--font-mono);
	font-size: var(--fs-mono);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--text-dim);
	white-space: nowrap;
}

/* Every screen in the apply/beta flow carries this class. Three groups:

   STATIC CHROME - .eyebrow, h1, .page-sub, .step-progress (the whole
   progress bar + its "Question X of Y" label). No rule at all - they
   render immediately, same as any other page's title, and never move.

   OTHER TEXT - h2 subheadings, plain paragraphs (:not(.page-sub), which
   is chrome above), and the question's own <label>. Just the
   progress-anchored reveal: scales in from --anchor-x (renderQuestionStep()/
   renderReview() set it to that step's own progress percentage as an
   inline custom property, so it grows from wherever the bar actually
   sits). No sweep, no wipe - a light band or a hard clip-path edge
   crossing letters you're meant to read fights legibility.

   CARDS/BUTTONS - .track-cards (picker), .facts (intro's stat grid),
   .review-list (review's answers), .panel (the "can't apply right now"
   card), .apply-actions (the Back/Next/Submit row), .btn-join (the
   sign-in prompt's link), .input-wrap (wraps the question's
   <input>/<textarea> - see renderQuestionStep(); form controls are
   replaced elements and the ::after trick below doesn't render on those
   in any browser, so the sweep needs a plain div to sit on instead of the
   field itself). All three effects, sequenced rather than simultaneous:
   scale, then a clip-path wipe opens the block, then the gradient light
   sweeps across what's now fully revealed.

   Shared mechanics: "both" holds the 0% keyframe until its delay elapses
   and the 100% keyframe after it finishes, rather than snapping back to
   each element's un-animated default the instant an animation ends - the
   sweep's default, untransformed position sits mid-band and highly
   visible, which is exactly what happened before this said "backwards"
   alone. overflow:hidden on the cards/buttons group clips the sweep to
   each block's own box - it travels 130% of its own width, which clears
   a narrow block easily but not a wide desktop viewport's empty space
   beside a short one, since nothing else stops it rendering past the
   block's edge. z-index:-1 on the sweep (same technique as the ambient
   glow further up this file) sits it behind any text still inside a
   cards/buttons block (e.g. the pill labels in .facts) rather than
   washing across it. .apply-done carries none of these classes, so it's
   untouched either way - it already runs its own checkmark-draw-in and
   confetti burst instead (see renderApplyDone()). Fresh markup every time
   (view.innerHTML is fully replaced on each render), so all of this plays
   automatically - no JS restart needed. */

.apply-screen-in h2,
.apply-screen-in p:not(.page-sub),
.apply-screen-in .apply-form label {
	transform-origin: var(--anchor-x, 30%) 6%;
	animation: apply-screen-in-scale 0.9s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.apply-screen-in .track-cards,
.apply-screen-in .facts,
.apply-screen-in .review-list,
.apply-screen-in .panel,
.apply-screen-in .apply-actions,
.apply-screen-in .btn-join,
.apply-screen-in .input-wrap {
	position: relative;
	overflow: hidden;
	transform-origin: var(--anchor-x, 30%) 6%;
	animation:
		apply-screen-in-scale 0.9s cubic-bezier(0.16, 1, 0.3, 1) both,
		apply-screen-in-wipe 0.7s cubic-bezier(0.65, 0, 0.35, 1) 0.15s both;
}

.apply-screen-in .track-cards::after,
.apply-screen-in .facts::after,
.apply-screen-in .review-list::after,
.apply-screen-in .panel::after,
.apply-screen-in .apply-actions::after,
.apply-screen-in .btn-join::after,
.apply-screen-in .input-wrap::after {
	content: '';
	position: absolute;
	/* Fixed px, not percent - the content block is auto-height (sized by
	   its own content, not an explicit value), and percentage inset on an
	   absolutely-positioned element inside an auto-height container
	   doesn't resolve against that container at all; it falls through to
	   the viewport, which is what turned this into a huge band spanning
	   most of the page instead of a band sized to the block. */
	inset: -40px -220px;
	z-index: -1;
	background: linear-gradient(105deg, transparent 42%, rgba(168, 85, 247, 0.55) 50%, rgba(56, 189, 248, 0.55) 54%, transparent 62%);
	pointer-events: none;
	mix-blend-mode: screen;
	/* Longer than the scale+wipe above and started after they're mostly
	   done (0.5s in), so it reads as a distinct final pass across an
	   already-visible card rather than a blink buried inside the rest of
	   the motion - the previous 1.1s-with-no-real-delay version mostly
	   played out while the card was still tiny/clipped, which is what
	   made it look "super fast". */
	animation: apply-screen-in-sweep 2.7s cubic-bezier(0.4, 0, 0.2, 1) 0.5s both;
}

@keyframes apply-screen-in-scale {
	from {
		opacity: 0;
		transform: scale(0.34);
	}
	to {
		opacity: 1;
		transform: scale(1);
	}
}

@keyframes apply-screen-in-wipe {
	from {
		clip-path: inset(0 100% 0 0);
	}
	to {
		clip-path: inset(0 0 0 0);
	}
}

@keyframes apply-screen-in-sweep {
	from {
		transform: translateX(-130%);
	}
	to {
		transform: translateX(130%);
	}
}

/* --- review screen ---------------------------------------------------------- */

.review-heading {
	margin: 4px 0 6px;
}

.review-list {
	display: flex;
	flex-direction: column;
	gap: 10px;
	max-width: 640px;
	margin-top: 20px;
}

.review-item {
	padding: 14px 16px;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius-sm);
}

.review-item-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
}

.review-item-label {
	font-family: var(--font-mono);
	font-size: var(--fs-mono);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--text-dim);
}

.review-item-edit {
	flex: none;
	padding: 0;
	background: none;
	border: 0;
	color: var(--brand-3);
	font-size: var(--fs-sm);
	font-weight: 600;
	cursor: pointer;
}

.review-item-edit:hover {
	text-decoration: underline;
}

.review-item-answer {
	margin: 6px 0 0;
	font-size: var(--fs-sm);
	color: var(--text);
	line-height: 1.5;
	white-space: pre-wrap;
}

/* --- done screen: checkmark + confetti -------------------------------------- */

/* .page doesn't center itself (it's flush-left, just width-capped) - centering
   only the text would leave it stuck in a left-hugging column, so this also
   centers the box itself, scoped to this one screen rather than every .page. */
.apply-done {
	text-align: center;
	margin: 0 auto;
}

.done-check {
	position: relative;
	width: 84px;
	height: 84px;
	margin: 12px auto 22px;
}

.done-check svg {
	width: 100%;
	height: 100%;
}

.done-check-circle {
	stroke: var(--ok);
	stroke-width: 2;
	stroke-dasharray: 151;
	stroke-dashoffset: 151;
	animation: done-circle 0.5s ease forwards;
}

.done-check-mark {
	stroke: var(--ok);
	stroke-width: 3;
	stroke-linecap: round;
	stroke-linejoin: round;
	stroke-dasharray: 30;
	stroke-dashoffset: 30;
	animation: done-mark 0.3s ease 0.45s forwards;
}

@keyframes done-circle {
	to {
		stroke-dashoffset: 0;
	}
}

@keyframes done-mark {
	to {
		stroke-dashoffset: 0;
	}
}

/* Landing spot (--tx/--ty), color and timing all come from JS per dot
   (renderApplyDone() in home.js) - one keyframe shared by all eight. */
.confetti-dot {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 6px;
	height: 6px;
	border-radius: 2px;
	opacity: 0;
	animation: confetti-pop 0.7s ease 0.5s forwards;
}

@keyframes confetti-pop {
	0% {
		opacity: 0;
		transform: translate(-50%, -50%) scale(0.5) rotate(0deg);
	}
	30% {
		opacity: 1;
	}
	100% {
		opacity: 0;
		transform: translate(calc(-50% + var(--tx)), calc(-50% + var(--ty))) scale(1) rotate(200deg);
	}
}

/* --- team ---------------------------------------------------------------- */

/* Glass card grid; avatars wear the gradient ring. */
.team-grid {
	display: grid;
	/* Wide enough that a role name like "Georgia State Roleplay" has a real
	   shot at fitting on one line next to the avatar - see .team-role below
	   for the rest of that fit (narrower letter-spacing, smaller avatar). */
	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
	gap: 14px;
	margin-bottom: 8px;
}

.team-card {
	display: flex;
	align-items: center;
	gap: 14px;
	padding: 16px 18px;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius);
	backdrop-filter: var(--glass-blur-sm);
	-webkit-backdrop-filter: var(--glass-blur-sm);
	transition: transform var(--t-base), border-color var(--t-base), box-shadow var(--t-base);
}

.team-card:hover {
	transform: translateY(-3px);
	border-color: rgba(124, 92, 255, 0.35);
	box-shadow: var(--glow-lg);
}

/* A bright arc of the member's own Discord role color continuously
   traverses the card's border (--role-color set inline per member in
   home.js, from the role's live Discord color - falls back to the brand
   gradient's own purple when a role has none set). Uses ::before, not
   ::after - .team-card::after already carries the grid-entrance sweep
   further down this file, and an element can only have one of each.
   inset:0 with the padding+mask-composite trick keeps this exactly on the
   card's own edge rather than needing to overflow it, so it can't be
   clipped by anything and doesn't need its own overflow:hidden. Registered
   as a typed custom property so the browser interpolates the angle itself
   frame-to-frame - a plain (unregistered) custom property doesn't animate
   smoothly through @keyframes the same way. */
@property --angle {
	syntax: '<angle>';
	inherits: false;
	initial-value: 0deg;
}

.team-card::before {
	content: '';
	position: absolute;
	inset: 0;
	z-index: 1;
	border-radius: inherit;
	padding: 1.5px;
	background: conic-gradient(from var(--angle), transparent 0%, var(--role-color) 8%, transparent 16%, transparent 100%);
	-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
	-webkit-mask-composite: xor;
	mask-composite: exclude;
	pointer-events: none;
	animation: team-current 2.2s linear infinite;
}

@keyframes team-current {
	to {
		--angle: 360deg;
	}
}

.team-card img {
	width: 44px;
	height: 44px;
	border-radius: 50%;
	object-fit: cover;
	flex: none;
	padding: 2px;
	background: var(--grad);
}

/* Optional per-role affiliation badge (e.g. a partner server's logo). Sits
   at the end of the card, in the space .team-info's text usually doesn't
   reach (most cards are wider than their name/role/tags content). Higher
   specificity than ".team-card img" (two classes vs. one class + one
   element) so this wins on any property it sets - but padding/background
   below are set explicitly to NONE rather than just omitted, because
   winning specificity only matters per-property: a property this rule
   doesn't mention at all still falls through to .team-card img's value
   for it, which is exactly what silently kept the gradient ring showing
   through two rounds of "still not transparent" before this was caught. */
.team-card .team-badge {
	width: 52px;
	height: 52px;
	padding: 0;
	background: none;
	/* No rounding at all, not even a small one - confirmed by actually
	   downloading and looking at the configured logo (a Georgia-state-
	   outline badge on a transparent canvas): its own content sits close
	   enough to the canvas edge that even the small 8px corner rounding
	   tried before this was clipping a sliver of it. No border-radius
	   means no corner can ever clip into the image, whatever shape a
	   future logo turns out to be. */
	border-radius: 0;
	/* contain, not cover - same reasoning as .release-card img (Releases/
	   Partnerships cards): cover crops to fill the frame regardless of the
	   image's own shape, which is what was chopping detail off before. */
	object-fit: contain;
	flex: none;
}

.team-info {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
	/* Grows to fill whatever the card doesn't need for the avatar/gap/badge,
	   which is what pushes the badge to the card's true right edge instead
	   of it sitting immediately after a short name. Invisible on any card
	   without a badge - text inside still just left-aligns as before. */
	flex: 1;
}

.team-name {
	font-family: var(--font-display);
	font-weight: 600;
	font-size: var(--fs-sm);
	color: var(--text);
}

.team-role {
	font-family: var(--font-mono);
	font-size: 0.68rem;
	/* Tighter than the site's usual mono tracking (0.08em) specifically here -
	   a long partner/role name (teamStore allows up to 40 characters) needs
	   the room, and at this size the extra spacing wasn't buying legibility,
	   just forcing an awkward mid-phrase line break. line-height gives a
	   two-line name (still possible for anything even longer) room to breathe
	   instead of crowding the tag below it. */
	letter-spacing: 0.02em;
	line-height: 1.4;
	text-transform: uppercase;
	color: var(--brand-3);
}

.team-tags {
	display: flex;
	flex-wrap: wrap;
	gap: 3px 6px;
	margin-top: 3px;
}

.team-tag {
	font-family: var(--font-mono);
	font-size: 0.68rem;
	color: var(--text-dim);
}

/* --- releases -------------------------------------------------------------- */

.release-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
	gap: 18px;
	margin-bottom: 8px;
}

.release-card {
	display: flex;
	flex-direction: column;
	overflow: hidden;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius);
	backdrop-filter: var(--glass-blur-sm);
	-webkit-backdrop-filter: var(--glass-blur-sm);
	transition: transform var(--t-base), border-color var(--t-base), box-shadow var(--t-base);
}

.release-card:hover {
	transform: translateY(-3px);
	border-color: rgba(124, 92, 255, 0.35);
	box-shadow: var(--glow-lg);
}

/* Same durations as the /apply wizard's card entrance (see apply-screen-in
   further down this file) - a staggered fade-up plus the same sweep speed
   - for the pages that get browsed repeatedly rather than seen once. Just
   the two effects, not the wipe step apply's cards also get. --card-delay
   is set inline per card (home.js, the same i*70ms stagger .track-card
   already uses) and read by both rules below so the fade and the sweep
   stay in step with each other per card.

   Deliberately NOT animation-fill-mode:forwards on the card's own fade -
   .team-card/.release-card already have their own :hover lift via
   transition, and a forwards-filled animation keeps "owning" transform
   forever after it finishes, which would permanently block that hover
   transition from ever taking effect again. Dropping forwards here is
   safe specifically because the animation's own 100% keyframe (opacity:1,
   translateY(0)) already matches the element's un-animated default, so
   there's nothing to snap back to - unlike the sweep below, whose default
   position sits fully visible mid-card, so it keeps "both" deliberately
   (its ::after pseudo-element has no competing :hover transition to
   block). */
.team-card,
.release-card,
.review-card,
.rules li {
	position: relative;
	overflow: hidden;
	animation: grid-card-in 0.9s cubic-bezier(0.16, 1, 0.3, 1) backwards;
	animation-delay: var(--card-delay, 0ms);
}

.team-card::after,
.release-card::after,
.review-card::after {
	content: '';
	position: absolute;
	inset: -20px -100px;
	z-index: -1;
	background: linear-gradient(105deg, transparent 42%, rgba(168, 85, 247, 0.55) 50%, rgba(56, 189, 248, 0.55) 54%, transparent 62%);
	pointer-events: none;
	mix-blend-mode: screen;
	animation: grid-card-sweep 2.7s cubic-bezier(0.4, 0, 0.2, 1) both;
	animation-delay: calc(var(--card-delay, 0ms) + 0.5s);
}

/* Rules gets the Team-card ring treatment instead of the sweep above - a
   thin arc travels the card's border rather than a light band crossing its
   face. Same @property --angle and team-current keyframe Team already
   registers further down this file (reused as-is, not redeclared). Uses
   ::after, not ::before like Team's does - .rules li::before is already the
   big numbered counter, so the ring has to live on the other pseudo here.
   Team's arc is a single flat --role-color; Rules has no equivalent
   per-item color to draw from, so this one sweeps through the site's own
   brand-1..4 stops instead - still a single arc, just multi-hued. */
.rules li::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: 1;
	border-radius: inherit;
	padding: 1.5px;
	background: conic-gradient(
		from var(--angle),
		transparent 0%,
		var(--brand-1) 4%,
		var(--brand-2) 8%,
		var(--brand-3) 12%,
		var(--brand-4) 16%,
		transparent 22%,
		transparent 100%
	);
	-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
	-webkit-mask-composite: xor;
	mask-composite: exclude;
	pointer-events: none;
	animation: team-current 2.2s linear infinite;
}

@keyframes grid-card-in {
	from {
		opacity: 0;
		transform: translateY(12px);
	}
	to {
		opacity: 1;
		transform: translateY(0);
	}
}

@keyframes grid-card-sweep {
	from {
		transform: translateX(-130%);
	}
	to {
		transform: translateX(130%);
	}
}

/* --- reviews --------------------------------------------------------------- */
/* .review-card shares .release-grid/.release-card's grid layout and the
   fade-up + sweep entrance above - only its own internal content differs
   (avatar + name + stars + quote instead of an image + tags + title). */

.review-card:hover {
	transform: translateY(-3px);
	border-color: rgba(124, 92, 255, 0.35);
	box-shadow: var(--glow-lg);
}

.review-card {
	display: flex;
	gap: 14px;
	padding: 16px 18px;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius);
	backdrop-filter: var(--glass-blur-sm);
	-webkit-backdrop-filter: var(--glass-blur-sm);
	transition: transform var(--t-base), border-color var(--t-base), box-shadow var(--t-base);
}

.review-avatar {
	flex: none;
	width: 40px;
	height: 40px;
	border-radius: 50%;
	object-fit: cover;
}

.review-body {
	display: flex;
	flex-direction: column;
	gap: 4px;
	min-width: 0;
}

.review-head {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
}

.review-name {
	font-family: var(--font-display);
	font-weight: 600;
	font-size: var(--fs-sm);
	color: var(--text);
}

.review-verified {
	display: inline-flex;
	align-items: center;
	gap: 3px;
	font-family: var(--font-mono);
	font-size: 0.66rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ok);
}

.review-stars {
	color: var(--brand-3);
	font-size: 0.85rem;
	letter-spacing: 0.08em;
}

.review-quote {
	margin: 2px 0 0;
	font-size: var(--fs-sm);
	color: var(--text-dim);
	line-height: 1.5;
}

/* The small release-name pill on a tagged review's head row - a plain .pill,
   just accent-tinted so it doesn't read as a status like the moderation
   pills below it can. */
.review-release-tag {
	border-color: rgba(124, 92, 255, 0.35);
	background: var(--accent-wash);
	color: var(--brand-3);
}

/* --- review summary + release filter --------------------------------------
   Sit between .page-sub and the grid. Both are derived from the currently-
   loaded review list in home.js (no extra fetch) - the summary always
   counts approved reviews only, the filter's options are only releases
   that actually have a review, so there's never a filter with zero results. */
.review-summary {
	display: flex;
	align-items: center;
	gap: 10px;
	margin-top: 16px;
}

.review-summary-score {
	font-family: var(--font-display);
	font-weight: 700;
	font-size: 1.6rem;
	line-height: 1;
	background: var(--grad);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

.review-summary-stars {
	color: var(--brand-3);
	font-size: 1rem;
	letter-spacing: 0.08em;
}

.review-summary-count {
	font-size: var(--fs-sm);
}

.review-filter {
	display: flex;
	align-items: center;
	gap: 10px;
	margin-top: 14px;
}

.review-filter label {
	font-size: 0.68rem;
	letter-spacing: 0.12em;
}

.review-filter .field-select {
	width: auto;
	min-width: 200px;
}

/* Dark <select>, same family as .apply-form input/.profile-fields input -
   used by both the release filter above and the write-form's release
   picker below. */
.field-select {
	width: 100%;
	padding: 10px 14px;
	background: rgba(255, 255, 255, 0.04);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius-sm);
	color: var(--text);
	font-family: var(--font-body);
	font-size: var(--fs-sm);
	cursor: pointer;
	transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}

.field-select:focus {
	outline: none;
	border-color: var(--brand-2);
	box-shadow: 0 0 0 3px rgba(124, 92, 255, 0.18);
	background: rgba(255, 255, 255, 0.06);
}

/* Honcho-only inline moderation row - only ever rendered when isHoncho() is
   true (see the /reviews route in home.js), so a regular member never sees
   this markup at all. Reuses the existing .pill-approved/.pill-denied/
   .pill-pending classes (originally built for applicationBlockedPage())
   rather than inventing new status colors. */
.review-mod {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 8px;
	margin-top: 10px;
	padding-top: 10px;
	border-top: 1px solid var(--glass-border);
}

.mod-btn {
	padding: 4px 12px;
	border: 1px solid var(--glass-border-hi);
	border-radius: var(--radius-pill);
	background: var(--glass);
	color: var(--text);
	font-family: var(--font-mono);
	font-size: 0.68rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	cursor: pointer;
	transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}

.mod-btn:hover {
	background: var(--glass-strong);
	border-color: rgba(255, 255, 255, 0.30);
}

.mod-btn:disabled {
	opacity: 0.5;
	cursor: default;
}

.mod-btn-ok {
	border-color: rgba(62, 207, 142, 0.35);
	color: var(--ok);
}

.mod-btn-ok:hover {
	background: rgba(62, 207, 142, 0.12);
	border-color: rgba(62, 207, 142, 0.5);
}

.mod-btn-bad {
	border-color: rgba(229, 72, 77, 0.35);
	color: var(--bad);
}

.mod-btn-bad:hover {
	background: rgba(229, 72, 77, 0.12);
	border-color: rgba(229, 72, 77, 0.5);
}

/* The armed "click again to delete" state - see armReviewDelete() in
   home.js. Solid fill instead of the outline the other .mod-btn states
   use, so it reads as unmistakably different, plus a pulse so it's not
   missed if the confirming button isn't where the eye already is. */
.mod-btn-confirming {
	background: var(--bad);
	border-color: var(--bad);
	color: #fff;
	animation: mod-confirm-pulse 1s ease-in-out infinite;
}

@keyframes mod-confirm-pulse {
	0%,
	100% {
		box-shadow: 0 0 0 0 rgba(229, 72, 77, 0.45);
	}
	50% {
		box-shadow: 0 0 0 6px rgba(229, 72, 77, 0);
	}
}

.mod-status {
	font-size: var(--fs-sm);
}

/* The inline "write a review" card - always the grid's first item, spanning
   the full row so it reads as a toolbar rather than competing with the
   review cards it sits above. Static variant (signed out, or already has a
   pending review) skips the accordion/button chrome entirely - see the
   "write-card static" branch in home.js's /reviews route. */
.write-card {
	grid-column: 1 / -1;
	background: var(--glass);
	border: 1px solid var(--glass-border-hi);
	border-radius: var(--radius);
	overflow: hidden;
}

.write-card.static {
	padding: 16px 18px;
}

.write-prompt {
	display: flex;
	align-items: center;
	gap: 12px;
	width: 100%;
	padding: 16px 18px;
	background: none;
	border: 0;
	cursor: pointer;
	font-family: var(--font-display);
	font-weight: 600;
	font-size: var(--fs-sm);
	color: var(--text);
	text-align: left;
}

.write-prompt .plus {
	flex: none;
	width: 28px;
	height: 28px;
	border-radius: 50%;
	background: var(--grad);
	display: grid;
	place-items: center;
	color: #fff;
	font-size: 1.05rem;
	line-height: 1;
	transition: transform var(--t-base);
}

/* Height-grow via the 0fr->1fr grid-template-rows trick (needs min-height:0
   + overflow:hidden on the inner wrapper to actually collapse to zero, not
   just "auto"), same speed/easing as the picker cards' own entrance so the
   whole site's motion reads as one language rather than a handful of
   differently-tuned transitions. */
.write-form {
	display: grid;
	grid-template-rows: 0fr;
	opacity: 0;
	transition: grid-template-rows 0.4s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.3s ease;
}

.write-form > * {
	min-height: 0;
	overflow: hidden;
}

#review-write.open .write-form {
	grid-template-rows: 1fr;
	opacity: 1;
}

#review-write.open .plus {
	transform: rotate(135deg);
}

.write-form .inner {
	padding: 0 18px 18px;
	display: flex;
	flex-direction: column;
	gap: 12px;
}

.stars-pick {
	display: flex;
	gap: 6px;
	font-size: 1.4rem;
	color: var(--glass-border-hi);
	line-height: 1;
	cursor: pointer;
	width: fit-content;
}

.stars-pick span {
	transition: color var(--t-fast);
}

.stars-pick span.filled {
	color: var(--brand-3);
}

.write-form textarea {
	width: 100%;
	min-height: 80px;
	padding: 10px 14px;
	background: rgba(255, 255, 255, 0.04);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius-sm);
	color: var(--text);
	font-family: var(--font-body);
	font-size: var(--fs-sm);
	line-height: 1.55;
	resize: vertical;
	transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}

.write-form textarea:focus {
	outline: none;
	border-color: var(--brand-2);
	box-shadow: 0 0 0 3px rgba(124, 92, 255, 0.18);
	background: rgba(255, 255, 255, 0.06);
}

.textarea-wrap {
	display: flex;
	flex-direction: column;
	gap: 4px;
}

.char-counter {
	align-self: flex-end;
}

.write-actions {
	display: flex;
	align-items: center;
	gap: 14px;
}

/* contain, not cover - release entries and partner logos both vary wildly in
   shape (screenshots, square logos, wide wordmarks, tall crests), and cover
   crops to fill the 16:9 box regardless, chopping detail off anything that
   isn't already landscape-shaped (confirmed live: a square bot-service logo
   got zoomed/cropped by cover). contain shows the whole image, letterboxed,
   on a padded backdrop instead - same fix already applied to .team-badge and
   .partner-card img for the same underlying reason. */
.release-card img {
	width: 100%;
	aspect-ratio: 16 / 9;
	object-fit: contain;
	padding: 20px;
	box-sizing: border-box;
	background: var(--glass);
}

/* Only present on Releases cards (Partnerships has no media field), and
   only wraps the img when a card actually opens a lightbox - a plain
   single-image release has neither this wrapper nor the badge below, so it
   renders exactly as it did before this existed. */
.release-media-wrap {
	position: relative;
}

.release-card[data-media] {
	cursor: pointer;
}

.media-badge {
	position: absolute;
	right: 10px;
	bottom: 10px;
	padding: 3px 9px;
	background: rgba(10, 12, 24, 0.72);
	border: 1px solid var(--glass-border-hi);
	border-radius: 999px;
	font-size: 0.75rem;
	color: var(--text);
}

.release-body {
	display: flex;
	flex-direction: column;
	gap: 6px;
	padding: 14px 16px 18px;
}

.release-tags {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
}

.release-title {
	font-family: var(--font-display);
	font-weight: 600;
	font-size: var(--fs-lead);
	color: var(--text);
}

.release-desc {
	font-size: var(--fs-sm);
	color: var(--text-dim);
	line-height: 1.5;
}

/* --- narrow -------------------------------------------------------------- */

@media (max-width: 960px) {
	.hero {
		grid-template-columns: 1fr;
		gap: 40px;
		text-align: center;
		padding: 60px 0 44px;
	}

	.hero-title,
	.hero-sub {
		margin-left: auto;
		margin-right: auto;
	}

	.hero-cta {
		justify-content: center;
	}

	.hero-card {
		justify-self: center;
		max-width: 420px;
	}

	.benefits {
		grid-template-columns: 1fr 1fr;
		row-gap: 48px;
	}
}

@media (max-width: 720px) {
	.split {
		grid-template-columns: 1fr;
	}

	.rules {
		grid-template-columns: 1fr;
	}
}

@media (max-width: 620px) {
	/* Shrinks rather than disappearing - .brand-tag already has
	   overflow:hidden/text-overflow:ellipsis/white-space:nowrap (see its base
	   rule above) and .brand-text has min-width:0, so it truncates cleanly
	   into whatever space is left instead of losing the tagline entirely on
	   the smallest screens. */
	.brand-tag {
		font-size: 0.6875rem;
		letter-spacing: 0.12em;
	}

	.brand img {
		width: 38px;
		height: 38px;
	}

	.brand-name {
		font-size: 1.15rem;
	}

	.brandbar {
		padding: 13px 16px 10px;
	}

	.navbar {
		justify-content: flex-start;
		gap: 2px;
		padding: 0 16px 10px;
	}

	.navbar a {
		padding: 9px 12px;
		font-size: 0.8125rem;
		letter-spacing: 0.06em;
	}

	footer {
		padding: 20px 16px 36px;
	}

	.hero {
		padding: 48px 0 34px;
	}

	.account-hero {
		flex-direction: column;
		text-align: center;
	}

	.benefits {
		grid-template-columns: 1fr;
	}

	.legal {
		padding: 20px 18px;
	}
}

/* --- account ------------------------------------------------------------- */

.account {
	max-width: 680px;
}

.account .account-hero {
	padding-bottom: 22px;
	border-bottom: 1px solid var(--border-soft);
}

.account .stats {
	margin-top: 20px;
}

.stats-sm .stat {
	padding: 14px 18px;
}

.stats-sm .stat-value {
	font-size: 1.125rem;
	font-weight: 600;
	letter-spacing: 0;
}

.account .sec {
	margin-top: 24px;
	padding-top: 20px;
}

.role-list {
	display: flex;
	flex-wrap: wrap;
	gap: 7px;
	margin: 0;
}

.role-list .role {
	margin: 0;
}

/* --- Chat widget --------------------------------------------------------- */

.chat {
	position: fixed;
	right: 20px;
	bottom: var(--chat-lift, 96px);
	z-index: 60;
	font-family: var(--font-body);
	transition: bottom 0.15s;
}

/* A gradient circle with a glow; lifts on hover like every other CTA. */
.chat-toggle {
	display: grid;
	place-items: center;
	width: 56px;
	height: 56px;
	border-radius: 50%;
	border: 0;
	background: var(--grad);
	color: #fff;
	cursor: pointer;
	box-shadow: var(--glow-md);
	transition: transform var(--t-base), box-shadow var(--t-base);
}

.chat-toggle:hover {
	transform: translateY(-3px) scale(1.05);
	box-shadow: var(--glow-lg);
}

.chat-toggle-icon {
	font-family: var(--font-display);
	font-size: 24px;
	line-height: 1;
	color: #fff;
}

.chat.is-open .chat-toggle {
	opacity: 0;
	pointer-events: none;
}

.chat-panel {
	position: absolute;
	right: 0;
	bottom: 0;
	display: none;
	flex-direction: column;
	width: min(380px, calc(100vw - 40px));
	height: min(540px, calc(100vh - var(--chat-lift, 96px) - 30px));
	background: rgba(13, 16, 32, 0.92);
	backdrop-filter: var(--glass-blur);
	-webkit-backdrop-filter: var(--glass-blur);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius-lg);
	box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
	overflow: hidden;
}

.chat.is-open .chat-panel {
	display: flex;
}

.chat-head {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 12px;
	padding: 14px 16px;
	border-bottom: 1px solid var(--glass-border);
}

.chat-head strong {
	display: block;
	font-family: var(--font-display);
	font-size: 14px;
}

.chat-sub {
	display: block;
	margin-top: 2px;
	font-size: 11px;
	color: var(--text-mute, var(--text-dim));
}

.chat-actions {
	display: flex;
	align-items: center;
	gap: 6px;
	flex: none;
}

.chat-reset {
	padding: 4px 11px;
	font: inherit;
	font-size: 11px;
	color: var(--text-dim);
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius-pill);
	cursor: pointer;
	white-space: nowrap;
	transition: color var(--t-fast), border-color var(--t-fast);
}

.chat-reset:hover {
	color: var(--text);
	border-color: var(--glass-border-hi);
}

.chat-close {
	flex: none;
	width: 28px;
	height: 28px;
	border: 1px solid transparent;
	border-radius: var(--radius-sm);
	background: none;
	color: var(--text-dim);
	font-size: 18px;
	line-height: 1;
	cursor: pointer;
}

.chat-close:hover {
	border-color: var(--glass-border);
	color: var(--text);
}

.chat-log {
	flex: 1;
	overflow-y: auto;
	padding: 16px;
	display: flex;
	flex-direction: column;
	gap: 10px;
}

.chat.is-paused .chat-toggle {
	opacity: 0.65;
}

.chat-paused-note {
	padding: 10px 12px;
	border: 1px solid var(--glass-border);
	border-left: 2px solid var(--warn, #f2a93b);
	border-radius: var(--radius-sm);
	background: var(--glass);
}

.chat-intro {
	color: var(--text-dim);
	font-size: 13px;
	line-height: 1.6;
}

.chat-msg {
	max-width: 85%;
	padding: 9px 13px;
	font-size: 13px;
	line-height: 1.55;
	white-space: pre-wrap;
	overflow-wrap: anywhere;
}

.chat-msg-user {
	align-self: flex-end;
	background: var(--grad);
	color: #fff;
	border-radius: 14px 14px 4px 14px;
}

.chat-msg-assistant {
	align-self: flex-start;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	border-radius: 14px 14px 14px 4px;
}

.chat-status {
	margin: 0;
	padding: 0 16px 6px;
	min-height: 16px;
	font-size: 11px;
	color: var(--text-dim);
}

.chat-status.is-error {
	color: var(--bad, #e5484d);
}

.chat-form {
	display: flex;
	gap: 8px;
	padding: 12px 16px 16px;
	border-top: 1px solid var(--glass-border);
}

.chat-input {
	flex: 1;
	min-width: 0;
	padding: 9px 13px;
	font: inherit;
	font-size: 13px;
	color: var(--text);
	background: rgba(255, 255, 255, 0.04);
	border: 1px solid var(--glass-border);
	border-radius: var(--radius-sm);
	transition: border-color var(--t-fast), box-shadow var(--t-fast);
}

.chat-input:focus {
	outline: none;
	border-color: var(--brand-2);
	box-shadow: 0 0 0 3px rgba(124, 92, 255, 0.18);
}

.chat-send {
	flex: none;
	padding: 0 16px;
	font: inherit;
	font-family: var(--font-display);
	font-weight: 600;
	font-size: 13px;
	color: #fff;
	background: var(--grad);
	border: 0;
	border-radius: var(--radius-sm);
	cursor: pointer;
	box-shadow: var(--glow-sm);
	transition: transform var(--t-base), box-shadow var(--t-base);
}

.chat-send:hover:not(:disabled) {
	transform: translateY(-1px);
	box-shadow: var(--glow-md);
}

.chat-send:disabled {
	opacity: 0.5;
	cursor: default;
}

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

@media (max-width: 520px) {
	.chat {
		right: 14px;
	}

	.chat-panel {
		position: fixed;
		inset: 0;
		width: 100vw;
		height: 100dvh;
		border: 0;
		border-radius: 0;
	}
}

/* --- release media lightbox ------------------------------------------------ */
/* Built and torn down entirely in JS (see home.js) - never present in the DOM
   until a release card with extra media is clicked. */

#lightbox {
	position: fixed;
	inset: 0;
	z-index: 200;
	display: grid;
	place-items: center;
	padding: 40px;
	background: rgba(5, 6, 14, 0.88);
	backdrop-filter: blur(6px);
	-webkit-backdrop-filter: blur(6px);
}

.lightbox-inner {
	position: relative;
	display: grid;
	place-items: center;
	max-width: min(920px, 100%);
	max-height: 100%;
}

.lightbox-media {
	display: grid;
	place-items: center;
	max-width: 100%;
	max-height: calc(100dvh - 120px);
}

.lightbox-media img,
.lightbox-media video {
	max-width: 100%;
	max-height: calc(100dvh - 120px);
	border-radius: var(--radius);
	box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}

.lightbox-close,
.lightbox-prev,
.lightbox-next {
	position: absolute;
	display: grid;
	place-items: center;
	width: 40px;
	height: 40px;
	border: 1px solid var(--glass-border-hi);
	border-radius: 50%;
	background: rgba(13, 16, 32, 0.8);
	color: var(--text);
	font-size: 1.25rem;
	line-height: 1;
	cursor: pointer;
	transition: background var(--t-base), border-color var(--t-base);
}

.lightbox-close:hover,
.lightbox-prev:hover,
.lightbox-next:hover {
	background: rgba(124, 92, 255, 0.25);
	border-color: rgba(124, 92, 255, 0.5);
}

.lightbox-close {
	top: -18px;
	right: -18px;
}

.lightbox-prev,
.lightbox-next {
	top: 50%;
	transform: translateY(-50%);
}

.lightbox-prev {
	left: -18px;
}

.lightbox-next {
	right: -18px;
}

.lightbox-dots {
	position: absolute;
	bottom: -30px;
	left: 50%;
	transform: translateX(-50%);
	display: flex;
	gap: 6px;
}

.lightbox-dot {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: var(--glass-border-hi);
}

.lightbox-dot.active {
	background: var(--brand-3);
}

@media (max-width: 620px) {
	#lightbox {
		padding: 20px;
	}

	.lightbox-close {
		top: -14px;
		right: 0;
	}

	.lightbox-prev {
		left: -6px;
	}

	.lightbox-next {
		right: -6px;
	}
}

/* --- fallbacks & motion --------------------------------------------------- */

/* Browsers without backdrop-filter get solid raised surfaces. */
@supports not (backdrop-filter: blur(1px)) {
	.topbar,
	.account-menu,
	.chat-panel,
	.hero-card {
		background: var(--bg-raised);
	}

	.panel,
	.stat,
	.team-card,
	.release-card,
	.benefit,
	.rules li,
	.legal {
		background: #0c0f1d;
	}
}

/* Kills every loop - the mesh script exits early on its own. */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.001ms !important;
		scroll-behavior: auto !important;
	}
}

/* --- sign-in overlay ------------------------------------------------------ */

/* Shown while the OAuth popup does its round trip (home.js). Glass over the
   page rather than a blackout: the site staying visible underneath is the
   point of the popup flow. */
.auth-overlay {
	position: fixed;
	inset: 0;
	z-index: 200;
	display: grid;
	place-items: center;
	background: color-mix(in srgb, var(--bg) 72%, transparent);
	backdrop-filter: var(--glass-blur-sm);
	-webkit-backdrop-filter: var(--glass-blur-sm);
	opacity: 0;
	visibility: hidden;
	pointer-events: none;
	transition:
		opacity 0.25s ease,
		visibility 0.25s ease;
}

.auth-overlay.show {
	opacity: 1;
	visibility: visible;
	pointer-events: auto;
}

.auth-overlay-inner {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 18px;
	padding: 34px 46px;
	border-radius: 22px;
	background: var(--glass);
	border: 1px solid var(--glass-border);
	backdrop-filter: var(--glass-blur);
	-webkit-backdrop-filter: var(--glass-blur);
}

.auth-spinner {
	width: 44px;
	height: 44px;
	border-radius: 50%;
	border: 3px solid var(--border);
	border-top-color: var(--brand-2);
	animation: auth-spin 0.8s linear infinite;
}

@keyframes auth-spin {
	to {
		transform: rotate(360deg);
	}
}

.auth-overlay-text {
	margin: 0;
	color: var(--text-dim);
	font-size: 0.95rem;
}
