/* ============================================================================
   julivn.dev — "COMMAND DECK"
   A generative, interactive, mission-control portfolio.

   Everything degrades gracefully: no-WebGL falls back to animated CSS
   gradients, prefers-reduced-motion kills all motion, touch devices drop
   the custom cursor and tilt. The page is fully usable as plain HTML.
   ============================================================================ */

/* ----------------------------------------------------------------------------
   tokens
   ---------------------------------------------------------------------------- */
:root {
	/* base surfaces — deep space indigo-black */
	--bg:        #05060e;
	--bg-2:      #090b18;
	--fg:        #f3f1ff;
	--muted:     #8b88b0;
	--faint:     #5a5878;
	--rule:      rgba(255, 255, 255, 0.09);
	--rule-2:    rgba(255, 255, 255, 0.045);
	--glass:     rgba(255, 255, 255, 0.035);
	--glass-2:   rgba(255, 255, 255, 0.06);

	/* plasma palette — three stops drive the WebGL shader + CSS fallbacks.
	   The accent picker recolors --accent; palette presets set all three.
	   Default palette is Burgundy (dark wine + plum + dusky violet) — quiet,
	   introverted, brooding. Bright presets stay available via the picker. */
	--accent:    #7a1f3a;   /* primary — dark burgundy */
	--accent-2:  #4a1730;   /* deep wine plum */
	--accent-3:  #2a1a3d;   /* dusky violet */

	--accent-soft: color-mix(in srgb, var(--accent) 16%, transparent);
	--accent-glow: color-mix(in srgb, var(--accent) 55%, transparent);
	/* --accent at 2.08:1 contrast on dark bg fails WCAG AA. --accent-text
	   is the brand color lifted into a readable register (~6.5:1) — used
	   for labels, badges, and small accent text that has to be read.
	   Raw --accent stays the brand color for dots, borders, hover states,
	   and decorative pseudo-elements where contrast doesn't matter. */
	--accent-text: color-mix(in srgb, var(--accent) 55%, var(--fg));
	/* The bright glow color for neon mode. Each palette preset declares
	   a tuned value via JS; the fallback below pushes --accent toward a
	   hot pink so custom-accent picks still glow. */
	--accent-neon: color-mix(in srgb, var(--accent) 35%, #ff5d8c);

	/* Three font roles:
	   · --font-script: Water Brush — used ONLY for the personal touch
	     points: the hero name and the "AI Engineer" title. Brush script
	     with character; carries the introvert-but-with-personality vibe.
	   · --font-display: clean system sans for headings that need to
	     read at a glance (project titles, big numerals).
	   · --font-body: system sans for paragraphs.
	   · --font-mono: monospace for labels and metadata. */
	--font-script:  "Water Brush", "Bonheur Royale", "Brush Script MT",
	                cursive;
	--font-display: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI",
	                "Helvetica Neue", Arial, sans-serif;
	--font-body:    -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI",
	                "Helvetica Neue", Arial, sans-serif;
	--font-mono:    ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;

	--page-pad: clamp(1.25rem, 4vw, 4.5rem);
	--max-w:    1480px;

	--ease:     cubic-bezier(0.22, 0.61, 0.36, 1);
	--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
	--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);

	color-scheme: dark;

	/* WebGL haze opacity per theme — JS reads --bg-intensity for the shader. */
	--bg-intensity: 1;
	--grain-opacity: 0.05;
}

:root[data-theme="light"] {
	color-scheme: light;
	--bg:        #ece9f7;
	--bg-2:      #f6f4fd;
	--fg:        #0c0a18;
	--muted:     #5b5878;
	--faint:     #8d8aa6;
	--rule:      rgba(20, 14, 40, 0.12);
	--rule-2:    rgba(20, 14, 40, 0.07);
	--glass:     rgba(255, 255, 255, 0.55);
	--glass-2:   rgba(255, 255, 255, 0.72);
	--accent-soft: color-mix(in srgb, var(--accent) 18%, transparent);
	/* On the light bg (#ece9f7) the raw --accent burgundy already lands at
	   ~8.6:1 contrast — no lift needed. Override with the raw token so
	   the brand color stays its full intensity in light mode. */
	--accent-text: var(--accent);
	/* Neon glow on a light bg needs the OPPOSITE shift — we want a
	   saturated, darker brand color to "burn" into the light backdrop
	   rather than a hot-bright color that washes out. */
	--accent-neon: color-mix(in srgb, var(--accent) 80%, #ff2266);
	--bg-intensity: 0.55;
	--grain-opacity: 0.035;
}

html, body {
	transition: background-color 420ms var(--ease), color 420ms var(--ease);
}

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

html {
	background: var(--bg);
	color: var(--fg);
	scroll-behavior: smooth;
	overflow-x: hidden;
	-webkit-text-size-adjust: 100%;
}

body {
	margin: 0;
	font-family: var(--font-body);
	font-size: 17px;
	line-height: 1.55;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	text-rendering: optimizeLegibility;
	overflow-x: hidden;
	position: relative;
}

a { color: inherit; text-decoration: none; }
ul { margin: 0; padding: 0; list-style: none; }
p  { margin: 0; }

::selection { background: var(--accent); color: #fff; }

/* Hide the OS cursor on devices that get our custom one — applied via JS
   class so touch/reduced-motion keep the native cursor. */
body.custom-cursor,
body.custom-cursor * { cursor: none; }

/* ----------------------------------------------------------------------------
   background — a vivid, animated, PURE-CSS aurora. No WebGL, no canvas, no
   JS dependency: it renders identically on every device (the previous WebGL
   plasma silently rendered black under Brave/iOS, leaving the page empty).
   Three blurred, palette-colored layers drift and rotate over each other.
   ---------------------------------------------------------------------------- */
.bg {
	position: fixed;
	inset: 0;
	z-index: -3;
	pointer-events: none;
	overflow: hidden;
	background: radial-gradient(135% 120% at 50% -10%, var(--bg-2), var(--bg) 60%);
}
.bg span {
	position: absolute;
	inset: -35%;
	display: block;
	filter: blur(64px) saturate(150%);
	will-change: transform;
	opacity: var(--bg-intensity);
}
/* warm blooms — accent + violet, upper field */
.bg .l1 {
	background:
		radial-gradient(38% 46% at 24% 30%, color-mix(in srgb, var(--accent)   92%, transparent), transparent 60%),
		radial-gradient(32% 40% at 80% 18%, color-mix(in srgb, var(--accent-2) 88%, transparent), transparent 62%),
		radial-gradient(30% 38% at 62% 50%, color-mix(in srgb, var(--accent)   60%, transparent), transparent 64%);
	animation: aurora-1 24s ease-in-out infinite alternate;
}
/* cool blooms — cyan + accent, lower field, screen-blended for glow */
.bg .l2 {
	background:
		radial-gradient(42% 48% at 72% 74%, color-mix(in srgb, var(--accent-3) 88%, transparent), transparent 62%),
		radial-gradient(36% 44% at 26% 82%, color-mix(in srgb, var(--accent)   72%, transparent), transparent 60%),
		radial-gradient(30% 36% at 50% 60%, color-mix(in srgb, var(--accent-2) 64%, transparent), transparent 64%);
	mix-blend-mode: screen;
	animation: aurora-2 30s ease-in-out infinite alternate;
}
/* slow rotating spectral wash for depth */
.bg .l3 {
	background: conic-gradient(from 90deg at 50% 50%,
		color-mix(in srgb, var(--accent)   45%, transparent),
		color-mix(in srgb, var(--accent-2) 45%, transparent),
		color-mix(in srgb, var(--accent-3) 45%, transparent),
		color-mix(in srgb, var(--accent)   45%, transparent));
	mix-blend-mode: screen;
	opacity: calc(var(--bg-intensity) * 0.35);
	animation: aurora-spin 48s linear infinite;
}
@keyframes aurora-1 {
	0%   { transform: translate3d(-4%, -3%, 0) scale(1.05) rotate(0deg); }
	100% { transform: translate3d(6%, 4%, 0)   scale(1.22) rotate(12deg); }
}
@keyframes aurora-2 {
	0%   { transform: translate3d(4%, 3%, 0)  scale(1.12) rotate(0deg); }
	100% { transform: translate3d(-5%, -4%, 0) scale(1.28) rotate(-14deg); }
}
@keyframes aurora-spin {
	to { transform: rotate(360deg) scale(1.3); }
}

/* Readability scrim — light enough to keep the aurora vivid, dark enough to
   keep text crisp. Stronger behind the hero name (upper-left) and the bottom. */
.bg-scrim {
	position: fixed;
	inset: 0;
	z-index: -2;
	pointer-events: none;
	background:
		radial-gradient(80% 75% at 24% 36%, color-mix(in srgb, var(--bg) 70%, transparent), transparent 64%),
		linear-gradient(to bottom, color-mix(in srgb, var(--bg) 42%, transparent) 0%, color-mix(in srgb, var(--bg) 32%, transparent) 20%, color-mix(in srgb, var(--bg) 34%, transparent) 72%, color-mix(in srgb, var(--bg) 62%, transparent) 100%);
}
:root[data-theme="light"] .bg-scrim {
	background:
		radial-gradient(80% 75% at 24% 36%, color-mix(in srgb, var(--bg) 64%, transparent), transparent 64%),
		linear-gradient(to bottom, color-mix(in srgb, var(--bg) 45%, transparent), color-mix(in srgb, var(--bg) 25%, transparent) 30%);
}

/* Film grain — animated, very subtle, sits above the plasma. */
.bg-grain {
	position: fixed;
	inset: -50%;
	z-index: -1;
	pointer-events: none;
	opacity: var(--grain-opacity);
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
	animation: grain 0.6s steps(2) infinite;
}
@keyframes grain {
	0%   { transform: translate(0, 0); }
	25%  { transform: translate(-3%, 2%); }
	50%  { transform: translate(2%, -3%); }
	75%  { transform: translate(-2%, -2%); }
	100% { transform: translate(3%, 3%); }
}

/* Faint scanline texture for the CRT/deck feel. */
.bg-scanlines {
	position: fixed;
	inset: 0;
	z-index: -1;
	pointer-events: none;
	opacity: 0.4;
	background: repeating-linear-gradient(
		to bottom,
		transparent 0,
		transparent 2px,
		rgba(0, 0, 0, 0.05) 3px,
		transparent 4px
	);
	mix-blend-mode: multiply;
}
:root[data-theme="light"] .bg-scanlines { opacity: 0.2; }

/* ----------------------------------------------------------------------------
   custom cursor
   ---------------------------------------------------------------------------- */
.cursor-ring,
.cursor-dot {
	position: fixed;
	top: 0;
	left: 0;
	z-index: 9999;
	pointer-events: none;
	border-radius: 50%;
	mix-blend-mode: difference;
	will-change: transform;
}
.cursor-ring {
	width: 38px;
	height: 38px;
	margin: -19px 0 0 -19px;
	border: 1.5px solid #fff;
	transition: width 220ms var(--ease-spring), height 220ms var(--ease-spring),
		margin 220ms var(--ease-spring), background 220ms var(--ease),
		opacity 220ms var(--ease);
}
.cursor-dot {
	width: 6px;
	height: 6px;
	margin: -3px 0 0 -3px;
	background: #fff;
}
body.cursor-hover .cursor-ring {
	width: 64px;
	height: 64px;
	margin: -32px 0 0 -32px;
	background: rgba(255, 255, 255, 0.18);
}
body.cursor-down .cursor-ring {
	width: 26px;
	height: 26px;
	margin: -13px 0 0 -13px;
}
.cursor-ring.hidden, .cursor-dot.hidden { opacity: 0; }

/* The page-wide accent glow that follows the pointer. */
body::before {
	content: "";
	position: fixed;
	inset: 0;
	pointer-events: none;
	z-index: -1;
	background: radial-gradient(
		320px circle at var(--gx, 50%) var(--gy, 50%),
		var(--accent-soft),
		transparent 62%
	);
	opacity: 0;
	transition: opacity 460ms var(--ease);
	mix-blend-mode: screen;
}
:root[data-theme="light"] body::before { mix-blend-mode: multiply; }
body.glowing::before { opacity: 1; }

@media (hover: none), (pointer: coarse) {
	.cursor-ring, .cursor-dot { display: none; }
}

/* blinking caret — used by the wordmark cursor block */
@keyframes blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } }

/* ----------------------------------------------------------------------------
   utilities
   ---------------------------------------------------------------------------- */
.mono { font-family: var(--font-mono); font-feature-settings: "tnum" 1, "calt" 0; }
.label {
	font-size: 0.7rem;
	letter-spacing: 0.22em;
	text-transform: uppercase;
	color: var(--muted);
	font-weight: 500;
}
.small { font-size: 0.78rem; }
.dim { opacity: 0.55; }
/* Gradient stops mix --fg (light) into each accent so the gradient
   stays in the brand color register but reads against the dark aurora.
   Earlier 85/75/70 lift looked fine on a desktop LCD but rendered
   markedly dimmer on iPhone OLED — crushed blacks make the right side
   of "Vargas" fade. Lifted to 65/55/45 so it carries on iOS displays
   too without losing the burgundy identity. */
.grad-text {
	background: linear-gradient(100deg,
		color-mix(in srgb, var(--accent)   65%, var(--fg)),
		color-mix(in srgb, var(--accent-2) 55%, var(--fg)) 45%,
		color-mix(in srgb, var(--accent-3) 45%, var(--fg)));
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-text-fill-color: transparent;
	color: transparent;
}
/* Safety net: if a browser can't clip the gradient to the text, fall back to
   a solid bright accent fill so the name/email are NEVER invisible. */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
	.grad-text {
		background: none;
		-webkit-text-fill-color: var(--accent-text);
		color: var(--accent-text);
	}
}

/* ----------------------------------------------------------------------------
   scroll rail (left side progress + section markers)
   ---------------------------------------------------------------------------- */
.scroll-rail {
	position: fixed;
	left: clamp(0.6rem, 1.6vw, 1.4rem);
	top: 50%;
	transform: translateY(-50%);
	z-index: 45;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.85rem;
}
.scroll-rail .rail-dot {
	position: relative;
	width: 8px;
	height: 8px;
	border-radius: 50%;
	border: 1px solid var(--rule);
	background: transparent;
	transition: all 280ms var(--ease);
}
.scroll-rail .rail-dot::after {
	content: attr(data-label);
	position: absolute;
	left: 18px;
	top: 50%;
	transform: translateY(-50%) translateX(-6px);
	font-family: var(--font-mono);
	font-size: 0.62rem;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--muted);
	white-space: nowrap;
	opacity: 0;
	pointer-events: none;
	transition: opacity 220ms var(--ease), transform 220ms var(--ease);
}
.scroll-rail .rail-dot:hover::after { opacity: 1; transform: translateY(-50%); }
.scroll-rail .rail-dot.active {
	background: var(--accent);
	border-color: var(--accent);
	box-shadow: 0 0 12px var(--accent-glow);
	transform: scale(1.4);
}
@media (max-width: 960px) { .scroll-rail { display: none; } }

/* ----------------------------------------------------------------------------
   nav
   ---------------------------------------------------------------------------- */
.nav {
	position: sticky;
	top: 0;
	z-index: 50;
	font-size: 0.85rem;
	pointer-events: none;
}
.nav-inner {
	pointer-events: auto;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 1.1rem var(--page-pad);
	/* Reserve room for the fixed top-right toggles (LIGHT + PALETTE pills,
	   ~186px intrinsic). Tied to page-pad + a constant so it tracks the
	   toggles' real pixel width at every viewport instead of drifting with
	   vw — prevents the "Contact" link from sliding under the pills. */
	padding-right: calc(var(--page-pad) + 13.5rem);
	max-width: var(--max-w);
	margin: 0 auto;
	border: 1px solid transparent;
	border-radius: 0;
	transition:
		max-width 560ms var(--ease), margin 560ms var(--ease),
		padding 560ms var(--ease), background 420ms var(--ease),
		border-color 420ms var(--ease), border-radius 560ms var(--ease),
		box-shadow 420ms var(--ease), backdrop-filter 420ms var(--ease);
}
.nav-inner.scrolled {
	max-width: 680px;
	margin: 0.7rem auto 0;
	padding: 0.5rem 0.7rem 0.55rem 1.15rem;
	border-radius: 999px;
	background: color-mix(in srgb, var(--bg) 52%, transparent);
	backdrop-filter: saturate(180%) blur(24px);
	-webkit-backdrop-filter: saturate(180%) blur(24px);
	border-color: var(--rule);
	box-shadow:
		0 18px 50px -14px rgba(0, 0, 0, 0.6),
		inset 0 1px 0 0 rgba(255, 255, 255, 0.08);
}

/* Wordmark — a clean, terminal-flavored logotype: a blinking gradient cursor
   block, bold "julivn", gradient ".dev", and a live status dot. Crisp at any
   size, unlike the old handwritten script that crowded the nav pill. */
.nav .mark {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	color: var(--fg);
	font-family: var(--font-display);
	font-weight: 700;
	font-size: clamp(1.05rem, 1.4vw, 1.2rem);
	letter-spacing: -0.02em;
	line-height: 1;
	transition: color 220ms var(--ease), gap 560ms var(--ease);
}
.mark-word { display: inline-flex; align-items: baseline; }
/* The TLD's gradient previously stopped at --accent-3 (dusky violet),
   which on top of the burgundy aurora bg dropped contrast so far the
   ".dev" was barely visible. Mixing each gradient stop with --fg
   (light) lifts the whole ".dev" toward readable without losing the
   accent identity — still recognizably the brand color, just bright
   enough to read on any background the aurora drifts past. */
.mark-tld {
	background: linear-gradient(100deg,
		color-mix(in srgb, var(--accent)   75%, var(--fg)),
		color-mix(in srgb, var(--accent-2) 65%, var(--fg)) 55%,
		color-mix(in srgb, var(--accent-3) 60%, var(--fg)));
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}
.mark-status {
	width: 7px; height: 7px; border-radius: 50%;
	background: #22c55e;
	box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.5);
	animation: pulse-green 2.4s var(--ease) infinite;
	flex: 0 0 auto;
	margin-left: 0.15rem;
}
@media (max-width: 400px) { .mark-status { display: none; } }
@media (hover: hover) { .nav .mark:hover .mark-word { color: var(--accent); } }

.nav-toggles {
	position: fixed;
	top: 1rem;
	right: var(--page-pad);
	z-index: 60;
	display: flex;
	align-items: center;
	gap: clamp(0.35rem, 1vw, 0.6rem);
}
.theme-toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.45rem;
	height: 34px;
	min-width: 5.5rem;
	padding: 0 0.8rem;
	font-size: 0.68rem;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--muted);
	background: var(--glass);
	border: 1px solid var(--rule);
	border-radius: 999px;
	cursor: pointer;
	font-family: var(--font-mono);
	backdrop-filter: blur(10px);
	-webkit-backdrop-filter: blur(10px);
	transition: color 220ms var(--ease), border-color 220ms var(--ease), background 220ms var(--ease);
}
@media (hover: hover) {
	.theme-toggle:hover { color: var(--fg); border-color: color-mix(in srgb, var(--accent) 50%, var(--rule)); }
}
.theme-toggle .theme-icon { color: var(--accent); line-height: 1; display: inline-flex; }
.theme-icon .icon-sun, .theme-icon .icon-moon { display: none; }
.theme-icon .icon-sun { display: block; }
:root[data-theme="light"] .theme-icon .icon-sun  { display: none; }
:root[data-theme="light"] .theme-icon .icon-moon { display: block; }

.accent-picker { position: relative; overflow: hidden; }
.accent-icon { flex: 0 0 auto; display: block; color: var(--muted); }
.accent-icon .accent-drop { fill: var(--accent); stroke: none; }
.accent-input {
	position: absolute; inset: 0; width: 100%; height: 100%;
	opacity: 0; cursor: pointer; border: 0; padding: 0; background: none;
}
.accent-input::-webkit-color-swatch-wrapper { padding: 0; }
.accent-input::-webkit-color-swatch { border: none; }

/* palette presets popover */
.palette-wrap { position: relative; }
.palette-pop {
	position: absolute;
	top: calc(100% + 0.5rem);
	right: 0;
	display: flex;
	gap: 0.5rem;
	padding: 0.6rem;
	background: color-mix(in srgb, var(--bg) 80%, transparent);
	border: 1px solid var(--rule);
	border-radius: 14px;
	backdrop-filter: blur(20px);
	-webkit-backdrop-filter: blur(20px);
	box-shadow: 0 18px 50px -14px rgba(0, 0, 0, 0.6);
	opacity: 0;
	visibility: hidden;
	transform: translateY(-6px) scale(0.96);
	transform-origin: top right;
	transition: opacity 200ms var(--ease), transform 200ms var(--ease-spring), visibility 200ms;
}
.palette-wrap.open .palette-pop { opacity: 1; visibility: visible; transform: none; }
.swatch {
	width: 26px; height: 26px;
	border-radius: 8px;
	border: 1px solid rgba(255, 255, 255, 0.2);
	cursor: pointer;
	padding: 0;
	transition: transform 180ms var(--ease-spring);
}
@media (hover: hover) { .swatch:hover { transform: scale(1.18) translateY(-2px); } }

.nav nav { display: flex; gap: clamp(0.75rem, 2.5vw, 2rem); align-items: center; }
.nav nav a {
	color: var(--muted);
	font-weight: 500;
	font-family: var(--font-mono);
	font-size: 0.78rem;
	letter-spacing: 0.04em;
	padding: 0.3rem 0;
	position: relative;
	transition: color 180ms var(--ease);
}
.nav nav a::after {
	content: "";
	position: absolute;
	left: 0; right: 0; bottom: -2px;
	height: 1px;
	background: var(--accent);
	transform: scaleX(0);
	transform-origin: left;
	transition: transform 240ms var(--ease);
}
.nav nav a:hover { color: var(--fg); }
.nav nav a:hover::after, .nav nav a.current::after { transform: scaleX(1); }
.nav nav a.current { color: var(--fg); }

/* ----------------------------------------------------------------------------
   section heads
   ---------------------------------------------------------------------------- */
.section-head {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 1.5rem;
	padding-bottom: 1.4rem;
	margin-bottom: clamp(2.5rem, 6vw, 4.5rem);
	border-bottom: 1px solid var(--rule);
}
.section-head .label { position: relative; }

/* ----------------------------------------------------------------------------
   hero
   ---------------------------------------------------------------------------- */
.hero {
	min-height: calc(100dvh - 56px);
	padding: clamp(2.5rem, 7vw, 5rem) var(--page-pad) clamp(2rem, 5vw, 3.5rem);
	max-width: var(--max-w);
	margin: 0 auto;
	display: flex;
	flex-direction: column;
	justify-content: center;
	position: relative;
}
.hero-top {
	display: flex;
	justify-content: space-between;
	align-items: center;
	flex-wrap: wrap;
	gap: 1rem;
	margin-bottom: clamp(1.5rem, 5vw, 3rem);
}
.avail {
	display: inline-flex;
	align-items: center;
	gap: 0.55rem;
	font-family: var(--font-mono);
	font-size: 0.7rem;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--fg);
	padding: 0.4rem 0.85rem;
	border: 1px solid color-mix(in srgb, #22c55e 35%, var(--rule));
	border-radius: 999px;
	background: color-mix(in srgb, #22c55e 8%, transparent);
}
.avail .dot {
	width: 7px; height: 7px; border-radius: 50%;
	background: #22c55e;
	box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.55);
	animation: pulse-green 2.4s var(--ease) infinite;
}
@keyframes pulse-green {
	0%   { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.35); }
	70%  { box-shadow: 0 0 0 8px rgba(34, 197, 94, 0); }
	100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); }
}

.hero-grid {
	display: grid;
	grid-template-columns: 1fr;
	gap: clamp(2rem, 5vw, 3.5rem);
	align-items: end;
}

/* kinetic title with 3D tilt — overflow visible on every wrapper so
   iOS Safari can't clip brush-script ascenders/descenders that extend
   past the line-height box. Defensive against name-clipping reports. */
.hero, .hero-grid, .hero-title-wrap { overflow: visible; }
.hero-title-wrap { perspective: 1200px; }

/* Metatron's Cube — 13-circle sacred-geometry mark in the hero.
   Rotation is driven by a JS momentum integrator (see script.js):
   mouse velocity feeds angular velocity (--rot), click injects a
   big shove, the spin decays back to a slow baseline. CSS just paints
   the current frame. The orbit wrapper handles position; the SVG
   itself takes the rotation, so transforms don't compete. */
.metatron-orbit {
	position: absolute;
	top: 50%;
	right: clamp(1.5rem, 5vw, 6rem);
	width: clamp(320px, 38vw, 480px);
	aspect-ratio: 1;
	transform: translateY(-50%);
	z-index: 1;
	/* The orbit is the tap target — a tap anywhere inside its bounds
	   (including the transparent gaps between the cube's lines) spins
	   it. Without this, only direct hits on the thin SVG strokes
	   registered, which is why tapping "near" it did nothing on mobile. */
	pointer-events: auto;
	cursor: pointer;
}
.metatron {
	width: 100%;
	height: 100%;
	display: block;
	pointer-events: none; /* taps go to the orbit, not individual strokes */
	color: var(--accent-text);
	filter: drop-shadow(0 0 22px color-mix(in srgb, var(--accent) 38%, transparent));
	transform-origin: 50% 50%;
	/* Two rotation modes:
	   · Default — slow baseline spin via the metatron-spin keyframe.
	     Compositor-only, zero per-frame JS. Smooth scroll on mobile.
	   · .boosted — JS owns the rotation via --rot (momentum integrator).
	     Added on a kick/tap, removed when momentum decays to idle. */
	animation:
		metatron-spin 80s linear infinite,
		metatron-pulse 9s ease-in-out infinite;
	will-change: transform, opacity;
	outline: none;
	border-radius: 50%;
}
.metatron.boosted {
	animation: metatron-pulse 9s ease-in-out infinite; /* spin paused; JS drives */
	transform: rotate(var(--rot, 0deg));
}
@keyframes metatron-spin {
	from { transform: rotate(0deg); }
	to   { transform: rotate(360deg); }
}
.metatron-lines line,
.metatron-circles circle {
	stroke-dasharray: 700;
	stroke-dashoffset: 700;
	animation: metatron-draw 2.6s var(--ease-out) 0.8s both;
}
.metatron-lines line { opacity: 0.55; }
.metatron-circles circle { opacity: 0.75; }
@keyframes metatron-pulse {
	0%, 100% { opacity: 0.6; }
	50%      { opacity: 1; }
}
@keyframes metatron-draw {
	to { stroke-dashoffset: 0; }
}

/* Mobile: cube on the upper-right of the hero, between the hero-top
   status row and the name. Sized so it doesn't crowd either —
   ~120-170px wide — and right-anchored to the page padding. CSS owns
   the position (no JS override for mobile). Doesn't overlap the
   "AVAILABLE FOR WORK / NEON / time" row above it because of the
   top offset, and doesn't reach the "Julian" baseline below it. */
@media (max-width: 900px) {
	.metatron-orbit {
		position: absolute;
		top: clamp(7.5rem, 20vh, 11rem);
		right: clamp(0.5rem, 3vw, 1.5rem);
		left: auto;
		width: clamp(120px, 34vw, 170px);
		margin: 0;
		transform: none;
		opacity: 0.55;
		z-index: 0;
	}
	/* The drop-shadow filter is the expensive part of animating a
	   rotating element on mobile GPUs — the filter region recomputes
	   every frame. Drop it on mobile (when not in neon mode); the cube
	   still reads fine as a line drawing without the soft glow. */
	.metatron { filter: none; }
}

/* Neon mode: the cube lights up like the name. Stacked drop-shadows
   plus a brighter currentColor for the strokes. */
body.neon-on .metatron {
	color: #fff8f2;
	filter:
		drop-shadow(0 0 4px rgba(255, 245, 240, 1))
		drop-shadow(0 0 14px color-mix(in srgb, var(--accent-neon) 90%, transparent))
		drop-shadow(0 0 38px color-mix(in srgb, var(--accent-neon) 80%, transparent))
		drop-shadow(0 0 80px color-mix(in srgb, var(--accent-neon) 55%, transparent));
	animation: metatron-neon-pulse 4.8s ease-in-out infinite;
}
/* The orbit's mobile rule dims to 0.55 so the cube doesn't dominate
   the name. In neon mode the cube lights up brighter so the glow
   reads against the dark bg. */
body.neon-on .metatron-orbit { opacity: 1; }
body.neon-on .metatron-lines line { opacity: 0.9; }
body.neon-on .metatron-circles circle { opacity: 1; }
@keyframes metatron-neon-pulse {
	0%, 100% { filter:
		drop-shadow(0 0 3px rgba(255, 245, 240, 0.95))
		drop-shadow(0 0 12px color-mix(in srgb, var(--accent-neon) 85%, transparent))
		drop-shadow(0 0 32px color-mix(in srgb, var(--accent-neon) 75%, transparent))
		drop-shadow(0 0 70px color-mix(in srgb, var(--accent-neon) 50%, transparent)); }
	50%      { filter:
		drop-shadow(0 0 5px rgba(255, 245, 240, 1))
		drop-shadow(0 0 18px color-mix(in srgb, var(--accent-neon) 95%, transparent))
		drop-shadow(0 0 46px color-mix(in srgb, var(--accent-neon) 90%, transparent))
		drop-shadow(0 0 100px color-mix(in srgb, var(--accent-neon) 65%, transparent)); }
}

@media (prefers-reduced-motion: reduce) {
	.metatron { animation: none; }
	.metatron-orbit { transition: none; }
	.metatron-lines line,
	.metatron-circles circle {
		stroke-dasharray: none;
		stroke-dashoffset: 0;
		animation: none;
	}
}
.display {
	font-family: var(--font-script);
	font-weight: 400;
	/* Water Brush is a brush-script — strokes are thinner per em than a
	   bold sans, so the type runs larger to carry the same presence. */
	font-size: clamp(4.5rem, 14vw, 11.5rem);
	/* line-height: 1 keeps Julian and Vargas tight to each other (script
	   faces are designed for tight stacking — that's their visual flow).
	   Generous margin-top gives the first line's J/L/I ascenders room
	   to extend above the .display box without being clipped by the
	   hero-top row on iPhone. */
	line-height: 1;
	letter-spacing: 0;
	margin: 0.35em 0 0;
	text-wrap: balance;
	transform-style: preserve-3d;
	will-change: transform;
}
/* Lines are tight on purpose (script flow). Just enough vertical paint
   room so Vargas's bottom curl and any glyph that strays past the
   line-box edge has somewhere to render. Matched neg margins keep
   the visual line spacing exactly equal to line-height alone. */
.display-line {
	display: block;
	padding: 0 0 0.08em;
	margin: 0 0 -0.08em;
}
/* The off-white Julian line blends into the bright burgundy aurora
   bloom in the upper-left of the hero, especially on iPhone OLED where
   the bg looks brighter. A subtle dark text-shadow gives the J/L/I
   ascenders a thin halo so they read cleanly against any bg the
   aurora drifts past. Vargas isn't affected — its .grad-text uses
   background-clip: text with a transparent fill, so adding text-shadow
   there would paint a weird per-glyph shadow on top of the gradient.
   When neon is on, body.neon-on .display-line overrides this with the
   neon stack, so the dark shadow never fights the bright glow. */
.display-line:first-child {
	text-shadow: 0 1px 8px rgba(0, 0, 0, 0.6), 0 0 2px rgba(0, 0, 0, 0.4);
}
/* `background-clip: text` only paints the gradient within the element's
   own background box. Asymmetric padding so glyphs aren't clipped at the
   paint edge — but importantly, the LEFT side stays near zero so the
   whole inline-block isn't pulled past the hero's page-pad on mobile.
     · top: 0.18em — apex of V, top hook of g
     · right: 0.18em — S's curl extends past its advance metric
     · bottom: 0.3em — descenders, S's bottom curl
     · left: 0.02em — minimal; just enough that the V's leftmost vertex
                      isn't clipped by aggressive subpixel rounding,
                      without pulling Vargas off the left edge of the
                      viewport on a ~390px phone.
   Matched negative margins keep the visual flow identical. */
.display-line .grad-text {
	display: inline-block;
	padding: 0.18em 0.18em 0.3em 0.02em;
	margin: -0.18em -0.18em -0.3em -0.02em;
}
.display-period { color: var(--accent-text); }

/* per-char scramble/rise spans injected by JS */
.display .ch {
	display: inline-block;
	will-change: transform, opacity;
}

/* ============================================================================
   Neon sign mode — when the user clicks the switch in the hero-top row,
   <body> gets class="neon-on" and the name lights up. Stacked
   drop-shadow filters fake a real neon halo; a small flicker animation
   imitates the bad-tube wobble of an old sign. JS persists in
   localStorage so the state survives reloads.
   ============================================================================ */
.neon-switch {
	display: inline-flex;
	align-items: center;
	gap: 0.55rem;
	padding: 0.35rem 0.7rem 0.35rem 0.5rem;
	font-family: var(--font-mono);
	font-size: 0.62rem;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	color: var(--muted);
	background: var(--glass);
	border: 1px solid var(--rule);
	border-radius: 999px;
	cursor: pointer;
	transition: color 220ms var(--ease), border-color 220ms var(--ease), background 220ms var(--ease);
}
.neon-switch-rail {
	position: relative;
	width: 28px;
	height: 16px;
	border-radius: 999px;
	background: color-mix(in srgb, var(--bg) 70%, transparent);
	border: 1px solid var(--rule);
	flex: 0 0 auto;
}
.neon-switch-knob {
	position: absolute;
	top: 1px;
	left: 1px;
	width: 12px;
	height: 12px;
	border-radius: 50%;
	background: var(--muted);
	transition:
		left 280ms cubic-bezier(0.5, 1.5, 0.5, 1),
		background 220ms var(--ease),
		box-shadow 220ms var(--ease);
}
.neon-switch[aria-pressed="true"] {
	color: var(--accent-text);
	border-color: color-mix(in srgb, var(--accent-text) 60%, var(--rule));
	background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.neon-switch[aria-pressed="true"] .neon-switch-knob {
	left: 14px;
	background: var(--accent-text);
	box-shadow:
		0 0 6px color-mix(in srgb, var(--accent-text) 80%, transparent),
		0 0 14px color-mix(in srgb, var(--accent) 60%, transparent);
}
@media (hover: hover) {
	.neon-switch:hover { color: var(--fg); border-color: color-mix(in srgb, var(--accent) 50%, var(--rule)); }
}

/* The neon light itself — applied when <body> has .neon-on.
   Double-layered glow:
     · filter: drop-shadow stack on .display — bounding-box glow,
       handles the soft outer wash and the tight inner halo.
     · text-shadow stack on .display-line — follows the glyph shape
       per character, fakes the actual tube light radiating from each
       letterform. iOS Safari renders these much brighter than filter
       drop-shadow alone, which is what was making the effect feel
       weak on mobile.
   Flicker animation breaks the steady glow with sub-second drops,
   like a real tube settling in. */
body.neon-on .display {
	filter:
		drop-shadow(0 0 4px rgba(255, 245, 240, 1))
		drop-shadow(0 0 14px color-mix(in srgb, var(--accent-neon) 90%, transparent))
		drop-shadow(0 0 36px color-mix(in srgb, var(--accent-neon) 85%, transparent))
		drop-shadow(0 0 80px color-mix(in srgb, var(--accent-neon) 70%, transparent))
		drop-shadow(0 0 130px color-mix(in srgb, var(--accent-2) 55%, transparent));
	animation: neon-flicker 5.2s steps(1, end) infinite;
}
body.neon-on .display-line {
	text-shadow:
		0 0 2px #fff,
		0 0 6px #fff,
		0 0 14px color-mix(in srgb, var(--accent-neon) 95%, transparent),
		0 0 30px var(--accent-neon),
		0 0 60px var(--accent-neon),
		0 0 100px var(--accent-2);
}
/* Julian: the un-gradient line gets a hot white-ish fill so it reads
   as the bright tube color, not its normal off-white. */
body.neon-on .display-line:first-child {
	color: #fff8f2;
	-webkit-text-fill-color: #fff8f2;
}
/* Vargas: the gradient shifts to a hot white-into-brand blend, with
   the V end white-hot like a tube's brightest point. */
body.neon-on .grad-text {
	background: linear-gradient(100deg,
		#fff8f2 0%,
		color-mix(in srgb, var(--accent) 45%, #fff) 30%,
		color-mix(in srgb, var(--accent-2) 70%, #fff) 65%,
		var(--accent-text));
	-webkit-background-clip: text;
	background-clip: text;
}
body.neon-on .display-period {
	color: #fff8f2;
	text-shadow:
		0 0 4px #fff,
		0 0 10px var(--accent-neon),
		0 0 24px var(--accent-neon);
}
@keyframes neon-flicker {
	0%, 3%, 5%, 8%, 12%, 100% { opacity: 1; }
	2%, 4%, 7%               { opacity: 0.55; }
	13%                      { opacity: 0.82; }
	14%, 99%                 { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
	body.neon-on .display { animation: none; }
}
.display .ch.rising {
	opacity: 0;
	transform: translateY(0.5em) rotateX(-40deg);
	animation: char-rise 760ms var(--ease-out) forwards;
}
@keyframes char-rise {
	to { opacity: 1; transform: none; }
}
/* Settled state forced by JS after the stagger window — guarantees the hero
   name is always visible even if the animation never fires. */
.display.settled .ch {
	opacity: 1 !important;
	transform: none !important;
	animation: none !important;
}

/* margin-top scales: enough room on desktop for the gradient name to
   breathe, more on mobile where the Water Brush descender ('g' tail in
   Vargas) needs clearance over "AI Engineer" below. */
.hero-tail { max-width: 640px; margin-top: clamp(2.25rem, 5vw, 2.75rem); }
.role {
	font-size: clamp(1.05rem, 1.6vw, 1.3rem);
	color: var(--fg);
	margin-bottom: 0.6rem;
	font-weight: 500;
	text-wrap: pretty;
}
/* "AI Engineer" — the job title beside the descriptive role line.
   Deliberately NOT the script font: matching the name would make the
   two reads merge into one texture; the contrast (brush name vs.
   clean-sans title) gives proper hierarchy. Bold, in the accent
   color so it still ties visually back to the name's gradient. */
.role-title {
	font-family: var(--font-display);
	font-weight: 700;
	color: var(--accent-text);
	letter-spacing: -0.01em;
}
.lead-statement {
	font-size: clamp(0.92rem, 1.2vw, 1.04rem);
	line-height: 1.6;
	color: var(--muted);
	margin: 0.9rem 0 1.2rem;
	padding-left: 1rem;
	border-left: 2px solid var(--accent);
	text-wrap: pretty;
}
.looking { font-size: 0.95rem; color: var(--muted); }

.hero-cta {
	display: inline-flex;
	align-items: center;
	gap: 0.6rem;
	margin-top: 1.6rem;
	padding: 0.85rem 1.5rem;
	font-family: var(--font-mono);
	font-size: 0.8rem;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--fg);
	border: 1px solid var(--rule);
	border-radius: 999px;
	background: var(--glass);
	position: relative;
	overflow: hidden;
	transition: color 240ms var(--ease), border-color 240ms var(--ease);
}
.hero-cta::before {
	content: "";
	position: absolute;
	inset: 0;
	background: linear-gradient(100deg, var(--accent), var(--accent-2), var(--accent-3));
	opacity: 0;
	z-index: -1;
	transition: opacity 280ms var(--ease);
}
.hero-cta .arrow { transition: transform 240ms var(--ease-spring); }
@media (hover: hover) {
	.hero-cta:hover { color: #fff; border-color: transparent; }
	.hero-cta:hover::before { opacity: 1; }
	.hero-cta:hover .arrow { transform: translateX(5px); }
}

.hero-scroll {
	display: flex;
	align-items: center;
	gap: 0.7rem;
	margin-top: clamp(2rem, 5vw, 3.5rem);
	color: var(--muted);
	font-family: var(--font-mono);
	font-size: 0.66rem;
	letter-spacing: 0.2em;
	text-transform: uppercase;
}
.scroll-chevron {
	display: inline-block;
	font-size: 1.1rem;
	color: var(--accent);
	animation: dock-bounce 2.4s infinite;
}
@keyframes dock-bounce {
	0%, 100% { transform: translateY(0); }
	10% { transform: translateY(7px); }
	20% { transform: translateY(0); }
	30% { transform: translateY(3px); }
	40% { transform: translateY(0); }
}

/* ----------------------------------------------------------------------------
   marquee
   ---------------------------------------------------------------------------- */
.marquee {
	border-top: 1px solid var(--rule);
	border-bottom: 1px solid var(--rule);
	overflow: hidden;
	padding: 1rem 0;
	background: color-mix(in srgb, var(--bg) 40%, transparent);
	backdrop-filter: blur(8px);
	-webkit-backdrop-filter: blur(8px);
	mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
	-webkit-mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
}
.marquee + .marquee { border-top: none; }
.marquee-track {
	display: inline-flex;
	gap: 2.5rem;
	white-space: nowrap;
	font-family: var(--font-mono);
	font-size: 0.8rem;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--muted);
	animation: marquee 46s linear infinite;
	will-change: transform;
}
.marquee.rev .marquee-track { animation-direction: reverse; animation-duration: 58s; }
.marquee-track > span { display: inline-block; }
.marquee-track .mq-dot { color: var(--accent); font-size: 0.55em; transform: translateY(-0.18em); }
@keyframes marquee {
	from { transform: translate3d(0, 0, 0); }
	to   { transform: translate3d(-50%, 0, 0); }
}

/* ----------------------------------------------------------------------------
   sections (shared spacing)
   ---------------------------------------------------------------------------- */
.work, .about, .stack-section, .contact {
	padding: clamp(4rem, 9vw, 7rem) var(--page-pad) clamp(2rem, 5vw, 3.5rem);
	max-width: var(--max-w);
	margin: 0 auto;
	position: relative;
}

/* ----------------------------------------------------------------------------
   project modules
   ---------------------------------------------------------------------------- */
.project {
	position: relative;
	padding: clamp(2rem, 5vw, 3rem);
	margin-bottom: clamp(1.5rem, 4vw, 2.5rem);
	border: 1px solid var(--rule);
	border-radius: 20px;
	background: var(--glass);
	backdrop-filter: blur(12px);
	-webkit-backdrop-filter: blur(12px);
	container-type: inline-size;
	container-name: project;
	overflow: hidden;
	transform-style: preserve-3d;
	transition: transform 200ms var(--ease), border-color 300ms var(--ease), box-shadow 300ms var(--ease);
	will-change: transform;
	/* Visual reading order: title first, then supporting stats below.
	   DOM order keeps stats next to the project-head for screen-reader
	   context; CSS flex order just reshuffles the paint. Absolute-
	   positioned children (project-ghost, project-glow) are out of flow,
	   so they're not affected. */
	display: flex;
	flex-direction: column;
}
/* Animated gradient border glow on hover, drawn as a masked pseudo-element. */
.project::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: 20px;
	padding: 1px;
	background: linear-gradient(130deg, var(--accent), var(--accent-2), var(--accent-3));
	-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;
	opacity: 0;
	transition: opacity 360ms var(--ease);
	pointer-events: none;
}
@media (hover: hover) {
	.project:hover {
		border-color: transparent;
		box-shadow: 0 30px 80px -30px color-mix(in srgb, var(--accent) 45%, transparent);
	}
	.project:hover::after { opacity: 1; }
}
/* radial spotlight that tracks the cursor inside the card (vars set by JS) */
.project > .project-glow {
	position: absolute;
	inset: 0;
	border-radius: 20px;
	pointer-events: none;
	opacity: 0;
	background: radial-gradient(
		260px circle at var(--mx, 50%) var(--my, 50%),
		color-mix(in srgb, var(--accent) 18%, transparent),
		transparent 60%
	);
	transition: opacity 300ms var(--ease);
	z-index: 0;
}
@media (hover: hover) { .project:hover > .project-glow { opacity: 1; } }
.project > *:not(.project-glow) { position: relative; z-index: 1; }

.project-ghost {
	position: absolute;
	/* Pulled further toward the card's outer-right edge so the giant
	   outline numeral bleeds OFF the right side instead of crowding the
	   content. Reads as editorial backdrop rather than a competing
	   element. The card itself has overflow:hidden so the bleed is
	   clipped at the card boundary — no horizontal scrollbar. */
	top: -2rem;
	right: -2.5rem;
	font-family: var(--font-display);
	font-weight: 900;
	font-size: clamp(7rem, 20cqi, 16rem);
	line-height: 0.85;
	letter-spacing: -0.05em;
	color: transparent;
	-webkit-text-stroke: 1px var(--rule);
	pointer-events: none;
	user-select: none;
	z-index: 0;
}

.project-head {
	display: flex;
	align-items: center;
	gap: 1rem;
	margin-bottom: 1.5rem;
}
.project-num { color: var(--accent-text); font-size: 0.85rem; letter-spacing: 0.1em; font-family: var(--font-mono); }
.badge {
	display: inline-flex;
	align-items: center;
	font-family: var(--font-mono);
	font-size: 0.66rem;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.12em;
	padding: 0.3rem 0.65rem;
	border-radius: 999px;
	background: var(--accent-soft);
	color: var(--accent-text);
}
.badge.live::before {
	content: "";
	display: inline-block;
	width: 6px; height: 6px; border-radius: 50%;
	background: var(--accent);
	margin-right: 0.5rem;
	animation: pulse 2.4s var(--ease) infinite;
}
@keyframes pulse {
	0%   { box-shadow: 0 0 0 0 var(--accent-soft); }
	70%  { box-shadow: 0 0 0 7px transparent; }
	100% { box-shadow: 0 0 0 0 transparent; }
}

.project-stats {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
	gap: 1.25rem 2rem;
	margin: 0 0 2rem;
	padding: 0 0 1.5rem;
	border-bottom: 1px solid var(--rule);
}
.project-stats.cols-3, .project-stats.cols-2 {
	grid-template-columns: repeat(auto-fit, minmax(140px, 240px));
	/* Without this, a 2-stat card at tablet+ widths leaves an orphan
	   empty column on the right (auto-fit packs items from the start,
	   not across the full row). Centering the row reads as intentional. */
	justify-content: center;
}
.project-stats li { display: flex; flex-direction: column; gap: 0.25rem; }
.project-stats .stat-num {
	font-family: var(--font-display);
	font-size: clamp(1.2rem, 2vw, 1.6rem);
	font-weight: 700;
	color: var(--fg);
	letter-spacing: -0.01em;
}
.project-stats .stat-label {
	font-size: 0.66rem;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--muted);
	font-family: var(--font-mono);
}

.project-title {
	font-family: var(--font-display);
	font-weight: 800;
	font-size: clamp(1.8rem, 9cqi, 5.5rem);
	line-height: 0.95;
	letter-spacing: -0.04em;
	margin: 0 0 1.5rem;
	overflow-wrap: anywhere;
	text-wrap: balance;
	transition: transform 300ms var(--ease);
}
@media (hover: hover) { .project:hover .project-title { transform: translateX(6px); } }

.project-desc {
	font-size: clamp(1rem, 1.3vw, 1.12rem);
	max-width: 70ch;
	color: var(--fg);
	margin: 0 0 2rem;
	opacity: 0.9;
	text-wrap: pretty;
}
.project-desc code, .architecture code {
	font-family: var(--font-mono);
	font-size: 0.9em;
	background: rgba(127, 127, 160, 0.16);
	padding: 0.08em 0.4em;
	border-radius: 4px;
}

.tags { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 2rem; }
.tags li {
	font-family: var(--font-mono);
	font-size: 0.7rem;
	letter-spacing: 0.04em;
	padding: 0.35rem 0.7rem;
	border: 1px solid var(--rule);
	border-radius: 999px;
	color: var(--muted);
	transition: border-color 180ms var(--ease), color 180ms var(--ease), background 180ms var(--ease);
}
/* Tighter chip metrics when the project card is narrow (tablet single-
   column, or future side-by-side card layouts). Prevents the last 1-2
   tags from orphaning to their own line just from a few pixels. */
@container project (max-width: 720px) {
	.tags { gap: 0.4rem; }
	.tags li { padding: 0.3rem 0.6rem; font-size: 0.67rem; letter-spacing: 0.03em; }
}
@media (hover: hover) {
	.tags li:hover {
		border-color: var(--accent);
		color: var(--fg);
		background: var(--accent-soft);
	}
}

.architecture {
	margin: 0 0 2rem;
	border: 1px solid var(--rule);
	border-radius: 10px;
	background: rgba(0, 0, 0, 0.22);
	overflow: hidden;
}
:root[data-theme="light"] .architecture { background: rgba(0, 0, 0, 0.04); }
.architecture > summary {
	cursor: pointer;
	padding: 0.85rem 1rem;
	font-family: var(--font-mono);
	font-size: 0.74rem;
	letter-spacing: 0.1em;
	color: var(--muted);
	text-transform: uppercase;
	user-select: none;
	list-style: none;
	display: flex;
	align-items: center;
	transition: color 180ms var(--ease);
}
.architecture > summary::-webkit-details-marker { display: none; }
.architecture > summary::before {
	content: "▸";
	margin-right: 0.6rem;
	transition: transform 200ms var(--ease);
	color: var(--accent);
}
.architecture[open] > summary::before { transform: rotate(90deg); }
.architecture > summary:hover { color: var(--fg); }
.architecture pre {
	margin: 0;
	padding: 0.5rem 1rem 1.25rem;
	font-family: var(--font-mono);
	font-size: 0.72rem;
	line-height: 1.5;
	color: color-mix(in srgb, var(--accent-3) 70%, var(--fg));
	overflow-x: auto;
	white-space: pre;
}

.project-links { display: flex; flex-wrap: wrap; gap: 1.25rem 2.5rem; margin-top: 1rem; }
.big-link {
	font-family: var(--font-mono);
	font-size: clamp(0.85rem, 1.2vw, 1rem);
	letter-spacing: 0.02em;
	color: var(--fg);
	display: inline-flex;
	align-items: center;
	gap: 0.6rem;
	padding: 0.5rem 0;
	border-bottom: 1px solid var(--rule);
	transition: color 200ms var(--ease), border-color 200ms var(--ease);
}
.big-link .arrow { transition: transform 240ms var(--ease-spring); }
.big-link:hover { color: var(--accent); border-bottom-color: var(--accent); }
.big-link:hover .arrow { transform: translate(4px, -4px); }

/* ----------------------------------------------------------------------------
   about
   ---------------------------------------------------------------------------- */
.about-body { max-width: 760px; }
.about-body p {
	font-size: clamp(1.15rem, 2vw, 1.7rem);
	line-height: 1.45;
	letter-spacing: -0.01em;
	margin-bottom: 1.5rem;
	color: var(--fg);
	text-wrap: pretty;
}
.about-body p:last-child { margin-bottom: 0; color: var(--muted); }
.about-body strong { color: var(--accent-text); font-weight: 600; }

/* ----------------------------------------------------------------------------
   stack
   ---------------------------------------------------------------------------- */
.stack-groups { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(2rem, 5vw, 4rem); }
.stack-group .group-label {
	font-family: var(--font-mono);
	font-size: 0.7rem;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--accent-text);
	font-weight: 500;
	margin: 0 0 1rem;
	padding-bottom: 0.6rem;
	border-bottom: 1px solid var(--rule);
}
.stack-list { display: flex; flex-direction: column; gap: 0.55rem; }
.stack-list li {
	font-family: var(--font-mono);
	font-size: clamp(0.92rem, 1.3vw, 1.02rem);
	color: var(--fg);
	padding: 0.18rem 0;
	border-bottom: 1px solid transparent;
	transition: color 180ms var(--ease), border-color 180ms var(--ease), padding-left 180ms var(--ease);
	width: fit-content;
}
@media (hover: hover) {
	.stack-list li:hover { color: var(--accent); border-bottom-color: var(--accent); padding-left: 0.4rem; }
}
@media (max-width: 760px) { .stack-groups { grid-template-columns: 1fr; gap: 2.5rem; } }

/* ----------------------------------------------------------------------------
   contact
   ---------------------------------------------------------------------------- */
.contact { overflow: hidden; }
.contact-canvas {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	z-index: 0;
	pointer-events: none;
	opacity: 0.55;
}
.contact > *:not(.contact-canvas) { position: relative; z-index: 1; }
.contact-pre { font-size: clamp(1rem, 1.5vw, 1.25rem); color: var(--muted); margin-bottom: 1.5rem; }
.email-link {
	font-family: var(--font-display);
	font-weight: 800;
	font-size: clamp(1.9rem, 8vw, 5.5rem);
	line-height: 1;
	letter-spacing: -0.04em;
	display: inline-flex;
	align-items: center;
	gap: 0.4em;
	word-break: break-word;
	padding: 0.2em 0;
}
.email-link .arrow { font-size: 0.5em; transition: transform 280ms var(--ease-spring); color: var(--accent); }
.email-link:hover .arrow { transform: translate(8px, -8px); }
.contact-meta {
	margin-top: 2.5rem;
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem 1rem;
	color: var(--muted);
	font-family: var(--font-mono);
	font-size: 0.82rem;
}
.contact-meta a { color: var(--muted); border-bottom: 1px solid transparent; transition: color 180ms var(--ease), border-color 180ms var(--ease); }
.contact-meta a:hover { color: var(--accent); border-bottom-color: var(--accent); }
.contact-meta .sep { opacity: 0.5; }
.contact-loc {
	margin-top: 0.65rem;
	color: var(--muted);
	font-family: var(--font-mono);
	font-size: 0.76rem;
	letter-spacing: 0.06em;
	display: inline-flex;
	align-items: center;
	gap: 0.55rem;
}
.contact-loc .dot-static { width: 6px; height: 6px; border-radius: 50%; background: var(--accent-text); flex: 0 0 auto; }

/* ----------------------------------------------------------------------------
   footer
   ---------------------------------------------------------------------------- */
.site-footer {
	border-top: 1px solid var(--rule);
	padding: 2.5rem var(--page-pad);
	max-width: var(--max-w);
	margin: 0 auto;
}
.footer-grid { display: flex; justify-content: space-between; gap: 2rem; flex-wrap: wrap; }
.site-footer .small { color: var(--muted); margin: 0 0 0.4rem; line-height: 1.5; font-family: var(--font-mono); }

/* ----------------------------------------------------------------------------
   reveal
   ---------------------------------------------------------------------------- */
.reveal {
	opacity: 0;
	transform: translateY(34px);
	transition: opacity 800ms var(--ease-out), transform 800ms var(--ease-out);
	will-change: opacity, transform;
}
.reveal.in { opacity: 1; transform: none; }

/* ----------------------------------------------------------------------------
   page entrance — a coordinated, staggered "load-in" for the above-the-fold
   content (nav, hero text, status panel, scroll cue), echoing the per-character
   name rise. Pure CSS with `both` fill so elements start hidden and ease in on
   first paint — no flash, no JS. Below-the-fold sections keep their scroll
   reveal. Reduced-motion zeroes these out (handled globally) → content shows.
   ---------------------------------------------------------------------------- */
@keyframes intro-up    { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: none; } }
@keyframes intro-down  { from { opacity: 0; transform: translateY(-16px); } to { opacity: 1; transform: none; } }
@keyframes intro-right { from { opacity: 0; transform: translateX(28px) scale(0.98); } to { opacity: 1; transform: none; } }
@keyframes intro-fade  { from { opacity: 0; } to { opacity: 1; } }

.nav-inner    { animation: intro-down  0.7s var(--ease-out) 0.05s both; }
.nav-toggles  { animation: intro-fade  0.9s var(--ease-out) 0.25s both; }
.scroll-rail  { animation: intro-fade  1s   var(--ease-out) 0.6s  both; }
.hero-top     { animation: intro-up    0.8s var(--ease-out) 0.18s both; }
.hero .role           { animation: intro-up 0.75s var(--ease-out) 0.42s both; }
.hero .lead-statement { animation: intro-up 0.75s var(--ease-out) 0.52s both; }
.hero .looking        { animation: intro-up 0.75s var(--ease-out) 0.60s both; }
.hero .hero-cta       { animation: intro-up 0.75s var(--ease-out) 0.68s both; }
.hero-scroll  { animation: intro-fade  1s   var(--ease-out) 0.95s both; }

/* The pure-CSS aurora itself fades up on first load for a soft wash-in. */
.bg, .bg-scrim { animation: intro-fade 1.4s var(--ease-out) both; }

/* ----------------------------------------------------------------------------
   click ripple
   ---------------------------------------------------------------------------- */
.ripple {
	position: fixed;
	pointer-events: none;
	z-index: 70;
	width: 200px; height: 200px;
	overflow: visible;
	transform: translate(-50%, -50%);
}
.ripple .ring {
	fill: none;
	stroke: var(--accent);
	vector-effect: non-scaling-stroke;
	opacity: 0;
	filter: blur(0.5px);
}
.ripple .ring-1 { animation: water-ring 1100ms var(--ease-out) forwards; }
.ripple .ring-2 { animation: water-ring 1100ms var(--ease-out) 130ms forwards; }
.ripple .ring-3 { animation: water-ring 1100ms var(--ease-out) 260ms forwards; }
@keyframes water-ring {
	0%   { r: 5;  opacity: 0;   stroke-width: 2.6; }
	8%   {        opacity: 0.7; }
	100% { r: 90; opacity: 0;   stroke-width: 0.3; }
}

/* ----------------------------------------------------------------------------
   /projects page
   ---------------------------------------------------------------------------- */
.proj-page {
	max-width: 940px;
	margin: 0 auto;
	padding: clamp(4rem, 10vw, 7rem) var(--page-pad);
}
.proj-intro {
	margin-bottom: clamp(3rem, 7vw, 5rem);
	padding-bottom: clamp(2rem, 5vw, 3rem);
	border-bottom: 1px solid var(--rule);
}
.proj-intro h1 {
	font-family: var(--font-display);
	font-weight: 800;
	font-size: clamp(2.6rem, 8vw, 5rem);
	line-height: 1;
	letter-spacing: -0.04em;
	margin: 0 0 1rem;
	/* keep the "j" descender's gradient fill inside the paint box */
	padding-bottom: 0.12em;
}
.proj-intro p { color: var(--muted); font-size: clamp(1rem, 1.4vw, 1.12rem); max-width: 58ch; margin: 0; text-wrap: pretty; }

.proj-card {
	padding: clamp(2rem, 5vw, 2.75rem);
	margin-bottom: clamp(1.25rem, 3vw, 2rem);
	border: 1px solid var(--rule);
	border-radius: 18px;
	background: var(--glass);
	backdrop-filter: blur(12px);
	-webkit-backdrop-filter: blur(12px);
	transition: border-color 280ms var(--ease), box-shadow 280ms var(--ease), transform 280ms var(--ease);
}
@media (hover: hover) {
	.proj-card:hover {
		border-color: color-mix(in srgb, var(--accent) 40%, var(--rule));
		box-shadow: 0 24px 60px -30px color-mix(in srgb, var(--accent) 40%, transparent);
	}
}
.proj-card-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding-bottom: 1rem;
	margin-bottom: 1.5rem;
	border-bottom: 1px solid var(--rule);
	flex-wrap: wrap;
}
.proj-card-head h2 {
	font-family: var(--font-display);
	font-weight: 700;
	font-size: clamp(1.6rem, 3.5vw, 2.3rem);
	letter-spacing: -0.02em;
	margin: 0;
	overflow-wrap: anywhere;
	text-wrap: balance;
}
.proj-status {
	display: inline-flex;
	align-items: center;
	font-family: var(--font-mono);
	font-size: 0.68rem;
	font-weight: 600;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	padding: 0.28rem 0.7rem;
	border: 1px solid var(--rule);
	border-radius: 999px;
	color: var(--fg);
	white-space: nowrap;
}
.proj-status::before {
	content: "";
	width: 6px; height: 6px; border-radius: 50%;
	margin-right: 0.5rem;
	background: var(--muted);
	flex: 0 0 auto;
}
.proj-status.live::before    { background: #22c55e; }
.proj-status.running::before { background: var(--accent-3); }
.proj-status.shipped::before { background: var(--accent-text); }
.proj-status.live    { border-color: color-mix(in srgb, #22c55e 35%, var(--rule)); }
.proj-status.running { border-color: color-mix(in srgb, var(--accent-3) 35%, var(--rule)); }
.proj-status.shipped { border-color: color-mix(in srgb, var(--accent) 38%, var(--rule)); }
.proj-desc { font-size: clamp(1rem, 1.35vw, 1.08rem); line-height: 1.65; max-width: 66ch; margin: 0 0 1.75rem; color: var(--fg); text-wrap: pretty; }
.proj-desc code, .proj-demonstrates code {
	font-family: var(--font-mono);
	font-size: 0.9em;
	background: rgba(127, 127, 160, 0.16);
	padding: 0.08em 0.4em;
	border-radius: 4px;
}
.proj-links { display: flex; flex-direction: column; gap: 0.35rem; margin: 0 0 2rem; }
.proj-links li { font-family: var(--font-mono); font-size: 0.9rem; padding-left: 1.1rem; position: relative; }
.proj-links li::before { content: "→"; position: absolute; left: 0; top: 0.4rem; color: var(--accent); }
.proj-links a {
	color: var(--accent-text);
	border-bottom: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
	padding: 0.4rem 0;
	display: inline-block;
	transition: border-color 220ms var(--ease);
}
.proj-links a:hover { border-bottom-color: var(--accent); }
.proj-demonstrates h3 {
	font-family: var(--font-mono);
	font-size: 0.7rem;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--accent-text);
	margin: 0 0 0.85rem;
}
.proj-demonstrates ul { display: flex; flex-direction: column; gap: 0.55rem; }
.proj-demonstrates li { font-size: 0.94rem; line-height: 1.55; padding-left: 1.2rem; position: relative; color: var(--fg); }
.proj-demonstrates li::before { content: "·"; position: absolute; left: 0.4rem; top: -0.15rem; color: var(--accent); font-weight: 700; font-size: 1.15rem; }
.proj-backlink { margin-top: clamp(3rem, 6vw, 4.5rem); font-size: 0.9rem; font-family: var(--font-mono); }
.proj-backlink a { color: var(--muted); border-bottom: 1px solid transparent; padding: 0.4rem 0; transition: color 220ms var(--ease), border-color 220ms var(--ease); }
.proj-backlink a:hover { color: var(--accent); border-bottom-color: var(--accent); }

/* ----------------------------------------------------------------------------
   responsive
   ---------------------------------------------------------------------------- */
@media (max-width: 760px) {
	/* Toggles move OUT of the nav band to a floating bottom-left cluster
	   so they're reachable with a thumb but stay out of the right side
	   where eyes finish reading. Compact, semi-translucent so they
	   don't obstruct the lead-statement underneath. Palette popover
	   flips upward + leftward to stay on screen. */
	.nav-inner { padding-right: var(--page-pad); gap: 0.5rem; }
	.nav nav { gap: clamp(0.55rem, 2.4vw, 0.95rem); }
	.nav nav a { font-size: 0.72rem; }
	.nav-inner.scrolled { max-width: calc(100% - 1.25rem); margin: 0.5rem auto 0; padding: 0.45rem 0.7rem; }

	.nav-toggles {
		top: auto;
		bottom: calc(0.9rem + env(safe-area-inset-bottom, 0px));
		left: 0.9rem;
		right: auto;
		gap: 0.4rem;
		/* Translucent backplate so the pills read as a single floating
		   control group rather than two pills floating over content.
		   Subtle blur picks up whatever's underneath without dominating. */
		padding: 0.35rem;
		border-radius: 999px;
		background: color-mix(in srgb, var(--bg) 60%, transparent);
		backdrop-filter: blur(14px);
		-webkit-backdrop-filter: blur(14px);
		border: 1px solid var(--rule);
		opacity: 0.85;
		transition: opacity 220ms var(--ease);
	}
	.nav-toggles:hover, .nav-toggles:focus-within { opacity: 1; }
	.theme-toggle { min-width: 0; padding: 0 0.6rem; height: 34px; font-size: 0.6rem; }
	.theme-toggle .theme-label { display: none; }
	.accent-picker .theme-label { display: none; }
	.palette-pop { top: auto; bottom: calc(100% + 0.5rem); left: 0; right: auto; transform-origin: bottom left; }
	.palette-wrap.open .palette-pop { transform: none; }

	/* Mobile WebKit struggles with perspective + per-char translateY/
	   rotateX transforms together on a custom-font heading, producing
	   subpixel shimmer and edge-clipping during scroll. Disable the
	   3D rig on small viewports — the character entrance still plays
	   in plain 2D, just without the perspective dive. */
	.hero-title-wrap { perspective: none; }
	.display { transform-style: flat; }
	.display .ch.rising { transform: translateY(0.45em); }

	.section-head { flex-direction: column; align-items: flex-start; gap: 0.4rem; }
	.hero { min-height: calc(100dvh - 40px); justify-content: center; }
	.hero-top { margin-bottom: 1.5rem; }
	/* The brush-script name's glyph ink overhangs its box on the left (the J's
	   flourish) and the right (s curl / period). On mobile the small page-pad
	   leaves no room, so the viewport's overflow-x:hidden clips it. Give the
	   name horizontal breathing room here (desktop's larger page-pad already
	   contains the proportionally larger swash). */
	.display { padding-left: 0.42em; padding-right: 0.2em; }
	.project { padding: 1.4rem; border-radius: 16px; }
	.project::after, .project > .project-glow { border-radius: 16px; }
	.project-ghost { display: none; }
	.project-stats, .project-stats.cols-2, .project-stats.cols-3 {
		grid-template-columns: repeat(2, 1fr);
		gap: 1rem 1.25rem;
	}
	.project-stats.cols-3 li:last-child { grid-column: 1 / -1; }
	.project-stats .stat-num { font-size: 1.1rem; }
	.contact-meta { flex-direction: column; align-items: flex-start; gap: 0.4rem; }
	.contact-meta .sep { display: none; }
	.nav nav a, .big-link, .contact-meta a, .stack-list li, .tags li, .email-link {
		min-height: 44px;
		display: inline-flex;
		align-items: center;
	}
	.footer-grid { flex-direction: column; gap: 1rem; }
	.hero-cta { min-height: 44px; }
}
@media (max-width: 480px) {
	.marquee-track { font-size: 0.7rem; gap: 1.75rem; }
	.proj-card-head { flex-direction: column; align-items: flex-start; gap: 0.6rem; }
	.proj-card { padding: 1.4rem; }
	/* Water Brush at the 4.5rem desktop floor blew past the viewport on
	   a ~390px phone — script faces are wider per em than sans, and the
	   tall ascenders/descenders cropped on the sides at the wrap. Re-
	   clamped just for mobile so the name fits without overflow. */
	/* letter-spacing intentionally omitted — Water Brush is a brush
	   script with built-in glyph connections; negative tracking does
	   nothing useful here. */
	.display { font-size: clamp(3rem, 16vw, 5rem); }
	.about-body p { font-size: clamp(1.05rem, 4.5vw, 1.4rem); }
}
@media (max-width: 360px) {
	.nav nav { gap: 0.45rem; }
	.nav nav a { font-size: 0.68rem; }
	.mark-svg { height: 2.3rem; }
	.hero-top { flex-direction: column; align-items: flex-start; gap: 0.6rem; }
	/* One more step down for the narrowest tested widths. */
	.display { font-size: clamp(2.6rem, 14vw, 4rem); }
}

/* ----------------------------------------------------------------------------
   reduced motion — kill all animation/transition, drop heavy effects
   ---------------------------------------------------------------------------- */
@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;
	}
	.bg-grain, .bg-scanlines { display: none; }
	.cursor-ring, .cursor-dot { display: none; }
	.reveal { opacity: 1; transform: none; }
	.display .ch.rising { opacity: 1; transform: none; animation: none; }
}
