/* ==========================================================================
   design-system.css
   Tokens, base styles, primitive components, and utility classes.
   Every other stylesheet imports this file first.
   ========================================================================== */

:root {
	color-scheme: dark;

	/* Surfaces */
	--bg: #07090f;
	--surface-1: rgba(255, 255, 255, 0.04);
	--surface-2: rgba(255, 255, 255, 0.07);
	--surface-3: rgba(255, 255, 255, 0.1);
	--surface-solid: #0f1320;
	--border: rgba(255, 255, 255, 0.08);
	--border-strong: rgba(255, 255, 255, 0.14);
	--border-accent: rgba(239, 68, 68, 0.4);

	/* Text */
	--text: #f4f5f9;
	--text-2: #a8afc1;
	--text-3: #929bae;
	--text-on-accent: #07090f;

	/* Accents — aurora palette */
	--violet: #ef4444;
	--blue: #f87171;
	--cyan: #7f1d1d;
	--pink: #fca5a5;
	--aurora: linear-gradient(
		135deg,
		#ef4444 0%,
		#f87171 35%,
		#7f1d1d 70%,
		#fca5a5 100%
	);
	--aurora-soft: linear-gradient(
		135deg,
		rgba(239, 68, 68, 0.5),
		rgba(248, 113, 113, 0.5),
		rgba(127, 29, 29, 0.5),
		rgba(252, 165, 165, 0.5)
	);
	--aurora-violet: linear-gradient(135deg, #ef4444, #991b1b);
	--aurora-blue: linear-gradient(135deg, #f87171, #7f1d1d);
	--aurora-warm: linear-gradient(135deg, #fca5a5, #f97316);

	/* Semantic */
	--success: #22c55e;
	--warning: #fbbf24;
	--danger: #f87171;
	--score-pos: #22c55e;
	--score-mixed: #fbbf24;
	--score-neg: #f87171;

	/* Status hues. These sit outside the aurora family on purpose: they
	   carry meaning (a tracking status, a free game, a rank medal) rather
	   than decoration, and the aurora is the only decorative colour.
	   "Want" is a cool blue so the two commonest library states can be
	   told apart at a glance — want (blue, intent) and played (red,
	   brand) used to be two shades of the same red. */
	--status-want: #93c5fd;
	/* Solid-fill variant for white text on top: #3b82f6 measured 3.7:1
	   against white (below the 4.5:1 small-text floor), so the strong
	   tone is a step darker. */
	--status-want-strong: #2563eb;
	--status-playing: #6ee7b7;
	--status-played: #fecaca;
	--status-dropped: #fdba74;
	--green-soft: #86efac;
	--amber-soft: #fef3c7;

	/* Rank medals */
	--rank-1: #fbbf24;
	--rank-2: #e5e7eb;
	--rank-3: #fb923c;

	/* Hover variants: the semantic colours, lightened for the hover state */
	--danger-hover: #fda4af;
	--warning-hover: #fcd34d;

	/* Layer scale. Every z-index in the app is one of these: an ad-hoc
	   number is how two overlays end up fighting over the same layer. */
	--z-deep: -2;
	--z-base: -1;
	--z-content: 0;
	--z-raised: 1;
	--z-overlay: 2;
	--z-sticky: 3;
	--z-toolbar: 5;
	--z-popover: 10;
	--z-nav: 50;
	--z-skip: 60;
	--z-modal: 100;

	/* ponytail: soft-accent derivation for low-alpha focus shadows and tints.
	   Half-strength of --border-accent; same hue, lower alpha. */
	--border-accent-soft: rgba(239, 68, 68, 0.18);

	/* ponytail: warning-color derivations. --warning is the base; --warning-soft
	   is the low-alpha surface tint; --warning-border is the 30% border;
	   --warning-text is a brighter variant for text on dark surfaces. */
	--warning-soft: rgba(251, 191, 36, 0.12);
	--warning-border: rgba(251, 191, 36, 0.3);
	--warning-text: #fde68a;

	/* Typography */
	--font-sans:
		"Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
	--font-display: "Inter", system-ui, sans-serif;
	--font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;

	--text-xs: 0.75rem;
	--text-sm: 0.875rem;
	--text-base: 0.9375rem;
	--text-md: 1rem;
	--text-lg: 1.125rem;
	--text-xl: 1.25rem;
	--text-2xl: 1.5rem;
	--text-3xl: 2rem;
	--text-4xl: 2.5rem;
	--text-5xl: 3.5rem;

	--leading-tight: 1.15;
	--leading-snug: 1.3;
	--leading-normal: 1.5;
	--leading-relaxed: 1.65;

	--tracking-tight: -0.02em;
	--tracking-snug: -0.01em;
	--tracking-wide: 0.06em;

	/* Spacing (4px base) */
	--s-1: 4px;
	--s-2: 8px;
	--s-3: 12px;
	--s-4: 16px;
	--s-5: 20px;
	--s-6: 24px;
	--s-7: 32px;
	--s-8: 40px;
	--s-9: 48px;
	--s-10: 64px;
	--s-12: 96px;

	/* Radius */
	--r-sm: 8px;
	--r-md: 12px;
	--r-lg: 16px;
	--r-xl: 20px;
	--r-2xl: 28px;
	--r-full: 9999px;

	/* Motion */
	--ease: cubic-bezier(0.4, 0, 0.2, 1);
	--ease-out: cubic-bezier(0, 0, 0.2, 1);
	--dur-fast: 150ms;
	--dur: 220ms;
	--dur-slow: 400ms;

	/* Effects */
	--glass-blur: saturate(180%) blur(20px);
	--glass-blur-sm: saturate(180%) blur(12px);
	--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3);
	--shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
	--shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.5);
	--shadow-glow: 0 0 40px rgba(239, 68, 68, 0.3);
	--inner-hi: inset 0 1px 0 rgba(255, 255, 255, 0.06);
	--inner-hi-strong: inset 0 1px 0 rgba(255, 255, 255, 0.1);

	/* Layout */
	--container: 1200px;
	--nav-h: 64px;
}

/* ==========================================================================
   Base reset
   ========================================================================== */

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

html {
	-webkit-text-size-adjust: 100%;
	-moz-text-size-adjust: 100%;
	text-size-adjust: 100%;
	scroll-behavior: smooth;
	-webkit-tap-highlight-color: transparent;
}

body {
	margin: 0;
	min-height: 100vh;
	font-family: var(--font-sans);
	font-size: var(--text-base);
	line-height: var(--leading-normal);
	color: var(--text);
	background: var(--bg);
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	text-rendering: optimizeLegibility;
	font-feature-settings: "cv11", "ss01", "ss03";
	overflow-x: hidden;
}

h1,
h2,
h3,
h4,
h5,
h6 {
	margin: 0;
	font-family: var(--font-display);
	font-weight: 600;
	line-height: var(--leading-tight);
	letter-spacing: var(--tracking-snug);
	color: var(--text);
}

p {
	margin: 0;
}

a {
	color: var(--pink);
	text-decoration: underline;
	text-decoration-color: rgba(252, 165, 165, 0.45);
	text-decoration-thickness: 1px;
	text-underline-offset: 2px;
	transition:
		color var(--dur-fast) var(--ease),
		text-decoration-color var(--dur-fast) var(--ease);
}

a:hover {
	color: #fff;
	text-decoration-color: rgba(255, 255, 255, 0.7);
}

a:focus-visible {
	outline: 2px solid var(--violet);
	outline-offset: 2px;
	border-radius: 4px;
}

img,
svg,
video {
	display: block;
	max-width: 100%;
}

button,
input,
select,
textarea {
	font: inherit;
	color: inherit;
}

button {
	cursor: pointer;
	background: none;
	border: none;
	padding: 0;
}

::selection {
	background: rgba(239, 68, 68, 0.4);
	color: var(--text);
}

::-webkit-scrollbar {
	width: 10px;
	height: 10px;
}

::-webkit-scrollbar-track {
	background: transparent;
}

::-webkit-scrollbar-thumb {
	background: rgba(255, 255, 255, 0.08);
	border-radius: var(--r-full);
	border: 2px solid var(--bg);
}

::-webkit-scrollbar-thumb:hover {
	background: rgba(255, 255, 255, 0.15);
}

/* ==========================================================================
   Aurora background — fixed, drifts slowly, behind everything
   ========================================================================== */

.aurora-bg {
	position: fixed;
	inset: 0;
	z-index: var(--z-base);
	pointer-events: none;
	overflow: hidden;
	background: var(--bg);
}

.aurora-bg::before,
.aurora-bg::after,
.aurora-bg > span {
	content: "";
	position: absolute;
	width: 80vmax;
	height: 80vmax;
	border-radius: 50%;
	/* A wide blur and a long falloff. The old edge was visible, and a
	   visible edge is what turns an ambient layer into a spotlight. */
	filter: blur(160px);
	opacity: 0;
	transition: opacity 1.2s var(--ease-out);
	will-change: transform;
}

/* Red, top left. It sits far enough past the corner that its gradient
   never resolves into an edge on screen. */
.aurora-bg::before {
	top: -38vmax;
	left: -32vmax;
	background: radial-gradient(
		circle,
		rgba(239, 68, 68, 0.34) 0%,
		rgba(239, 68, 68, 0.12) 38%,
		transparent 72%
	);
	animation: aurora-drift-1 60s var(--ease) infinite;
}

/* Deep crimson, bottom right. */
.aurora-bg::after {
	bottom: -38vmax;
	right: -32vmax;
	background: radial-gradient(
		circle,
		rgba(127, 29, 29, 0.4) 0%,
		rgba(127, 29, 29, 0.14) 40%,
		transparent 74%
	);
	animation: aurora-drift-2 70s var(--ease) infinite;
}

/* Soft rose, top right. It used to sit dead centre, which made it the
   hotspot of the red blob rather than a third light of its own. */
.aurora-bg > span {
	top: -22vmax;
	right: -18vmax;
	background: radial-gradient(
		circle,
		rgba(252, 165, 165, 0.24) 0%,
		rgba(252, 165, 165, 0.08) 40%,
		transparent 72%
	);
	animation: aurora-drift-3 80s var(--ease) infinite;
}

body.is-loaded .aurora-bg::before,
body.is-loaded .aurora-bg::after,
body.is-loaded .aurora-bg > span {
	opacity: 1;
}

@keyframes aurora-drift-1 {
	0%,
	100% {
		transform: translate(0, 0) scale(1);
	}
	50% {
		transform: translate(14vmax, 8vmax) scale(1.08);
	}
}

@keyframes aurora-drift-2 {
	0%,
	100% {
		transform: translate(0, 0) scale(1);
	}
	50% {
		transform: translate(-12vmax, -8vmax) scale(1.12);
	}
}

@keyframes aurora-drift-3 {
	0%,
	100% {
		transform: translate(0, 0) scale(1);
	}
	50% {
		transform: translate(-14vmax, 12vmax) scale(1.15);
	}
}

@media (prefers-reduced-motion: reduce) {
	.aurora-bg::before,
	.aurora-bg::after,
	.aurora-bg > span {
		animation: none;
		opacity: 1;
	}
	*,
	*::before,
	*::after {
		animation-duration: 0.001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.001ms !important;
		scroll-behavior: auto !important;
	}
}

/* ==========================================================================
   Container
   ========================================================================== */

.container {
	width: 100%;
	max-width: var(--container);
	margin: 0 auto;
	padding-left: var(--s-6);
	padding-right: var(--s-6);
}

@media (max-width: 640px) {
	.container {
		padding-left: var(--s-4);
		padding-right: var(--s-4);
	}
}

/* ==========================================================================
   Buttons
   ========================================================================== */

.btn {
	--btn-bg: var(--surface-2);
	--btn-fg: var(--text);
	--btn-border: var(--border);

	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--s-2);
	height: 40px;
	min-height: 44px;
	padding: 0 var(--s-5);
	font-family: var(--font-sans);
	font-size: var(--text-sm);
	font-weight: 600;
	letter-spacing: var(--tracking-snug);
	color: var(--btn-fg);
	background: var(--btn-bg);
	border: 1px solid var(--btn-border);
	border-radius: var(--r-md);
	cursor: pointer;
	text-decoration: none;
	white-space: nowrap;
	user-select: none;
	-webkit-user-select: none;
	transition:
		transform var(--dur-fast) var(--ease),
		background var(--dur) var(--ease),
		border-color var(--dur) var(--ease),
		box-shadow var(--dur) var(--ease),
		color var(--dur) var(--ease);
	box-shadow: var(--inner-hi);
}

.btn:hover {
	background: var(--surface-3);
	border-color: var(--border-strong);
	text-decoration: none;
	color: var(--text);
	transform: translateY(-1px);
}

.btn:active {
	transform: translateY(0);
}

.btn:focus-visible {
	outline: none;
	border-color: var(--border-accent);
	box-shadow:
		0 0 0 3px rgba(239, 68, 68, 0.25),
		var(--inner-hi);
}

.btn--primary {
	--btn-bg: var(--aurora-violet);
	--btn-fg: white;
	--btn-border: transparent;
	background: var(--aurora-violet);
	border: 1px solid transparent;
	position: relative;
	overflow: hidden;
	/* The ::after cross-fade sits at --z-base so the label stays on top.
	   Most .btn--primary labels are bare text nodes, which can't be lifted with
	   z-index the way element children can — hence this own stacking context. */
	isolation: isolate;
}

.btn--primary::after {
	content: "";
	position: absolute;
	inset: 0;
	background: var(--aurora);
	opacity: 0;
	transition: opacity var(--dur) var(--ease);
	pointer-events: none;
	z-index: var(--z-base);
}

.btn--primary:hover {
	--btn-bg: var(--aurora);
	background: var(--aurora);
	color: white;
	box-shadow: var(--shadow-glow), var(--inner-hi);
	transform: translateY(-1px);
}

.btn--primary:hover::after {
	opacity: 1;
}

.btn--ghost {
	--btn-bg: transparent;
	--btn-border: var(--border);
}

.btn--ghost:hover {
	background: var(--surface-1);
}

.btn--danger {
	--btn-bg: rgba(248, 113, 113, 0.12);
	--btn-fg: var(--danger);
	--btn-border: rgba(248, 113, 113, 0.3);
}

.btn--danger:hover {
	--btn-bg: rgba(248, 113, 113, 0.2);
	--btn-fg: var(--danger-hover);
	--btn-border: rgba(248, 113, 113, 0.5);
	color: var(--danger-hover);
}

/* Disabled button visual. Lives here with the rest of the .btn family rather
   than in admin.css: it is a button state, and the studio catalogue pager on
   games_by_entity#show needs it on a public page — a rule an admin-only sheet
   owns silently leaves those controls unstyled until the test that guards
   this catches it. */
.btn--disabled {
	opacity: 0.4;
	cursor: not-allowed;
	pointer-events: none;
}

.btn--warning {
	--btn-bg: rgba(251, 191, 36, 0.14);
	--btn-fg: var(--warning);
	--btn-border: rgba(251, 191, 36, 0.32);
}

.btn--warning:hover {
	--btn-bg: rgba(251, 191, 36, 0.22);
	--btn-fg: var(--warning-hover);
	--btn-border: rgba(251, 191, 36, 0.5);
	color: var(--warning-hover);
}

/* ponytail: .btn--sm is explicitly below the 44px touch target. Desktop-only.
   Future work should remove the sm variant entirely; until then, every
   usage is a known small-target exemption. */
.btn--sm {
	height: 32px;
	min-height: 0;
	padding: 0 var(--s-4);
	font-size: var(--text-xs);
}

.btn--block {
	display: flex;
	width: 100%;
}

.btn[disabled],
.btn:disabled {
	opacity: 0.5;
	cursor: not-allowed;
	transform: none !important;
}

/* ==========================================================================
   Forms
   ========================================================================== */

.select {
	width: 100%;
	height: 44px;
	min-height: 44px;
	padding: 0 var(--s-4);
	font-family: var(--font-sans);
	font-size: var(--text-md);
	color: var(--text);
	background: var(--surface-1);
	border: 1px solid var(--border);
	border-radius: var(--r-md);
	box-shadow: var(--inner-hi);
	transition:
		border-color var(--dur) var(--ease),
		box-shadow var(--dur) var(--ease),
		background var(--dur) var(--ease);
}

.select {
	appearance: none;
	-webkit-appearance: none;
	background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23a8afc1' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
	background-repeat: no-repeat;
	background-position: right 16px center;
	padding-right: 40px;
}

/* An open dropdown is painted by the UA, not by the page. `color` still
   inherits from .select (--text, near-white) but the option list falls back to
   the UA's light surface, so every unselected option renders near-white on
   white — invisible, while the highlighted row stays readable and makes it
   look like a rendering glitch. `color-scheme: dark` on :root is not enough
   here, so set the surface explicitly.

   --surface-solid rather than --surface-1: the translucent value is unreliable
   once the popup composites outside the page. Same fix as .form-select
   (admin.css) and .uas-form__select (profile.css); on the primitive so every
   class="select" in the app inherits it. */
.select option {
	color: var(--text);
	background-color: var(--surface-solid);
	padding: var(--s-2) var(--s-3);
}

/* Where the customizable <select> spec is implemented the popup is a real
   element, so it needs its own surface and border. Browsers without support
   drop this rule and fall back to the option styling above. */
.select::picker(select) {
	background: var(--surface-solid);
	color: var(--text);
	border: 1px solid var(--border);
	border-radius: var(--r-md);
}

.select:hover {
	border-color: var(--border-strong);
}

.select:focus {
	outline: none;
	border-color: var(--border-accent);
	background: var(--surface-2);
	box-shadow:
		0 0 0 4px rgba(239, 68, 68, 0.18),
		var(--inner-hi);
}

/* Checkbox */
.checkbox {
	display: flex;
	align-items: flex-start;
	gap: var(--s-3);
	cursor: pointer;
	padding: var(--s-3) 0;
}

.checkbox__input {
	appearance: none;
	-webkit-appearance: none;
	flex-shrink: 0;
	margin-top: 2px;
	width: 20px;
	height: 20px;
	background: var(--surface-1);
	border: 1px solid var(--border-strong);
	border-radius: 6px;
	cursor: pointer;
	position: relative;
	transition:
		background var(--dur) var(--ease),
		border-color var(--dur) var(--ease);
}

.checkbox__input:hover {
	border-color: var(--violet);
}

.checkbox__input:checked {
	background: var(--aurora-violet);
	border-color: transparent;
}

.checkbox__input:checked::after {
	content: "";
	position: absolute;
	top: 4px;
	left: 7px;
	width: 5px;
	height: 9px;
	border: solid white;
	border-width: 0 2px 2px 0;
	transform: rotate(45deg);
}

.checkbox__input:focus-visible {
	outline: none;
	box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.3);
}

.checkbox__label {
	flex: 1;
	cursor: pointer;
}

.checkbox__title {
	display: block;
	font-size: var(--text-md);
	font-weight: 500;
	color: var(--text);
}

.checkbox__description {
	display: block;
	font-size: var(--text-sm);
	color: var(--text-3);
	margin-top: 2px;
}

/* ==========================================================================
   Pills, badges, status
   ========================================================================== */

.pill {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	height: 26px;
	padding: 0 var(--s-3);
	font-size: var(--text-xs);
	font-weight: 500;
	color: var(--text-2);
	background: var(--surface-1);
	border: 1px solid var(--border);
	border-radius: var(--r-full);
	white-space: nowrap;
	transition:
		background var(--dur) var(--ease),
		border-color var(--dur) var(--ease),
		color var(--dur) var(--ease);
}

.pill:hover {
	background: var(--surface-2);
	border-color: var(--border-strong);
	color: var(--text);
}

.pill--accent {
	color: var(--violet);
	border-color: rgba(239, 68, 68, 0.3);
	background: rgba(239, 68, 68, 0.1);
}

.badge {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	padding: 3px 8px;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	border-radius: var(--r-sm);
	white-space: nowrap;
	line-height: 1.3;
}

/* The tracking status badges. The class names are the enum keys with the
   underscore swapped for a hyphen, because library/_list.html.erb builds
   them from the status: badge--want-to-play, badge--playing, badge--played,
   badge--dropped. */
.badge--want-to-play {
	color: var(--status-want);
	background: rgba(59, 130, 246, 0.16);
	border: 1px solid rgba(59, 130, 246, 0.32);
}

.badge--playing {
	color: var(--status-playing);
	background: rgba(16, 185, 129, 0.16);
	border: 1px solid rgba(16, 185, 129, 0.3);
}

.badge--played {
	color: var(--status-played);
	background: rgba(239, 68, 68, 0.16);
	border: 1px solid rgba(239, 68, 68, 0.3);
}

.badge--dropped {
	color: var(--status-dropped);
	background: rgba(249, 115, 22, 0.16);
	border: 1px solid rgba(249, 115, 22, 0.3);
}

.badge--discount {
	color: var(--pink);
	background: rgba(239, 68, 68, 0.18);
	border: 1px solid rgba(239, 68, 68, 0.36);
}

/* The sync-in-progress badge on the app page. */
.badge--accent {
	color: var(--pink);
	background: rgba(239, 68, 68, 0.14);
	border: 1px solid rgba(239, 68, 68, 0.32);
}

/* The game is at the lowest price we have ever seen it at. Green, because
   it is good news about the price. */
.badge--lowest {
	color: var(--green-soft);
	background: rgba(34, 197, 94, 0.16);
	border: 1px solid rgba(34, 197, 94, 0.3);
}

.badge--year,
.badge--weekly {
	color: var(--status-played);
	background: rgba(239, 68, 68, 0.16);
	border: 1px solid rgba(239, 68, 68, 0.3);
}

.badge--weekly {
	background: rgba(127, 29, 29, 0.16);
	border-color: rgba(127, 29, 29, 0.3);
	color: var(--pink);
}

/* ponytail: app-show surface badge variants. Promote from inline
   style="background: var(--aurora-blue)" on app/views/apps/show.html.erb. */
.badge--coming-soon {
	background: var(--aurora-blue);
	color: white;
	border: none;
}

.badge--released {
	background: var(--surface-2);
	color: var(--text);
	border: 1px solid var(--border);
}

.badge--metacritic {
	background: var(--score-pos);
	color: var(--text-on-accent);
	border: none;
}

.badge--muted {
	background: var(--surface-2);
	color: var(--text-3);
	border: 1px solid var(--border);
}

/* ponytail: AI-slop flag.

   Two densities, because the surfaces differ. In dense rows and cards a
   pill stole title width and read as loud as a price chip, so the inline
   mark is just "AI" in muted mono — chrome-free, ~16px, tooltip carries
   the meaning. The hero badge row and admin panel have room and use the
   full pill, since a status badge is the established pattern there.

   Deliberately neutral rather than --warning (amber, reserved for "Patch
   Required") so a flagged game never reads as a patching problem. */
.badge--slop {
	color: var(--text-2);
	background: var(--surface-3);
	border: 1px dashed var(--border-strong);
}

.slop-mark {
	flex-shrink: 0;
	font-family: var(--font-mono);
	font-size: 9px;
	font-weight: 700;
	letter-spacing: 0.04em;
	line-height: 1;
	text-transform: uppercase;
	color: var(--text-3);
	cursor: help;
}

/* Card variant: the artwork has room, so anchor the mark to its top-left
   corner instead of putting it in the title flow. Out of flow entirely —
   it costs the card no horizontal space. Needs a scrim of its own because
   the image-wrap gradient runs dark only at the very top of the frame. */
.game-card .slop-mark,
.home-hero__featured-wrap .slop-mark,
.home-hero__tile-wrap .slop-mark {
	position: absolute;
	top: var(--s-3);
	left: var(--s-3);
	z-index: var(--z-sticky);
	padding: 3px 5px;
	border-radius: var(--r-sm);
	color: var(--text-2);
	background: rgba(7, 9, 15, 0.72);
	backdrop-filter: blur(4px);
	border: 1px solid var(--border-strong);
}

/* ponytail: hero badges row — replaces inline display:flex + gap on
   app/views/apps/show.html.erb. Used by both home and app show hero badges. */
.hero-badges {
	display: flex;
	gap: var(--s-2);
	flex-wrap: wrap;
}

/* ponytail: inline-flex row of stars in the star-rating component.
   Promoted from inline style="display: flex; gap: 2px;" on app show. */
.stars--inline {
	display: flex;
	gap: 2px;
}

/* Score colours. The discount value used to carry its own price variant
   here; it now shares the score scale with the review scores. */
.score--positive {
	color: var(--score-pos);
}
.score--mixed {
	color: var(--score-mixed);
}
.score--negative {
	color: var(--score-neg);
}

/* ==========================================================================
   Headings & section titles
   ========================================================================== */

.eyebrow {
	display: inline-flex;
	align-items: center;
	gap: var(--s-2);
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: var(--tracking-wide);
	text-transform: uppercase;
	color: var(--text-3);
}

.eyebrow::before {
	content: "";
	width: 24px;
	height: 1px;
	background: var(--aurora);
}

.section-title {
	font-family: var(--font-display);
	font-size: var(--text-2xl);
	font-weight: 600;
	letter-spacing: var(--tracking-tight);
	color: var(--text);
	margin: 0;
}

.section-title--lg {
	font-size: var(--text-3xl);
}

.section-subtitle {
	font-size: var(--text-sm);
	font-weight: 400;
	color: var(--text-3);
	margin-left: var(--s-2);
}

.section-header {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--s-3);
	margin-bottom: var(--s-5);
	/* A heading plus a row of controls is a pair that fits on one line right up
	   until it does not, and the controls are the part that cannot be
	   compressed -- a select at a squeezed width clips its own text. Wrapping
	   puts the controls on their own line instead, which is the only place they
	   still have room. Nothing changes at any width where the two already fit,
	   so this costs the wide layouts nothing. */
	flex-wrap: wrap;
}

/* A heading that used to sit directly in .section carries `margin: 0 0
   var(--s-4)`, and as a flex item that margin does not collapse with the
   container's own -- it would open a double-width gap. The container's
   margin-bottom is the gap; the heading does not add one. */
.section-header > h2 {
	margin-bottom: 0;
}

.section-header__actions {
	display: inline-flex;
	align-items: center;
	gap: var(--s-3);
}

/* ==========================================================================
   Empty state
   ========================================================================== */

.empty-state {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--s-3);
	padding: var(--s-9) var(--s-6);
	text-align: center;
	color: var(--text-3);
	font-size: var(--text-md);
	background: var(--surface-1);
	border: 1px dashed var(--border);
	border-radius: var(--r-lg);
}

.empty-state__icon {
	width: 48px;
	height: 48px;
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--surface-2);
	border: 1px solid var(--border);
	border-radius: 50%;
	color: var(--text-2);
}

.empty-state__action {
	margin-top: var(--s-2);
	gap: 6px;
}

.empty-state__action > span:last-child {
	transition: transform var(--dur) var(--ease);
}

.empty-state__action:hover > span:last-child {
	transform: translateX(2px);
}

@media (prefers-reduced-motion: reduce) {
	.empty-state__action > span:last-child,
	.empty-state__action:hover > span:last-child {
		transition: none;
		transform: none;
	}
}

/* ==========================================================================
   Skeleton
   ========================================================================== */

.skeleton {
	background: linear-gradient(
		90deg,
		rgba(255, 255, 255, 0.04) 25%,
		rgba(255, 255, 255, 0.1) 50%,
		rgba(255, 255, 255, 0.04) 75%
	);
	background-size: 200% 100%;
	animation: shimmer 1.5s infinite;
	border-radius: var(--r-sm);
}

@keyframes shimmer {
	0% {
		background-position: -200% 0;
	}
	100% {
		background-position: 200% 0;
	}
}

/* ==========================================================================
   Kbd
   ========================================================================== */

kbd {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 22px;
	height: 22px;
	padding: 0 6px;
	font-family: var(--font-mono);
	font-size: 11px;
	font-weight: 600;
	color: var(--text-2);
	background: var(--surface-2);
	border: 1px solid var(--border-strong);
	border-bottom-width: 2px;
	border-radius: 6px;
	line-height: 1;
}

/* ==========================================================================
   Utilities
   ========================================================================== */

.text-2 {
	color: var(--text-2);
}
.text-3 {
	color: var(--text-3);
}

.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;
}

/* ==========================================================================
   Entity follow (developer / publisher / tag pages)
   ========================================================================== */

.entity-follow {
	display: flex;
	align-items: center;
	gap: var(--s-3);
	margin: var(--s-4) 0 var(--s-5);
}

/* Those block margins are for a follow row sitting in normal flow under a
   heading. In a section header the row is a flex item, and the margins would
   push it off the row's centre line rather than space anything. */
.section-header__actions .entity-follow {
	margin: 0;
}

.entity-follow__count {
	font-size: var(--text-sm);
	color: var(--text-3);
	/* "1 follower" breaking across lines reads as two facts. The count sits
	   beside the follow button wherever that button lands, so it holds
	   together in all of those places, not just the list rows. */
	white-space: nowrap;
}

.tag-description {
	max-width: 65ch;
	margin: var(--s-3) 0 0;
	color: var(--text-2);
	font-size: var(--text-sm);
	line-height: 1.6;
}

.tag-description p {
	margin: 0;
}

.tag-description p + p {
	margin-top: var(--s-2);
}

.entity-follow__hint {
	max-width: 60ch;
	margin: calc(var(--s-4) * -1 + var(--s-2)) 0 var(--s-5);
	font-size: var(--text-sm);
	color: var(--text-3);
	line-height: 1.5;
}

.entity-follow__hint a {
	color: var(--text-2);
	text-decoration: underline;
	text-underline-offset: 2px;
}

.entity-follow__hint a:hover {
	color: var(--text);
}

/* ==========================================================================
   Search — studio (developer / publisher) and tag results
   ========================================================================== */

.search-studios {
	margin-bottom: var(--s-6);
}

.search-studios__title {
	font-size: var(--text-sm);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--text-3);
	margin-bottom: var(--s-3);
}

.search-games__title {
	font-size: var(--text-sm);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--text-3);
	margin: var(--s-5) 0 var(--s-3);
}

.search-entity-list {
	list-style: none;
	padding: 0;
	margin: 0;
	display: flex;
	flex-direction: column;
	gap: var(--s-2);
}

.search-entity {
	display: flex;
	align-items: center;
	gap: var(--s-3);
	padding: var(--s-3) var(--s-4);
	background: var(--surface-1);
	border: 1px solid var(--border);
	border-radius: var(--r-md);
	transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

/* The row is not itself a link — it holds a nested <form> for the follow
   button, so the hover affordance lives on the name. */
.search-entity:hover {
	background: var(--surface-2);
	border-color: var(--border-strong);
}

.search-entity__badge {
	flex-shrink: 0;
	font-size: var(--text-xs);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	color: var(--text-3);
	background: var(--surface-3);
	border: 1px solid var(--border);
	border-radius: var(--r-sm);
	padding: 2px var(--s-2);
}

.search-entity__name {
	font-size: var(--text-sm);
	font-weight: 500;
	color: var(--text);
	text-decoration: none;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.search-entity__name:hover {
	color: var(--text);
	text-decoration: underline;
	text-underline-offset: 2px;
}

.search-entity__name:focus-visible {
	outline: 2px solid var(--violet);
	outline-offset: 2px;
	border-radius: 2px;
}

.search-entity__count {
	flex-shrink: 0;
	font-size: var(--text-sm);
	color: var(--text-3);
	white-space: nowrap;
}

/* Pushes the follow button to the right edge, after the count. */
.search-entity .entity-follow {
	margin: 0 0 0 auto;
}

.entity-follow--compact {
	gap: var(--s-2);
}

.entity-follow--compact .entity-follow__form {
	display: inline;
}

.search-follow-hint {
	margin-top: var(--s-5);
	font-size: var(--text-sm);
	color: var(--text-3);
}

.search-follow-hint a {
	color: var(--text-2);
	text-underline-offset: 2px;
}

.search-follow-hint a:hover {
	color: var(--text);
}

/* ==========================================================================
   Tippy tooltip theme
   ========================================================================== */

/* The vendored tippy.css ships a flat #333 box that sits visibly off-theme
   against the glassy dark surfaces. These rules come after it in the
   cascade (the vendor sheet loads first — see ApplicationHelper::
   VENDOR_STYLESHEETS), so equal specificity is enough. */
.tippy-box {
	font-family: var(--font-sans);
	font-size: var(--text-xs);
	font-weight: 500;
	line-height: 1.4;
	color: var(--text);
	background: var(--surface-solid);
	/* No border: the tippy arrow is a CSS triangle drawn in the same
	   colour as the box, and a border on the box can't follow it — the
	   arrow reads as detached. The shadow carries the separation. */
	border: none;
	border-radius: var(--r-sm);
	box-shadow: var(--shadow);
}

.tippy-box .tippy-arrow {
	color: var(--surface-solid);
}

.tippy-box .tippy-content {
	padding: var(--s-2) var(--s-3);
}
