/* Self-hosted fonts (CSP: style-src/font-src 'self') */
/* latin */
@font-face {
	font-family: 'Bricolage Grotesque';
	font-style: normal;
	font-weight: 400 800;
	font-stretch: 100%;
	font-display: swap;
	src: url('/fonts/bricolage-grotesque-latin.woff2') format('woff2');
	unicode-range:
		U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
		U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
		U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
	font-family: 'Bricolage Grotesque';
	font-style: normal;
	font-weight: 400 800;
	font-stretch: 100%;
	font-display: swap;
	src: url('/fonts/bricolage-grotesque-latin-ext.woff2') format('woff2');
	unicode-range:
		U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
		U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
		U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
	font-family: 'Wix Madefor Text';
	font-style: normal;
	font-weight: 400 800;
	font-display: swap;
	src: url('/fonts/wix-madefor-text-latin.woff2') format('woff2');
	unicode-range:
		U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
		U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
		U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
	font-family: 'Wix Madefor Text';
	font-style: normal;
	font-weight: 400 800;
	font-display: swap;
	src: url('/fonts/wix-madefor-text-latin-ext.woff2') format('woff2');
	unicode-range:
		U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
		U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
		U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
	font-family: 'Wix Madefor Text';
	font-style: italic;
	font-weight: 400 800;
	font-display: swap;
	src: url('/fonts/wix-madefor-text-italic-latin.woff2') format('woff2');
	unicode-range:
		U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
		U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
		U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
	font-family: 'Wix Madefor Text';
	font-style: italic;
	font-weight: 400 800;
	font-display: swap;
	src: url('/fonts/wix-madefor-text-italic-latin-ext.woff2') format('woff2');
	unicode-range:
		U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
		U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
		U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
	color-scheme: light dark;

	/* Colors - Light mode (flat grey canvas, green accent) */
	--color-primary: oklch(0.54 0.18 145);
	--color-primary-hover: oklch(0.48 0.17 145);
	--color-primary-active: oklch(0.48 0.17 145);
	/* Pure white, not an off-white: the near-white this used to be
	   (oklch(0.985 0.005 145), #f8fbf8) measured 4.44:1 on --color-primary,
	   just under the 4.5:1 AA floor for 16px text like the primary pill CTA.
	   White clears it at 4.63:1 and is indistinguishable on a green ground. */
	--color-on-primary: oklch(1 0 0);
	/* One step darker than the 0.47 this started at, which measured 4.36:1 on
	   the 12%-primary soft green the "verified" pill uses as its ground — just
	   under the 4.5:1 AA floor. 0.455 reads 4.63:1 there and improves every
	   other green-on-light-ground use of this token too. */
	--color-primary-text: oklch(0.455 0.17 145);
	--color-background: oklch(0.93 0.004 250);
	--color-surface: oklch(0.975 0.002 250);
	--color-text: oklch(0.22 0.012 260);
	--color-text-muted: oklch(0.44 0.014 260);
	--color-border: oklch(0.84 0.008 250);
	--color-danger: oklch(0.55 0.19 25);
	--color-danger-hover: oklch(0.5 0.18 25);
	--color-on-danger: oklch(0.985 0.005 25);
	/* Warm light cast by the lantern Kody holds (landing + auth heroes) */
	--lantern-glow: oklch(0.88 0.11 85 / 0.4);
	--lantern-glow-dark: oklch(0.8 0.13 80 / 0.55);

	/* Colors - Dark mode (graphite canvas, brighter green) */
	--color-primary-dark: oklch(0.72 0.205 145);
	--color-primary-hover-dark: oklch(0.78 0.21 145);
	--color-primary-active-dark: oklch(0.78 0.21 145);
	--color-on-primary-dark: oklch(0.2 0.05 148);
	--color-primary-text-dark: oklch(0.75 0.2 145);
	--color-background-dark: oklch(0.19 0.008 255);
	--color-surface-dark: oklch(0.235 0.01 255);
	--color-text-dark: oklch(0.945 0.005 250);
	--color-text-muted-dark: oklch(0.72 0.01 255);
	--color-border-dark: oklch(0.33 0.01 255);
	--color-danger-dark: oklch(0.75 0.16 25);
	--color-danger-hover-dark: oklch(0.7 0.17 25);
	--color-on-danger-dark: oklch(0.2 0.05 25);

	/* Tab stops in code and other preformatted text. Browsers default to 8. */
	tab-size: 2;

	/* Typography */
	--font-display: 'Bricolage Grotesque', system-ui, sans-serif;
	--font-body: 'Wix Madefor Text', system-ui, sans-serif;
	--font-family: var(--font-body);
	--font-size-xs: 0.75rem;
	--font-size-sm: 0.875rem;
	--font-size-base: 1rem;
	--font-size-lg: 1.25rem;
	--font-size-xl: 2rem;
	--font-size-2xl: 3rem;
	--font-weight-normal: 400;
	--font-weight-medium: 500;
	--font-weight-semibold: 600;
	--font-weight-bold: 700;
	--line-height-body: 1.62;
	--line-height-body-dark: 1.7;

	/* Spacing */
	--spacing-xs: 0.25rem;
	--spacing-sm: 0.5rem;
	--spacing-md: 1rem;
	--spacing-lg: 1.5rem;
	--spacing-xl: 2rem;
	--spacing-2xl: 3rem;

	/* Border radius */
	--radius-sm: 0.25rem;
	--radius-md: 0.5rem;
	--radius-lg: 0.75rem;
	--radius-xl: 1rem;
	--radius-card: 14px;
	--radius-full: 999px;

	/* Shadows */
	--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
	--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
	--shadow-sm-dark: 0 1px 2px 0 rgb(255 255 255 / 0.05);
	--shadow-md-dark:
		0 4px 6px -1px rgb(255 255 255 / 0.08),
		0 2px 4px -2px rgb(255 255 255 / 0.08);

	/* Motion */
	--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
	--transition-fast: 160ms var(--ease-out);
	--transition-normal: 200ms var(--ease-out);

	/* Kody shirt pattern, used as a mask for background whispers */
	--kody-pattern: url('/images/kody-pattern.png');

	/* Select chevron (see `getSelectCss`). A data-URI background image cannot
	   read `currentColor`, so the stroke is baked in and the token is themed
	   instead. Each value is its theme's muted-text color, which clears the
	   3:1 non-text contrast minimum on both that theme's surfaces. */
	--select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%234e535a' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
	--select-chevron-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a0a5ab' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");

	/* Responsive spacing - used for page-level padding */
	--spacing-page: var(--spacing-2xl);
	--spacing-section: var(--spacing-xl);
	--spacing-header: var(--spacing-xl);
}

/* Tablet responsive overrides */
@media (max-width: 1024px) {
	:root {
		--spacing-page: var(--spacing-xl);
		--spacing-section: var(--spacing-lg);
		--spacing-header: var(--spacing-lg);
	}
}

/* Mobile responsive overrides */
@media (max-width: 640px) {
	:root {
		--spacing-page: var(--spacing-md);
		--spacing-section: var(--spacing-md);
		--spacing-header: var(--spacing-md);
		--font-size-xl: 1.5rem;
		--font-size-2xl: 2rem;
	}
}

/* Explicit light choice must pin UA rendering even under a dark system. */
:root[data-theme='light'] {
	color-scheme: light;
}

:root[data-theme='dark'] {
	color-scheme: dark;
	--color-primary: var(--color-primary-dark);
	--color-primary-hover: var(--color-primary-hover-dark);
	--color-primary-active: var(--color-primary-active-dark);
	--color-on-primary: var(--color-on-primary-dark);
	--color-primary-text: var(--color-primary-text-dark);
	--color-background: var(--color-background-dark);
	--color-surface: var(--color-surface-dark);
	--color-text: var(--color-text-dark);
	--color-text-muted: var(--color-text-muted-dark);
	--color-border: var(--color-border-dark);
	--color-danger: var(--color-danger-dark);
	--color-danger-hover: var(--color-danger-hover-dark);
	--color-on-danger: var(--color-on-danger-dark);
	--lantern-glow: var(--lantern-glow-dark);
	--select-chevron: var(--select-chevron-dark);
	--line-height-body: var(--line-height-body-dark);

	/* Dark mode shadows - lighter for visibility */
	--shadow-sm: var(--shadow-sm-dark);
	--shadow-md: var(--shadow-md-dark);
}

@media (prefers-color-scheme: dark) {
	:root:not([data-theme='light']) {
		color-scheme: dark;
		--color-primary: var(--color-primary-dark);
		--color-primary-hover: var(--color-primary-hover-dark);
		--color-primary-active: var(--color-primary-active-dark);
		--color-on-primary: var(--color-on-primary-dark);
		--color-primary-text: var(--color-primary-text-dark);
		--color-background: var(--color-background-dark);
		--color-surface: var(--color-surface-dark);
		--color-text: var(--color-text-dark);
		--color-text-muted: var(--color-text-muted-dark);
		--color-border: var(--color-border-dark);
		--color-danger: var(--color-danger-dark);
		--color-danger-hover: var(--color-danger-hover-dark);
		--color-on-danger: var(--color-on-danger-dark);
		--lantern-glow: var(--lantern-glow-dark);
		--select-chevron: var(--select-chevron-dark);
		--line-height-body: var(--line-height-body-dark);

		/* Dark mode shadows - lighter for visibility */
		--shadow-sm: var(--shadow-sm-dark);
		--shadow-md: var(--shadow-md-dark);
	}
}

/* Reset and base styles */
*,
*::before,
*::after {
	box-sizing: border-box;
}

body {
	margin: 0;
	padding: 0;
	font-family: var(--font-family);
	font-size: 1.0625rem;
	line-height: var(--line-height-body);
	color: var(--color-text);
	background-color: var(--color-background);
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
	overflow-x: clip;
}

h1,
h2,
h3 {
	font-family: var(--font-display);
	font-optical-sizing: auto;
	text-wrap: balance;
}

/* No `border-radius` here. This rule used to force 4px onto whatever you
   tabbed to, visibly resquaring pills, cards, and rounded fields at the moment
   they took focus — and since it is unlayered, no component style could win
   against it, so it had grown a set of per-component escape hatches whose only
   job was to put the real radius back. `outline` already follows the element's
   own `border-radius`, so the declaration was never needed. */
:focus-visible {
	outline: 2.5px solid var(--color-primary);
	outline-offset: 3px;
}

/* The router moves focus to <main> after client-side navigation (and the
   skip link targets it). That focus is managed wayfinding for assistive
   tech — an outline around the entire page region communicates nothing,
   and keyboard users still get real indicators on the elements inside. */
main:focus-visible {
	outline: none;
}

/* Connected pill controls (waitlist form, community search): the container
   shows one focus ring via :focus-within; inner segments must not draw
   their own. Unlayered on purpose — component styles live in @layer rmx,
   which the global :focus-visible rule above would otherwise always beat. */
[data-focus-container] :focus-visible {
	outline: none;
}

/* ...except a submit button inside one. The suppression above is meant for the
   text segments, which share the container's ring and show a caret. The button
   is its own action, and by the time focus reaches it the container ring is
   already lit, so without this there was no indication at all that the button
   had focus. Drawn inside the button (negative offset) so it cannot collide
   with the container edge, in the on-primary tone that the button's own label
   uses: 4.6:1 on the green fill in light, 7.8:1 in dark. */
[data-focus-container] button:focus-visible {
	outline: 2.5px solid var(--color-on-primary);
	outline-offset: -5px;
}

/* Individually bordered redesign fields (login form): the field draws its own
   focus ring — green border + soft glow, see getAuthInputCss() — so the global
   outline above would double-ring it. Same unlayered escape hatch as
   data-focus-container. The radius override this used to carry is gone with the
   global one it was undoing. */
[data-field-ring]:focus-visible {
	outline: none;
}

::selection {
	background: oklch(from var(--color-primary) l c h / 0.3);
}

input[type='radio'],
input[type='checkbox'] {
	accent-color: var(--color-primary);
}

#root {
	min-height: 100vh;
}

/* The account/admin shell owns its whole box (the prototype's `.account`
   padding: the shared page gutter inline, so its content lines up with the
   header's 72rem content box, plus the vertical rhythm). `<main>`'s generic
   padding for non-redesigned routes would stack on top of it. Unlayered on
   purpose — it has to beat the css() class that sets that padding. */
main:has([data-account-shell]) {
	padding: 0;
}

/* Always reserve the classic scrollbar's track, so a short page and a long one
   have the same content width. Without it, every navigation between the two
   shifts the layout sideways — which the route view transition then animates.
   No-op where scrollbars overlay the content (macOS default, touch). */
html {
	scrollbar-gutter: stable;
}

/* Smooth scrolling only after the initial #hash jump has settled (set by
   theme-init.js) so cross-page anchor arrivals land instantly. */
html.is-settled {
	scroll-behavior: smooth;
}

/* Theme toggle icons: the button shows the theme you'd switch to via CSS so
   the server-rendered markup is correct before hydration. */
:root[data-theme='light'] .icon-sun,
:root:not([data-theme]) .icon-sun {
	display: none;
}

:root[data-theme='dark'] .icon-moon {
	display: none;
}

@media (prefers-color-scheme: dark) {
	:root:not([data-theme]) .icon-sun {
		display: inline;
	}

	:root:not([data-theme]) .icon-moon {
		display: none;
	}
}

/* Shiki dual-theme tokens follow `:root[data-theme]` (and system dark when
   no explicit theme is set). Inline light colors are the default. */
:root[data-theme='dark'] .shiki,
:root[data-theme='dark'] .shiki span {
	color: var(--shiki-dark) !important;
	background-color: var(--shiki-dark-bg) !important;
	font-style: var(--shiki-dark-font-style) !important;
	font-weight: var(--shiki-dark-font-weight) !important;
	text-decoration: var(--shiki-dark-text-decoration) !important;
}

@media (prefers-color-scheme: dark) {
	:root:not([data-theme='light']) .shiki,
	:root:not([data-theme='light']) .shiki span {
		color: var(--shiki-dark) !important;
		background-color: var(--shiki-dark-bg) !important;
		font-style: var(--shiki-dark-font-style) !important;
		font-weight: var(--shiki-dark-font-weight) !important;
		text-decoration: var(--shiki-dark-text-decoration) !important;
	}
}

/* ---------- motion system (heykody.dev redesign) ----------
   Enhance-only: content is fully visible without JS; the `html.js` class
   (set pre-paint by theme-init.js) gates initial-hidden states, and
   reduced-motion removes everything. One ease everywhere (--ease-out). */

@media (prefers-reduced-motion: no-preference) {
	/* list rhythm + illustration entrances (classes added by client/reveal.ts) */
	html.js .reveal {
		opacity: 0;
		translate: 0 20px;
		transition:
			opacity 600ms var(--ease-out),
			translate 600ms var(--ease-out);
		transition-delay: var(--reveal-delay, 0ms);
	}

	html.js .reveal-pop {
		opacity: 0;
		translate: 0 8px;
		scale: 0.94;
		transition:
			opacity 420ms var(--ease-out),
			translate 420ms var(--ease-out),
			scale 420ms var(--ease-out);
		transition-delay: var(--reveal-delay, 0ms);
	}

	html.js .reveal.in,
	html.js .reveal-pop.in {
		opacity: 1;
		translate: 0 0;
		scale: 1;
	}

	/* Card reveal: the same quiet rise as .reveal, but as an animation rather
	   than a transition, so the card's own hover transitions (border, lift)
	   keep their easing after the entrance. No scale — text never pops. */
	html.js .reveal-card {
		opacity: 0;
	}

	html.js .reveal-card.in {
		opacity: 1;
		animation: card-in 600ms var(--ease-out) var(--reveal-delay, 0ms) backwards;
	}

	/* Page-open choreography: full document loads only — SPA navigations
	   set html[data-spa-nav] (client-router) and get the view transition
	   as their entrance instead. */
	html.js:not([data-spa-nav]) [data-rise] {
		animation: rise 700ms var(--ease-out) both;
		animation-delay: calc(60ms + var(--rise, 0) * 90ms);
	}

	/* SPA route transitions (View Transitions API). The router wraps the
	   DOM swap in startViewTransition (client/client-router.tsx); chrome
	   that persists across routes is lifted out of the root snapshot by
	   view-transition-name and pinned static here. Browsers without the
	   API never match these pseudos — the swap is simply instant. */
	::view-transition-old(root) {
		animation: vt-page-out 120ms var(--ease-out) both;
	}

	::view-transition-new(root) {
		animation: vt-page-in 220ms var(--ease-out) both;
	}

	::view-transition-old(site-header),
	::view-transition-new(site-header),
	::view-transition-old(site-footer),
	::view-transition-new(site-footer),
	::view-transition-old(nav-progress),
	::view-transition-new(nav-progress) {
		animation: none;
	}

	@keyframes vt-page-out {
		to {
			opacity: 0;
		}
	}

	@keyframes vt-page-in {
		from {
			opacity: 0;
			transform: translateY(8px);
		}
	}

	@keyframes rise {
		from {
			opacity: 0;
			translate: 0 26px;
		}

		to {
			opacity: 1;
			translate: 0 0;
		}
	}

	@keyframes card-in {
		from {
			opacity: 0;
			translate: 0 20px;
		}

		to {
			opacity: 1;
			translate: 0 0;
		}
	}

	@keyframes success-in {
		from {
			opacity: 0;
			translate: 0 6px;
			scale: 0.97;
		}

		to {
			opacity: 1;
			translate: 0 0;
			scale: 1;
		}
	}
}

@media (prefers-reduced-motion: reduce) {
	html.is-settled {
		scroll-behavior: auto;
	}
}

.app-shell {
	min-height: 100vh;
	display: flex;
	align-items: center;
	justify-content: center;
}

.loading-spinner {
	width: 36px;
	height: 36px;
	border-radius: 999px;
	border: 4px solid color-mix(in srgb, var(--color-border) 70%, transparent);
	border-top-color: var(--color-primary);
	opacity: 0;
	animation:
		spinner-fade-in 0.2s ease 120ms forwards,
		spinner-spin 0.9s linear 120ms infinite;
}

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

@keyframes spinner-fade-in {
	to {
		opacity: 1;
	}
}
