:root {
  color-scheme: dark;
  --bg: #07080e;
  --bg-deep: #030409;
  --panel: rgba(18, 19, 27, 0.78);
  --panel-solid: #12131b;
  --panel-2: rgba(28, 30, 41, 0.78);
  --panel-3: rgba(36, 39, 53, 0.82);
  --text: #f4f3ee;
  --text-soft: #d0cec7;
  --muted: #a2a3ae;
  --faint: #858692;
  --line: rgba(255, 255, 255, 0.09);
  --line-strong: rgba(255, 255, 255, 0.16);
  --accent: #ffcc21;
  --accent-strong: #ffe36b;
  --accent-2: #ff9f43;
  --accent-soft: rgba(255, 204, 33, 0.16);
  --accent-line: rgba(255, 204, 33, 0.34);
  --good: #34d399;
  --bad: #fb7185;
  --info: #38bdf8;
  --violet: #a78bfa;
  --on-accent: #15120a;
  --shadow-1: 0 18px 50px rgba(0, 0, 0, 0.24);
  --shadow-2: 0 32px 90px rgba(0, 0, 0, 0.38);
  --glow-1: 0 0 34px rgba(255, 204, 33, 0.32);
  --glow-2: 0 0 76px rgba(255, 204, 33, 0.42);
  /* Theme-aware surface highlights. The dark values live here so every rule that needs a
     sheen reads one variable instead of hard-coding white, which is what made the light
     theme blow out. */
  --sheen: linear-gradient(to bottom, rgba(255, 255, 255, 0.085), rgba(255, 255, 255, 0.012) 34%, transparent 66%);
  --edge-highlight: rgba(255, 255, 255, 0.28);
  --inner-top: rgba(255, 255, 255, 0.085);
  --inner-bottom: rgba(0, 0, 0, 0.16);
  --orb-opacity: 0.3;
  --grid-opacity: 0.24;
  --noise-opacity: 0.04;
  /* The art panels behind project visuals are their own surface, not a themed one: they
     stay near-black in the dark theme and become warm paper in the light one, so the
     labels, the dialog code and the accent shapes keep their contrast either way. */
  --art-bg: #090a10;
  --body-glow: 9%;
  /* Background layer strengths. The aurora is capped at 22% on purpose: it is a solid wash
     behind the body copy, and at 55% the muted text fell to 2.1:1, well under WCAG AA. The
     brightest palettes (amber, teal, lime) need the most caution. The mesh and the traces are
     lines rather than a wash, so they carry the visual identity without touching legibility,
     and they can stay strong. */
  --aurora-strength: 13%;
  --hex-opacity: 0.55;
  --trace-opacity: 0.6;
  --r-xs: 10px;
  --r-sm: 14px;
  --r-md: 20px;
  --r-lg: 28px;
  --r-pill: 999px;
  --font: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", Inter, ui-sans-serif, system-ui, sans-serif;
  --mono: "SFMono-Regular", "SF Mono", Menlo, Monaco, Consolas, monospace;
  /* The column used to stop at 1180px, which left wide dead margins on desktop that
     read as empty sides. 1360px keeps every fractional grid and the 62ch prose measure
     intact while giving the content room to breathe. */
  --page: min(1600px, calc(100vw - 96px));
  --ease: cubic-bezier(0.32, 0.72, 0, 1);
  --spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --g: 1;
}

html[data-theme="light"] {
  color-scheme: light;
  /* The light palette used to sit on a near-white page (#f1f0eb) with white panels over
     it, so panels, glass sheen and the ambient glow all summed into the same value and
     every surface read as one flat overexposed sheet. The page is now a warm paper
     (#e9e7e0) and the panels are lifted white, which restores the separation. */
  --bg: #e9e7e0;
  --bg-deep: #ddd9cf;
  --panel: rgba(255, 255, 255, 0.72);
  --panel-solid: #fbfaf7;
  --panel-2: rgba(252, 251, 248, 0.88);
  --panel-3: rgba(243, 241, 235, 0.94);
  --text: #14161c;
  --text-soft: #2f323a;
  --muted: #565b66;
  /* The captions are 9-10px, and #6b707b only reached 4.0:1 on this page, under the AA
     floor the accent tokens are held to. This holds 4.7:1 on the page and 5.5:1 on a panel. */
  --faint: #5f6470;
  --line: rgba(20, 22, 28, 0.13);
  --line-strong: rgba(20, 22, 28, 0.22);
  --on-accent: #17130a;
  --shadow-1: 0 1px 2px rgba(35, 32, 22, 0.05), 0 10px 30px rgba(35, 32, 22, 0.09);
  --shadow-2: 0 2px 4px rgba(35, 32, 22, 0.06), 0 24px 60px rgba(35, 32, 22, 0.14);
  /* A white sheen is a dark-theme trick: on a light panel it is pure overexposure, so
     light panels get a faint highlight instead of a wash. */
  --sheen: linear-gradient(to bottom, rgba(255, 255, 255, 0.22), transparent 58%);
  --edge-highlight: rgba(20, 22, 28, 0.1);
  --inner-top: rgba(255, 255, 255, 0.35);
  --inner-bottom: rgba(20, 22, 28, 0.06);
  --orb-opacity: 0.08;
  --grid-opacity: 0.5;
  --noise-opacity: 0.03;
  /* An accent wash this strong over a light page reads as a blown highlight rather than
     as ambience, so the light theme gets a fifth of it. */
  --body-glow: 2%;
  /* Coloured blobs on paper read as stains, not as glow: the aurora is a whisper so the
     page stays an even sheet, and the neutral hex/traces/grid lines carry the texture.
     The violet orb below adds the second colour highlight. */
  --aurora-strength: 7%;
  --hex-opacity: 0.3;
  --trace-opacity: 0.38;
  /* Same art panels on paper: dark on dark read as a terminal, but a near-black block on
     a white card turned the muted label and the accent code into unreadable dark-on-dark. */
  --art-bg: #f1eee6;
}

/* Accent palettes.
   Twelve hues spaced around the colour wheel so no two swatches read as the same
   colour, plus one neutral. Each supplies the same nine variables. The light variants
   are not the vivid values reused: on warm paper those are unreadable as text, so
   every light --accent-strong is darkened until it clears 4.7:1 against the page,
   and both themes are verified by tests/markup.test.mjs. */

html[data-accent="rose"] {
  --accent: #f55c75;
  --accent-strong: #fd879b;
  --accent-2: #f36b49;
  --accent-soft: rgba(245, 92, 117, 0.16);
  --accent-line: rgba(245, 92, 117, 0.34);
  --glow-1: 0 0 34px rgba(245, 92, 117, 0.32);
  --glow-2: 0 0 76px rgba(245, 92, 117, 0.42);
  --on-accent: #38050d;
}

html[data-accent="coral"] {
  --accent: #f6895a;
  --accent-strong: #feaa86;
  --accent-2: #f5bb47;
  --accent-soft: rgba(246, 137, 90, 0.16);
  --accent-line: rgba(246, 137, 90, 0.34);
  --glow-1: 0 0 34px rgba(246, 137, 90, 0.32);
  --glow-2: 0 0 76px rgba(246, 137, 90, 0.42);
  --on-accent: #391404;
}

html[data-accent="amber"] {
  --accent: #f8d059;
  --accent-strong: #ffe085;
  --accent-2: #e2f745;
  --accent-soft: rgba(248, 208, 89, 0.16);
  --accent-line: rgba(248, 208, 89, 0.34);
  --glow-1: 0 0 34px rgba(248, 208, 89, 0.32);
  --glow-2: 0 0 76px rgba(248, 208, 89, 0.42);
  --on-accent: #3a2c04;
}

html[data-accent="lime"] {
  --accent: #b5f35e;
  --accent-strong: #cbfb88;
  --accent-2: #6ff14b;
  --accent-soft: rgba(181, 243, 94, 0.16);
  --accent-line: rgba(181, 243, 94, 0.34);
  --glow-1: 0 0 34px rgba(181, 243, 94, 0.32);
  --glow-2: 0 0 76px rgba(181, 243, 94, 0.42);
  --on-accent: #233806;
}

html[data-accent="green"] {
  --accent: #68e893;
  --accent-strong: #90f4b1;
  --accent-2: #56e6bb;
  --accent-soft: rgba(104, 232, 147, 0.16);
  --accent-line: rgba(104, 232, 147, 0.34);
  --glow-1: 0 0 34px rgba(104, 232, 147, 0.32);
  --glow-2: 0 0 76px rgba(104, 232, 147, 0.42);
  --on-accent: #093417;
}

html[data-accent="teal"] {
  --accent: #66ead9;
  --accent-strong: #8ef5e7;
  --accent-2: #54c5e8;
  --accent-soft: rgba(102, 234, 217, 0.16);
  --accent-line: rgba(102, 234, 217, 0.34);
  --glow-1: 0 0 34px rgba(102, 234, 217, 0.32);
  --glow-2: 0 0 76px rgba(102, 234, 217, 0.42);
  --on-accent: #09352f;
}

html[data-accent="cyan"] {
  --accent: #5cc2f5;
  --accent-strong: #87d5fd;
  --accent-2: #497cf3;
  --accent-soft: rgba(92, 194, 245, 0.16);
  --accent-line: rgba(92, 194, 245, 0.34);
  --glow-1: 0 0 34px rgba(92, 194, 245, 0.32);
  --glow-2: 0 0 76px rgba(92, 194, 245, 0.42);
  --on-accent: #052738;
}

html[data-accent="azure"] {
  --accent: #5988f8;
  --accent-strong: #85a9ff;
  --accent-2: #5145f7;
  --accent-soft: rgba(89, 136, 248, 0.16);
  --accent-line: rgba(89, 136, 248, 0.34);
  --glow-1: 0 0 34px rgba(89, 136, 248, 0.32);
  --glow-2: 0 0 76px rgba(89, 136, 248, 0.42);
  --on-accent: #04143a;
}

html[data-accent="blue"] {
  --accent: #755cf5;
  --accent-strong: #9b87fd;
  --accent-2: #a449f3;
  --accent-soft: rgba(117, 92, 245, 0.16);
  --accent-line: rgba(117, 92, 245, 0.34);
  --glow-1: 0 0 34px rgba(117, 92, 245, 0.32);
  --glow-2: 0 0 76px rgba(117, 92, 245, 0.42);
  --on-accent: #0d0538;
}

html[data-accent="violet"] {
  --accent: #cd5ff1;
  --accent-strong: #de89fa;
  --accent-2: #ef4ddd;
  --accent-soft: rgba(205, 95, 241, 0.16);
  --accent-line: rgba(205, 95, 241, 0.34);
  --glow-1: 0 0 34px rgba(205, 95, 241, 0.32);
  --glow-2: 0 0 76px rgba(205, 95, 241, 0.42);
  --on-accent: #2b0637;
}

html[data-accent="magenta"] {
  --accent: #f35ec1;
  --accent-strong: #fb88d5;
  --accent-2: #f14b7d;
  --accent-soft: rgba(243, 94, 193, 0.16);
  --accent-line: rgba(243, 94, 193, 0.34);
  --glow-1: 0 0 34px rgba(243, 94, 193, 0.32);
  --glow-2: 0 0 76px rgba(243, 94, 193, 0.42);
  --on-accent: #380627;
}

html[data-accent="platinum"] {
  --accent: #afaca1;
  --accent-strong: #ccc7b8;
  --accent-2: #a4a696;
  --accent-soft: rgba(175, 172, 161, 0.16);
  --accent-line: rgba(175, 172, 161, 0.34);
  --glow-1: 0 0 34px rgba(175, 172, 161, 0.32);
  --glow-2: 0 0 76px rgba(175, 172, 161, 0.42);
  --on-accent: #12141a;
}

html[data-theme="light"][data-accent="rose"] {
  --accent: #9c1128;
  --accent-strong: #a3142c;
  --accent-2: #9a2e13;
  --accent-soft: rgba(156, 17, 40, 0.16);
  --accent-line: rgba(156, 17, 40, 0.32);
  --glow-1: 0 0 16px rgba(156, 17, 40, 0.1);
  --glow-2: 0 0 32px rgba(156, 17, 40, 0.13);
  --on-accent: #ffffff;
}

html[data-theme="light"][data-accent="coral"] {
  --accent: #9c3b11;
  --accent-strong: #8e3712;
  --accent-2: #9a6d13;
  --accent-soft: rgba(156, 59, 17, 0.16);
  --accent-line: rgba(156, 59, 17, 0.32);
  --glow-1: 0 0 16px rgba(156, 59, 17, 0.1);
  --glow-2: 0 0 32px rgba(156, 59, 17, 0.13);
  --on-accent: #ffffff;
}

html[data-theme="light"][data-accent="amber"] {
  --accent: #7e620e;
  --accent-strong: #5d490c;
  --accent-2: #68740e;
  --accent-soft: rgba(126, 98, 14, 0.16);
  --accent-line: rgba(126, 98, 14, 0.32);
  --glow-1: 0 0 16px rgba(126, 98, 14, 0.1);
  --glow-2: 0 0 32px rgba(126, 98, 14, 0.13);
  --on-accent: #ffffff;
}

html[data-theme="light"][data-accent="lime"] {
  --accent: #47710d;
  --accent-strong: #33500a;
  --accent-2: #20670d;
  --accent-soft: rgba(71, 113, 13, 0.16);
  --accent-line: rgba(71, 113, 13, 0.32);
  --glow-1: 0 0 16px rgba(71, 113, 13, 0.1);
  --glow-2: 0 0 32px rgba(71, 113, 13, 0.13);
  --on-accent: #ffffff;
}

html[data-theme="light"][data-accent="green"] {
  --accent: #117432;
  --accent-strong: #0d5525;
  --accent-2: #106b50;
  --accent-soft: rgba(17, 116, 50, 0.16);
  --accent-line: rgba(17, 116, 50, 0.32);
  --glow-1: 0 0 16px rgba(17, 116, 50, 0.1);
  --glow-2: 0 0 32px rgba(17, 116, 50, 0.13);
  --on-accent: #ffffff;
}

html[data-theme="light"][data-accent="teal"] {
  --accent: #0f7164;
  --accent-strong: #0b5248;
  --accent-2: #0e5368;
  --accent-soft: rgba(15, 113, 100, 0.16);
  --accent-line: rgba(15, 113, 100, 0.32);
  --glow-1: 0 0 16px rgba(15, 113, 100, 0.1);
  --glow-2: 0 0 32px rgba(15, 113, 100, 0.13);
  --on-accent: #ffffff;
}

html[data-theme="light"][data-accent="cyan"] {
  --accent: #116c99;
  --accent-strong: #0f5578;
  --accent-2: #12378f;
  --accent-soft: rgba(17, 108, 153, 0.16);
  --accent-line: rgba(17, 108, 153, 0.32);
  --glow-1: 0 0 16px rgba(17, 108, 153, 0.1);
  --glow-2: 0 0 32px rgba(17, 108, 153, 0.13);
  --on-accent: #ffffff;
}

html[data-theme="light"][data-accent="azure"] {
  --accent: #113b9c;
  --accent-strong: #143fa3;
  --accent-2: #1c139a;
  --accent-soft: rgba(17, 59, 156, 0.16);
  --accent-line: rgba(17, 59, 156, 0.32);
  --glow-1: 0 0 16px rgba(17, 59, 156, 0.1);
  --glow-2: 0 0 32px rgba(17, 59, 156, 0.13);
  --on-accent: #ffffff;
}

html[data-theme="light"][data-accent="blue"] {
  --accent: #28119c;
  --accent-strong: #2c14a3;
  --accent-2: #5b139a;
  --accent-soft: rgba(40, 17, 156, 0.16);
  --accent-line: rgba(40, 17, 156, 0.32);
  --glow-1: 0 0 16px rgba(40, 17, 156, 0.1);
  --glow-2: 0 0 32px rgba(40, 17, 156, 0.13);
  --on-accent: #ffffff;
}

html[data-theme="light"][data-accent="violet"] {
  --accent: #79119c;
  --accent-strong: #8014a3;
  --accent-2: #9a138b;
  --accent-soft: rgba(121, 17, 156, 0.16);
  --accent-line: rgba(121, 17, 156, 0.32);
  --glow-1: 0 0 16px rgba(121, 17, 156, 0.1);
  --glow-2: 0 0 32px rgba(121, 17, 156, 0.13);
  --on-accent: #ffffff;
}

html[data-theme="light"][data-accent="magenta"] {
  --accent: #9c116e;
  --accent-strong: #9d136f;
  --accent-2: #9a133c;
  --accent-soft: rgba(156, 17, 110, 0.16);
  --accent-line: rgba(156, 17, 110, 0.32);
  --glow-1: 0 0 16px rgba(156, 17, 110, 0.1);
  --glow-2: 0 0 32px rgba(156, 17, 110, 0.13);
  --on-accent: #ffffff;
}

html[data-theme="light"][data-accent="platinum"] {
  --accent: #5e5a50;
  --accent-strong: #565349;
  --accent-2: #5c5e50;
  --accent-soft: rgba(94, 90, 80, 0.16);
  --accent-line: rgba(94, 90, 80, 0.32);
  --glow-1: 0 0 16px rgba(94, 90, 80, 0.1);
  --glow-2: 0 0 32px rgba(94, 90, 80, 0.13);
  --on-accent: #ffffff;
}

html[data-glow="0"] {
  --glow-1: none;
  --glow-2: none;
}

html[data-glow="0.5"] {
  --glow-1: 0 0 12px rgba(255, 255, 255, 0.045);
  --glow-2: 0 0 26px color-mix(in srgb, var(--accent) 8%, transparent);
}

html[data-glow="1"] {
  --glow-1: 0 0 34px color-mix(in srgb, var(--accent) 14%, transparent);
  --glow-2: 0 0 76px color-mix(in srgb, var(--accent) 17%, transparent);
}

html[data-glow="1.6"] {
  --glow-1: 0 0 28px color-mix(in srgb, var(--accent) 21%, transparent);
  --glow-2: 0 0 68px color-mix(in srgb, var(--accent) 27%, transparent);
}

html[data-theme="light"] .tech-ribbon i {
  filter: none;
}

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

html {
  overflow-x: clip;
  scroll-behavior: smooth;
  /* The page scrolls through the custom .scroll-rail; a native bar next to it reads as a
     duplicate control, so it is hidden on every engine. Inner panels keep their own. */
  scrollbar-width: none;
  -ms-overflow-style: none;
  background: var(--bg);
}

body {
  min-width: 320px;
  min-height: 100vh;
  margin: 0;
  overflow-x: clip;
  /* The solid page colour lives on <html>, not here. body carries only the accent
     glow on top of a transparent base: .ambient sits at negative
     z-index, and an opaque body background paints above them and hides the whole
     hex/trace/aurora stack. That is why the background was invisible. */
  background:
    radial-gradient(circle at 50% -20%, color-mix(in srgb, var(--accent) var(--body-glow), transparent), transparent 42%),
    transparent;
  color: var(--text);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body.menu-open {
  overflow: hidden;
}

button,
a {
  -webkit-tap-highlight-color: transparent;
}

button {
  font: inherit;
}

a {
  color: inherit;
  text-decoration: none;
}

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

/* The icon set is drawn on a 24x24 grid, so it is sized in whole pixels per slot and
   inherits the colour of whatever it sits in. Nothing here depends on a font being able
   to render a particular code point, which is the whole point of moving off Unicode. */
.icon {
  display: block;
  flex: 0 0 auto;
  overflow: visible;
}

.capability-icon .icon {
  width: 21px;
  height: 21px;
}

.lab-icon .icon {
  width: 19px;
  height: 19px;
}

.theme-icon .icon {
  width: 17px;
  height: 17px;
}

.section-share .icon {
  width: 13px;
  height: 13px;
}

/* The glow control reads as a brightness mark, and the marquee dividers stay small
   enough to disappear into the rhythm of the ribbon. */
.glow-button .icon,
.mobile-display-button .icon {
  width: 15px;
  height: 15px;
}

.ribbon-sep .icon {
  width: 9px;
  height: 9px;
  opacity: 0.55;
}

.dialog-copy .icon {
  width: 14px;
  height: 14px;
}

/* The small arrow that rides next to a label, inline with the text baseline. */
.icon-slot,
.icon-inline {
  display: inline-block;
  width: 12px;
  height: 12px;
  vertical-align: -1px;
  opacity: 0.72;
  transition: transform 0.2s var(--ease), opacity 0.2s ease;
}

a:hover > .icon-inline,
.button:hover .icon-slot {
  opacity: 1;
  transform: translate(1px, -1px);
}

::selection {
  background: color-mix(in srgb, var(--accent) 28%, transparent);
  color: var(--text);
}

/* See the note on the html rule above: the page-level native bar is hidden because the
   custom rail is the scroll control. */
::-webkit-scrollbar {
  display: none;
  width: 0;
  height: 0;
}

:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 72%, transparent);
  outline-offset: 3px;
}

.skip-link {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 1000;
  padding: 9px 14px;
  border-radius: var(--r-pill);
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 750;
  transform: translateY(-150%);
  transition: transform 0.2s var(--ease);
}

.skip-link:focus {
  transform: translateY(0);
}

.noscript {
  position: fixed;
  right: 16px;
  bottom: 16px;
  left: 16px;
  z-index: 1100;
  padding: 14px;
  border: 1px solid var(--accent-line);
  border-radius: 14px;
  background: var(--panel-solid);
  color: var(--text);
  text-align: center;
}

.section-shell,
.site-header {
  width: var(--page);
  margin-inline: auto;
}

.glass-card {
  position: relative;
  isolation: isolate;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: linear-gradient(145deg, color-mix(in srgb, var(--panel-solid) 90%, transparent), color-mix(in srgb, var(--panel) 72%, transparent));
  box-shadow: var(--shadow-1), inset 0 1px 0 var(--inner-top), inset 0 -1px 0 var(--inner-bottom);
/* 10px instead of 24px, and no saturate(): the blur radius drives the cost of every
     backdrop sample and the extra colour pass doubled it, for a difference you cannot
     see on an already translucent panel. There are more than a dozen of these panels on
     the page, so the saving is the difference between a saturated GPU and a smooth one. */
backdrop-filter: blur(10px);
}

.glass-card::before {
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
  background: var(--sheen);
  content: "";
  pointer-events: none;
}

.glass-card::after {
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(135deg, var(--edge-highlight), transparent 28%, transparent 68%, color-mix(in srgb, var(--accent) 18%, transparent));
  content: "";
  pointer-events: none;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
}

/* The light theme used to add its own near-white gradients on top of the panel, which is
   what produced the washed-out look. It now only adds a faint top light, so a light
   panel reads as paper, not as glare. */
html[data-theme="light"] .glass-card::before {
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0.16), transparent 46%);
}

html[data-theme="light"] .glass-card::after {
  background: linear-gradient(135deg, transparent, transparent 62%, color-mix(in srgb, var(--accent) 26%, transparent));
}

/* Light-theme glare fixes. Every glossy white highlight below is a dark-theme trick:
   on white panels it sums into blown-out spots, so the light theme gets the same shape
   at a fraction of the strength (or a dark edge instead of a white one). */
html[data-theme="light"] .button-primary {
  box-shadow: var(--glow-2), inset 0 1px 0 rgba(255, 255, 255, 0.18), inset 0 -1px 0 rgba(0, 0, 0, 0.12);
}

html[data-theme="light"] .header-contact {
  box-shadow: var(--glow-1), inset 0 1px 0 rgba(255, 255, 255, 0.18), inset 0 -1px 0 rgba(0, 0, 0, 0.12);
}

html[data-theme="light"] .header-contact:hover {
  box-shadow: var(--glow-2), inset 0 1px 0 rgba(255, 255, 255, 0.2);
}

html[data-theme="light"] .button::after,
html[data-theme="light"] .header-contact::after {
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.16), transparent);
}

html[data-theme="light"] .site-header::before {
  background: linear-gradient(90deg, transparent, rgba(20, 22, 28, 0.16), transparent);
}

html[data-theme="light"] .profile-avatar::after {
  background: linear-gradient(to top, rgba(0, 0, 0, 0.32), transparent 48%), linear-gradient(130deg, rgba(255, 255, 255, 0.08), transparent 34%);
}

/* Light-theme evenness. On paper every coloured wash reads as a stain and every diagonal
   panel gradient as a smudge, so the light theme flattens them: the vignette barely
   shades the edges, sections lose their tint spot, cards go flat paper, and the contact
   orb becomes a faint warm breath without its glow shadow. */
html[data-theme="light"] .ambient-vignette {
  background: radial-gradient(120% 90% at 50% 8%, transparent 55%, color-mix(in srgb, var(--bg-deep) 42%, transparent) 100%);
}

html[data-theme="light"] .glass-card {
  background: var(--panel-solid);
}

html[data-theme="light"] .capability-card {
  background:
    radial-gradient(420px circle at var(--spot-x, 50%) var(--spot-y, 28%), var(--accent-soft), transparent 44%),
    var(--panel-solid);
}

html[data-theme="light"] .contact-orb {
  background: radial-gradient(circle, color-mix(in srgb, var(--accent) 5%, transparent), transparent 70%);
  box-shadow: none;
}

/* Second colour highlight for the white theme: the violet orb glows a touch stronger
   than the whisper-quiet accent orbs, so the paper carries a faint violet light next
   to the warm accent without turning blotchy again. */
html[data-theme="light"] .ambient-orb-b {
  opacity: 0.08;
}

/* Russian typography. There were no data-lang rules at all, so Cyrillic was rendered with
   metrics tuned for Latin: the large negative tracking that makes a Latin display line
   look tight makes Cyrillic letters collide, and uppercase Cyrillic at 0.12em tracking
   shoves the small kicker labels onto a second line. */

html[data-lang="ru"] .section-kicker,
html[data-lang="ru"] .accent-menu-label,
html[data-lang="ru"] .about-link-copy > span,
html[data-lang="ru"] .card-tag,
html[data-lang="ru"] .contact-meta,
html[data-lang="ru"] .terminal-state{
  letter-spacing: 0.04em;
  text-transform: none;
}

html[data-lang="ru"] .section-kicker {
  font-weight: 700;
}

/* Negative tracking is tuned for Latin. At the same values on Cyrillic the round and
   angular letters (ш, щ, ж, б, д) start to touch, and the taller ascenders need a little
   more leading than the 1.02 line-height the display sizes use for English. */
html[data-lang="ru"] .hero h1 {
  letter-spacing: -0.03em;
  line-height: 1.04;
}

html[data-lang="ru"] .section-heading h2,
html[data-lang="ru"] .principle-intro strong,
html[data-lang="ru"] .contact-card h2,
html[data-lang="ru"] .about-copy h2,
html[data-lang="ru"] .capability-card h3,
html[data-lang="ru"] .lab-card h3,
html[data-lang="ru"] .stack-card h3,
html[data-lang="ru"] .process-item h3,
html[data-lang="ru"] .dialog-content h2{
  letter-spacing: -0.015em;
  line-height: 1.18;
}

html[data-lang="ru"] .section-heading > p,
html[data-lang="ru"] .about-copy p,
html[data-lang="ru"] .contact-card p,
html[data-lang="ru"] .capability-card p,
html[data-lang="ru"] .process-item p {
  line-height: 1.75;
}

/* Long Russian words ("развёртывания", "эксплуатации", "инфраструктура") need somewhere to
   break when a narrow column runs out of room, otherwise they push the layout wider than
   the page instead of wrapping inside it. */
html[data-lang="ru"] h1,
html[data-lang="ru"] h2,
html[data-lang="ru"] h3,
html[data-lang="ru"] p,
html[data-lang="ru"] strong,
html[data-lang="ru"] b {
  overflow-wrap: break-word;
  hyphens: auto;
}

/* Never hyphenate the short labels: breaking a badge or a button mid-word is worse than
   a slightly looser line. */

html[data-lang="ru"] .section-kicker,
html[data-lang="ru"] .about-link-copy > span,
html[data-lang="ru"] .accent-menu-label,
html[data-lang="ru"] .card-tag,
html[data-lang="ru"] .contact-meta,
html[data-lang="ru"] .terminal-state,
html[data-lang="ru"] .tech-ribbon-track span,
html[data-lang="ru"] .status-pill,
html[data-lang="ru"] #langToggle,
html[data-lang="ru"] .header-contact,
html[data-lang="ru"] .icon-button,
html[data-lang="ru"] .local-time{
  hyphens: none;
}

/* The background is the one place allowed to be decorative, so it layers five things: a
   slow aurora, a hexagonal mesh, circuit traces, drifting particles and film grain. The
   hex mesh and the traces are what make it specific to this site rather than a generic
   gradient. Every moving layer animates transform/opacity only, and the whole stack stops
   under prefers-reduced-motion or ?motion=off.
   Stacking: this stays at negative z-index *behind* the content and *above* the solid
   <html> background. It is only visible while <body> has no opaque background layer. */
.ambient {
  position: fixed;
  inset: 0;
  z-index: -3;
  overflow: hidden;
  pointer-events: none;
}

/* The orbs used to be solid circles behind filter: blur(110px), which meant two
   ~700px filter passes repainting on every frame of the drift animation. A radial
   gradient fades to transparent on its own, so it looks the same and costs no filter. */
.ambient-orb {
  position: absolute;
  width: 48vw;
  aspect-ratio: 1;
  border-radius: 50%;
  opacity: var(--orb-opacity);
  will-change: transform;
  animation: orbDrift 24s ease-in-out infinite alternate;
}

.ambient-orb-a {
  top: -24vw;
  left: -12vw;
  background: radial-gradient(circle, var(--accent) 0%, color-mix(in srgb, var(--accent) 55%, transparent) 42%, transparent 72%);
}

.ambient-orb-b {
  right: -18vw;
  top: 26vh;
  background: radial-gradient(circle, var(--violet) 0%, color-mix(in srgb, var(--violet) 55%, transparent) 42%, transparent 72%);
  animation-delay: -8s;
  animation-duration: 31s;
}

/* A third, slower orb low on the page. It is what stops the composition from reading as
   "two blobs in a corner" once the accent is a cool colour. */
.ambient-orb-c {
  bottom: -28vw;
  left: 26vw;
  width: 56vw;
  background: radial-gradient(circle, var(--info) 0%, color-mix(in srgb, var(--info) 46%, transparent) 44%, transparent 74%);
  animation-delay: -17s;
  animation-duration: 38s;
}

/* A slow conic sweep behind everything. It is one big element rotated on the
   compositor, so it costs a transform per frame and nothing else. The strength is a theme
   variable: the previous 16% mix on a 190vmax disc was effectively invisible, which is why
   the page read as flat, and the same value would drown the text on warm paper. */
.ambient-aurora {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 132vmax;
  aspect-ratio: 1;
  margin: -66vmax 0 0 -66vmax;
  /* The ray edges used to read as a fixed shell in the middle of the screen, most
     obvious where the content thins out at the top and bottom of the page. The fades
     are wider now and the wash is weaker, so it reads as atmosphere, not structure. */
  background: conic-gradient(
    from 0deg,
    transparent 0deg,
    color-mix(in srgb, var(--accent) var(--aurora-strength), transparent) 44deg,
    transparent 132deg,
    color-mix(in srgb, var(--violet) var(--aurora-strength), transparent) 180deg,
    transparent 266deg,
    color-mix(in srgb, var(--info) var(--aurora-strength), transparent) 310deg,
    transparent 14deg
  );
  opacity: 0.9;
  mask-image: radial-gradient(closest-side, black 12%, transparent 76%);
  -webkit-mask-image: radial-gradient(closest-side, black 12%, transparent 76%);
  will-change: transform;
  animation: auroraSpin 150s linear infinite;
}

/* A hexagonal mesh. This is the layer that gives the page a character of its own: a square
   grid is the default choice everywhere, hexagons read as blocks and structure. It is one
   tiling image translated on its own layer, so it costs a single transform per frame. */
.ambient-hex {
  position: absolute;
  inset: -30% -20%;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='97' viewBox='0 0 56 97'%3E%3Cpath d='M28 1 54 24.25 54 72.75 28 96 2 72.75 2 24.25Z' fill='none' stroke='%23ffffff' stroke-opacity='.45' stroke-width='1'/%3E%3C/svg%3E");
  background-size: 56px 97px;
  opacity: var(--hex-opacity);
  mask-image: radial-gradient(115% 75% at 50% 0%, black, transparent 78%);
  -webkit-mask-image: radial-gradient(115% 75% at 50% 0%, black, transparent 78%);
  will-change: transform;
  animation: hexDrift 44s linear infinite;
}

html[data-theme="light"] .ambient-hex {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='97' viewBox='0 0 56 97'%3E%3Cpath d='M28 1 54 24.25 54 72.75 28 96 2 72.75 2 24.25Z' fill='none' stroke='%23000000' stroke-opacity='.5' stroke-width='1'/%3E%3C/svg%3E");
}

/* Circuit traces with solder points: the part of the theme that fits an engineer who ships
   contracts. Same trick as the mesh, one tiling image that is only ever translated. */
.ambient-traces {
  position: absolute;
  inset: -20%;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='420' height='420' viewBox='0 0 420 420'%3E%3Cg fill='none' stroke='%23ffffff' stroke-opacity='.5' stroke-width='1'%3E%3Cpath d='M0 60h70a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10h90'/%3E%3Cpath d='M420 150h-80a10 10 0 0 0-10 10v30a10 10 0 0 1-10 10h-60'/%3E%3Cpath d='M120 420v-60a10 10 0 0 1 10-10h30a10 10 0 0 0 10-10v-40'/%3E%3Cpath d='M300 0v40a10 10 0 0 0 10 10h110'/%3E%3C/g%3E%3Cg fill='%23ffffff' fill-opacity='.75'%3E%3Ccircle cx='190' cy='120' r='3'/%3E%3Ccircle cx='220' cy='300' r='3'/%3E%3Ccircle cx='310' cy='50' r='3'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 420px 420px;
  opacity: var(--trace-opacity);
  mask-image: radial-gradient(100% 70% at 50% 100%, black, transparent 80%);
  -webkit-mask-image: radial-gradient(100% 70% at 50% 100%, black, transparent 80%);
  will-change: transform;
  animation: traceDrift 68s linear infinite;
}

html[data-theme="light"] .ambient-traces {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='420' height='420' viewBox='0 0 420 420'%3E%3Cg fill='none' stroke='%23000000' stroke-opacity='.45' stroke-width='1'%3E%3Cpath d='M0 60h70a10 10 0 0 1 10 10v40a10 10 0 0 0 10 10h90'/%3E%3Cpath d='M420 150h-80a10 10 0 0 0-10 10v30a10 10 0 0 1-10 10h-60'/%3E%3Cpath d='M120 420v-60a10 10 0 0 1 10-10h30a10 10 0 0 0 10-10v-40'/%3E%3Cpath d='M300 0v40a10 10 0 0 0 10 10h110'/%3E%3C/g%3E%3Cg fill='%23000000' fill-opacity='.7'%3E%3Ccircle cx='190' cy='120' r='3'/%3E%3Ccircle cx='220' cy='300' r='3'/%3E%3Ccircle cx='310' cy='50' r='3'/%3E%3C/g%3E%3C/svg%3E");
}

/* The mask lives on the wrapper so the grid pattern itself can be translated on its own
   layer: the wrapper is what fades the grid out, and translating the inner layer gives
   the page a sense of depth without repainting a single pixel of content. */
.ambient-grid {
  position: absolute;
  inset: 0;
  opacity: var(--grid-opacity);
  mask-image: linear-gradient(to bottom, black, transparent 78%);
  -webkit-mask-image: linear-gradient(to bottom, black, transparent 78%);
}

.ambient-grid-drift {
  position: absolute;
  inset: -25% 0;
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 54px 54px;
  will-change: transform;
  animation: gridDrift 26s linear infinite;
}

.ambient-vignette {
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 90% at 50% 8%, transparent 40%, color-mix(in srgb, var(--bg-deep) 78%, transparent) 100%);
}

.ambient-noise {
  position: absolute;
  inset: 0;
  opacity: var(--noise-opacity);
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.7'/%3E%3C/svg%3E");
}

#networkCanvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0.5;
}

.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 150;
  width: 100%;
  height: 2px;
  background: transparent;
}

.scroll-progress span {
  display: block;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, var(--accent-2), var(--accent), var(--violet));
  box-shadow: 0 0 16px var(--accent);
  transform: scaleX(var(--progress, 0));
  transform-origin: 0 50%;
}

/* Custom scroll rail on the right edge. It only mirrors the native scroll position and
   offers a drag handle: wheel, keyboard and touch scrolling keep working natively, and
   the rail fades out shortly after scrolling stops. */
.scroll-rail {
  position: fixed;
  top: 50%;
  right: 10px;
  z-index: 150;
  width: 22px;
  height: min(38vh, 320px);
  opacity: 0;
  pointer-events: none;
  transform: translateY(-50%);
  transition: opacity 0.35s ease;
}

.scroll-rail.is-active {
  opacity: 1;
  pointer-events: auto;
}

.scroll-rail::before {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 3px;
  border-radius: var(--r-pill);
  background: var(--line);
  content: "";
  transform: translateX(-50%);
}

.scroll-thumb {
  position: absolute;
  top: 0;
  left: 50%;
  width: 9px;
  min-height: 44px;
  border-radius: var(--r-pill);
  background: linear-gradient(var(--accent-2), var(--accent), var(--violet));
  box-shadow: 0 0 14px color-mix(in srgb, var(--accent) 45%, transparent);
  cursor: grab;
  transform: translateX(-50%);
  touch-action: none;
}

.scroll-thumb.is-dragging {
  cursor: grabbing;
}

.site-header {
  position: fixed;
  top: max(14px, env(safe-area-inset-top));
  left: 50%;
  z-index: 100;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  min-height: 58px;
  padding: 8px 10px 8px 12px;
  border: 1px solid transparent;
  border-radius: 22px;
  transform: translateX(-50%);
  transition: background 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
}

.site-header.scrolled {
  border-color: var(--line);
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  box-shadow: var(--shadow-1), inset 0 1px 0 var(--inner-top);
  /* A fixed bar with a wide backdrop blur repaints on every scrolled frame, so the
     radius is kept small and the filter is not part of the transition. */
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.site-header::before {
  position: absolute;
  inset: 1px 14px auto;
  z-index: -1;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.25), transparent);
  content: "";
  opacity: 0;
  transition: opacity 0.3s ease;
}

.site-header.scrolled::before {
  opacity: 1;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  width: max-content;
}

.brand-mark {
  position: relative;
  display: grid;
  width: 36px;
  height: 36px;
  place-items: center;
  overflow: hidden;
  border: 1px solid var(--accent-line);
  border-radius: 12px;
  background: linear-gradient(145deg, var(--accent-soft), var(--panel-2));
  box-shadow: var(--glow-1), inset 0 1px 0 rgba(255, 255, 255, 0.12);
}

/* Two colours, never three: the G takes the text colour so it survives both themes,
   and the X takes the live accent, so the mark follows the palette the visitor picks.
   Nothing here depends on a font rendering a particular glyph. */
.brand-mark svg {
  width: 23px;
  height: 23px;
  overflow: visible;
}

.brand-mark .mark-g {
  stroke: var(--text);
  stroke-width: 5;
}

/* No glow on the 23px mark: at that size the blur bleeds across the gap and welds the
   two letters together. The tile already carries the accent halo around the mark. */
.brand-mark .mark-x {
  stroke: var(--accent);
  stroke-width: 5;
}

.brand-mark::before {
  position: absolute;
  width: 26px;
  height: 1px;
  background: var(--accent);
  content: "";
  transform: rotate(-42deg);
}

.brand-copy {
  display: grid;
  line-height: 1.05;
}

.brand-copy strong {
  font-size: 13px;
  letter-spacing: -0.02em;
}

.brand-copy small {
  margin-top: 4px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 9px;
}

.desktop-nav {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--panel-2) 72%, transparent);
}

.desktop-nav a {
  position: relative;
  padding: 7px 12px;
  border-radius: var(--r-pill);
  color: var(--muted);
  font-size: 12px;
  font-weight: 650;
  white-space: nowrap;
  transition: color 0.2s ease, background 0.2s ease;
}

.desktop-nav a:hover,
.desktop-nav a.active {
  color: var(--text);
  background: var(--accent-soft);
}

.desktop-nav a.active::after {
  position: absolute;
  right: 12px;
  bottom: 4px;
  left: 12px;
  height: 1px;
  background: var(--accent);
  box-shadow: 0 0 10px var(--accent);
  content: "";
}

.header-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
}

.local-time {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-right: 3px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
}

.local-time i,
.contact-meta i {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--good);
  box-shadow: 0 0 10px var(--good);
  will-change: transform, opacity;
  animation: livePulse 1.8s ease-in-out infinite;
}

.icon-button,
.menu-button {
  display: inline-grid;
  width: 34px;
  height: 34px;
  padding: 0;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: color-mix(in srgb, var(--panel-2) 72%, transparent);
  color: var(--text-soft);
  cursor: pointer;
  transition: transform 0.18s var(--ease), color 0.18s ease, border-color 0.18s ease, background 0.18s ease;
}

.icon-button:hover,
.menu-button:hover {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-strong);
  box-shadow: var(--glow-1), inset 0 1px 0 rgba(255, 255, 255, 0.12);
  transform: translateY(-1px);
}

.glow-button span,
.motion-button span {
  font-size: 14px;
  line-height: 1;
}

.glow-button[aria-pressed="true"] {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-strong);
  box-shadow: var(--glow-1);
}

.glow-button[aria-pressed="false"],
.motion-button[aria-pressed="false"] {
  color: var(--faint);
  opacity: 0.72;
}

.motion-button[aria-pressed="true"] {
  color: var(--accent-strong);
}

/* The button is a fixed 34px square like its siblings, but its label is text, not an
   icon glyph: without an explicit size the two letters inherit the body size and look
   twice as heavy as the symbols next to them. */
#langToggle {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1;
}

/* The accent used to advance on every click, so reaching the colour you wanted took up to
   four presses and there was nothing on screen to show the choices. It is a real menu
   now: a button opens it, the swatches show every palette, and the active one is marked. */
.accent-picker {
  position: relative;
  display: inline-grid;
}

.accent-menu {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: 120;
  display: grid;
  gap: 9px;
  /* Three columns wide enough for the longest label ("Фиолетовый") next to its dot. With
     twelve palettes the menu is four rows tall, so it is capped and scrolls on a short
     viewport rather than growing off the bottom of the screen. */
  width: 300px;
  max-width: calc(100vw - 24px);
  max-height: min(320px, calc(100vh - 120px));
  overflow-y: auto;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  box-shadow: var(--shadow-2), var(--glow-1);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  animation: accentMenuIn 0.18s var(--ease);
  scrollbar-width: thin;
}

.accent-menu[hidden] {
  display: none;
}

.accent-menu-label {
  color: var(--faint);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.accent-swatches {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
}

.accent-swatch {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 7px;
  min-height: 34px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: 11px;
  background: color-mix(in srgb, var(--panel-2) 70%, transparent);
  color: var(--text-soft);
  cursor: pointer;
  font-size: 9px;
  font-weight: 700;
  text-align: left;
  transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease;
}

.accent-swatch:hover {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-strong);
}

.accent-swatch[aria-checked="true"] {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-strong);
  box-shadow: var(--glow-1);
}

.accent-swatch i {
  display: block;
  width: 15px;
  height: 15px;
  border: 2px solid currentcolor;
  border-radius: 50%;
  background: var(--swatch);
}

.accent-swatch b {
  overflow: hidden;
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.accent-button span {
  width: 13px;
  height: 13px;
  border: 2px solid var(--text);
  border-radius: 50%;
  background: conic-gradient(from 210deg, var(--accent), var(--violet), var(--info), var(--accent));
  box-shadow: 0 0 10px var(--accent-line);
}

.theme-icon {
  font-size: 16px;
}

.header-contact {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 34px;
  overflow: hidden;
  padding: 0 14px;
  border: 1px solid color-mix(in srgb, var(--accent) 72%, transparent);
  border-radius: var(--r-pill);
  background: linear-gradient(135deg, var(--accent-strong), var(--accent), var(--accent-2));
  color: var(--on-accent);
  font-size: 12px;
  font-weight: 800;
  box-shadow: var(--glow-1), inset 0 1px 0 rgba(255, 255, 255, 0.35), inset 0 -1px 0 rgba(0, 0, 0, 0.16);
  transition: transform 0.18s var(--ease), filter 0.18s ease, box-shadow 0.18s ease;
}

.header-contact .icon {
  width: 15px;
  height: 15px;
  margin-right: 7px;
}

.header-contact::after,
.button::after {
  position: absolute;
  inset: -40% auto -40% -80%;
  width: 48%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.34), transparent);
  content: "";
  pointer-events: none;
  transform: skewX(-16deg);
  transition: left 0.55s var(--ease);
}

.header-contact:hover::after,
.button:hover::after {
  left: 130%;
}

.header-contact:hover {
  filter: brightness(1.05);
  box-shadow: var(--glow-2), inset 0 1px 0 rgba(255, 255, 255, 0.42);
  transform: translateY(-1px);
}

.menu-button {
  display: none;
}

.menu-button span {
  position: absolute;
  width: 14px;
  height: 1px;
  background: currentColor;
  transition: transform 0.22s var(--ease);
}

.menu-button span:first-child {
  transform: translateY(-3px);
}

.menu-button span:last-child {
  transform: translateY(3px);
}

.menu-button[aria-expanded="true"] span:first-child {
  transform: rotate(45deg);
}

.menu-button[aria-expanded="true"] span:last-child {
  transform: rotate(-45deg);
}

.mobile-menu {
  position: fixed;
  top: calc(82px + env(safe-area-inset-top));
  right: 20px;
  left: 20px;
  z-index: 90;
  display: none;
  /* Nine rows of controls taller than a landscape phone viewport, so the panel scrolls
     instead of pushing its last links below the screen where they cannot be reached. */
  max-height: calc(100svh - 110px);
  overflow-y: auto;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 22px;
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  box-shadow: var(--shadow-2);
  backdrop-filter: blur(9px);
  scrollbar-width: thin;
}

.mobile-display-controls {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
  margin-bottom: 6px;
}

.mobile-display-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 42px;
  padding: 0 11px;
  border: 1px solid var(--line);
  border-radius: 13px;
  background: color-mix(in srgb, var(--panel-2) 72%, transparent);
  color: var(--text-soft);
  cursor: pointer;
  font-size: 10px;
}

.mobile-display-button > span {
  color: var(--accent);
  font-size: 13px;
}

.mobile-display-button[aria-pressed="false"] {
  color: var(--faint);
}

.mobile-display-button[aria-pressed="true"] {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  box-shadow: var(--glow-1);
}

.mobile-menu a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 46px;
  padding: 0 12px;
  border-radius: 13px;
  color: var(--text-soft);
  font-weight: 650;
}

.mobile-menu a:hover {
  background: var(--accent-soft);
  color: var(--accent-strong);
}

.hero {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(430px, 0.8fr);
  align-items: center;
  gap: clamp(46px, 7vw, 96px);
  min-height: 100svh;
  padding-top: 132px;
  padding-bottom: 72px;
}

.status-pill,
.section-kicker,
.filter-button,
.project-status,
.card-tag,
.contact-meta,
.terminal-state{
  display: inline-flex;
  align-items: center;
}

.status-pill {
  gap: 8px;
  width: max-content;
  max-width: 100%;
  padding: 7px 11px;
  border: 1px solid var(--accent-line);
  border-radius: var(--r-pill);
  background: linear-gradient(135deg, var(--accent-soft), color-mix(in srgb, var(--panel-2) 65%, transparent));
  color: var(--text-soft);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), var(--glow-1);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  backdrop-filter: blur(9px);
}

.status-pill i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--good);
  box-shadow: 0 0 12px var(--good);
  will-change: transform, opacity;
  animation: livePulse 1.8s ease-in-out infinite;
}

.hero-kicker {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 24px 0 12px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
}

.hero-kicker b {
  color: var(--accent);
}

.hero h1 {
  max-width: 760px;
  margin: 0;
  --h1-fill: linear-gradient(180deg, var(--text) 4%, color-mix(in srgb, var(--text) 84%, var(--accent) 16%) 82%, color-mix(in srgb, var(--text) 68%, var(--accent) 32%) 100%);
  background: var(--h1-fill);
  background-clip: text;
  color: transparent;
  font-size: clamp(48px, 6.2vw, 86px);
  font-weight: 850;
  line-height: 0.96;
  letter-spacing: -0.068em;
  text-wrap: balance;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.hero h1::after {
  display: none;
}

.hero-lead {
  max-width: 64ch;
  margin: 28px 0 0;
  color: var(--text-soft);
  font-size: clamp(16px, 1.5vw, 19px);
  line-height: 1.7;
}

.hero-actions,
.contact-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 32px;
}

.button {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  min-height: 46px;
  overflow: hidden;
  padding: 0 18px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-size: 13px;
  font-weight: 750;
  isolation: isolate;
  transition: transform 0.2s var(--ease), box-shadow 0.2s ease, border-color 0.2s ease, background 0.2s ease, filter 0.2s ease;
}

.button i {
  font-style: normal;
  transition: transform 0.2s var(--ease);
}

.button:hover {
  transform: translateY(-2px);
}

.button:active {
  transform: translateY(0) scale(0.98);
}

.button:hover i {
  transform: translate(2px, -2px);
}

.button-primary {
  border-color: color-mix(in srgb, var(--accent) 58%, transparent);
  background: linear-gradient(135deg, var(--accent-strong), var(--accent), var(--accent-2));
  color: var(--on-accent);
  box-shadow: var(--glow-2), inset 0 1px 0 rgba(255, 255, 255, 0.42), inset 0 -1px 0 rgba(0, 0, 0, 0.18);
}

.button-ghost {
  border-color: var(--line);
  background: color-mix(in srgb, var(--panel-2) 65%, transparent);
  color: var(--text-soft);
}

.button-ghost:hover {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-strong);
  box-shadow: var(--glow-1);
}

.hero-facts {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  max-width: 760px;
  margin-top: 42px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--line);
}

.hero-facts > div {
  display: grid;
  min-width: 0;
  padding: 14px 15px;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
}

.hero-facts span {
  color: var(--faint);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.hero-facts strong {
  margin-top: 3px;
  color: var(--text-soft);
  font-size: 12px;
  /* Four cells across the desktop strip leave about 160px each, which is narrower than
     "JavaScript + Solidity" on one line. Wrapping keeps the whole value visible; the
     single-line ellipsis used to cut it to "JavaScript + Solidi". */
  line-height: 1.35;
  text-wrap: balance;
}

/* The highlight used to travel by animating background-position on a masked 1px ring,
   which repainted the whole card every frame and forced its backdrop-filter to re-run.
   Pulsing the ring's opacity is composited instead. */
.profile-card::after,
.contact-card::after {
  animation: edgeGlow 6s ease-in-out infinite;
  will-change: opacity;
}
.terminal-bar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  min-height: 48px;
  padding: 0 14px;
  border-bottom: 1px solid var(--line);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10px;
}

.console-dots {
  display: flex;
  gap: 6px;
}

.console-dots i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--line-strong);
}

.console-dots i:first-child {
  background: var(--bad);
}

.console-dots i:nth-child(2) {
  background: var(--accent);
}

.console-dots i:last-child {
  background: var(--good);
}

.tech-ribbon {
  position: relative;
  width: calc(100% - 36px);
  margin-inline: auto;
  overflow: hidden;
  border-block: 1px solid var(--line);
  background: color-mix(in srgb, var(--panel) 55%, transparent);
  transform: rotate(-1.2deg) scale(1.02);
}

.tech-ribbon-track {
  display: flex;
  align-items: center;
  width: max-content;
  min-height: 54px;
  animation: ribbonMove 28s linear infinite;
}

.tech-ribbon span {
  padding: 0 24px;
  color: var(--text-soft);
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}

.tech-ribbon i {
  color: var(--accent);
  font-style: normal;
  filter: drop-shadow(0 0 7px color-mix(in srgb, var(--accent) 36%, transparent));
  transition: filter 0.25s ease, transform 0.25s var(--ease);
}

.tech-ribbon:hover i {
  filter: drop-shadow(0 0 12px color-mix(in srgb, var(--accent) 58%, transparent));
  transform: translateY(-1px);
}

.section-shell:not(.hero) {
  position: relative;
  padding-top: 150px;
  scroll-margin-top: 86px;
}

.section-heading {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(280px, 0.55fr);
  align-items: end;
  gap: 60px;
  margin-bottom: 48px;
}

/* Copies a deep link to the section. This is the replacement for the fragment in the
   address bar: the browser never shows one, so the shareable part has to be an explicit
   action. It stays quiet until the section is hovered or the button itself is focused, and
   it is never hidden from the accessibility tree, only dimmed. */
.section-share {
  position: relative;
  display: grid;
  width: 22px;
  height: 22px;
  padding: 0;
  place-items: center;
  border: 1px solid transparent;
  border-radius: 7px;
  background: transparent;
  color: var(--faint);
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
  opacity: 0.45;
  transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease, opacity 0.18s ease;
}

/* The glyph stays 22px, but the target grows to 28px: WCAG 2.2 asks for 24px minimum and
   these sit in a row of nine- and ten-pixel kickers, where a touch needs the extra room.
   The overlay is invisible and moves nothing. */
.section-share::after {
  position: absolute;
  inset: -3px;
  content: "";
}

.section-heading:hover .section-share,
.contact-card:hover .section-share,
.section-share:focus-visible {
  opacity: 1;
}

.section-share:hover,
.section-share:focus-visible {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-strong);
}

.section-share:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.section-kicker {
  gap: 10px;
  margin: 0 0 14px;
  color: var(--accent-strong);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.section-kicker span:first-child {
  position: relative;
  display: grid;
  width: 26px;
  height: 22px;
  place-items: center;
  border: 1px solid var(--accent-line);
  border-radius: 7px;
  background: var(--accent-soft);
  font-family: var(--mono);
  font-size: 9px;
}

.section-heading h2,
.about-copy h2,
.contact-card h2 {
  max-width: 820px;
  margin: 0;
  background: linear-gradient(180deg, var(--text), color-mix(in srgb, var(--text) 78%, var(--accent) 22%));
  background-clip: text;
  color: transparent;
  font-size: clamp(36px, 4.8vw, 66px);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  font-weight: 820;
  line-height: 1.02;
  letter-spacing: -0.055em;
  text-wrap: balance;
}

.section-heading > p {
  margin: 0 0 6px;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.75;
}

.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-bottom: 22px;
}

.filter-button {
  justify-content: center;
  gap: 7px;
  min-height: 34px;
  padding: 0 13px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--panel-2) 56%, transparent);
  color: var(--muted);
  cursor: pointer;
  font-size: 11px;
  font-weight: 700;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease, transform 0.2s var(--ease);
}

.filter-button span {
  color: var(--faint);
  font-family: var(--mono);
  font-size: 9px;
}

.filter-button:hover {
  border-color: var(--accent-line);
  color: var(--text);
  transform: translateY(-1px);
}

.filter-button.active {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-strong);
  box-shadow: var(--glow-1);
}

.filter-button.active span {
  color: var(--accent);
}

/* Cases. A card per project with its motion piece on top. The flagship spans the grid with
   the video beside the copy; the rest sit two to a row. The whole card is one control: the
   title is the only focusable element (a real button) and its ::after is stretched over the
   card, so a click on the video or the text opens the same case study without nesting one
   interactive element inside another. The play toggle sits above it. */
.case-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(16px, 2vw, 28px);
}

.case {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background:
    radial-gradient(520px circle at var(--spot-x, 50%) var(--spot-y, 0%), var(--accent-soft), transparent 46%),
    linear-gradient(145deg, color-mix(in srgb, var(--panel-solid) 90%, transparent), color-mix(in srgb, var(--panel) 74%, transparent));
  box-shadow: inset 0 1px 0 var(--inner-top), inset 0 -1px 0 var(--inner-bottom);
  transition: opacity 0.7s var(--ease), transform 0.35s var(--ease), border-color 0.3s ease, box-shadow 0.35s ease;
}

.case[data-featured="true"] {
  grid-column: 1 / -1;
  flex-direction: row;
  align-items: stretch;
}

.case-media {
  min-width: 0;
  padding: 12px 12px 0;
}

.case[data-featured="true"] .case-media {
  flex: 1.4 1 0;
  padding: 14px;
}

.case-frame {
  position: relative;
  z-index: 2;
  isolation: isolate;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: calc(var(--r-lg) - 10px);
  background: #06070d;
  pointer-events: none;
  transition: border-color 0.3s ease, box-shadow 0.4s ease;
}

/* The picture is dark in both themes on purpose: it is a screen, not a panel. */
.case-frame::after {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
  content: "";
  pointer-events: none;
}

.case-video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.case-toggle {
  position: absolute;
  right: 12px;
  bottom: 34px;
  z-index: 3;
  display: grid;
  width: 34px;
  height: 34px;
  padding: 0;
  place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 50%;
  background: rgba(8, 9, 14, 0.62);
  color: #fff;
  cursor: pointer;
  pointer-events: auto;
  backdrop-filter: blur(10px);
  transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s var(--ease);
}

/* The disc is 34px; the target is 46px, which is what a thumb needs. */
.case-toggle::after {
  position: absolute;
  inset: -6px;
  content: "";
}

.case-toggle .icon {
  width: 14px;
  height: 14px;
}

.case-toggle:hover,
.case-toggle:focus-visible {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 24%, rgba(8, 9, 14, 0.7));
  transform: scale(1.06);
}

.case-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.case-copy {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  min-width: 0;
  padding: 20px 24px 22px;
}

.case[data-featured="true"] .case-copy {
  justify-content: center;
  padding: 34px 38px 32px 26px;
}

.case-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.case-mark {
  box-shadow: 0 0 18px color-mix(in srgb, var(--accent) calc(var(--g) * 22%), transparent);
  flex: none;
  width: 34px;
  height: 34px;
  padding: 7px;
  border: 1px solid var(--accent-line);
  border-radius: 11px;
  background: var(--accent-soft);
  color: var(--accent-strong);
}

.case-meta .project-status {
  color: var(--accent-strong);
}

.case-year {
  color: var(--faint);
}

.case-year::before {
  margin-right: 10px;
  color: var(--line);
  content: "/";
}

.case-num {
  margin-left: auto;
  color: transparent;
  font-size: 34px;
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  letter-spacing: -0.05em;
  line-height: 1;
  -webkit-text-stroke: 1px var(--accent-line);
  user-select: none;
  transition: -webkit-text-stroke-color 0.3s ease;
}

.case:hover .case-num {
  -webkit-text-stroke-color: var(--accent);
}

.case-copy h3 {
  margin: 18px 0 0;
  font-size: clamp(24px, 2.2vw, 32px);
  line-height: 1.05;
  letter-spacing: -0.045em;
}

.case[data-featured="true"] .case-copy h3 {
  font-size: clamp(30px, 3.2vw, 46px);
}

.case-open {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
  font: inherit;
  letter-spacing: inherit;
  text-align: left;
  transition: color 0.2s ease;
}

.case-open::after {
  position: absolute;
  inset: 0;
  z-index: 1;
  content: "";
}

.case-open:focus-visible {
  outline: none;
}

.case-open:focus-visible::after {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-radius: var(--r-lg);
}

.case:hover .case-open {
  color: var(--accent-strong);
}

.case-desc {
  max-width: 62ch;
  margin: 12px 0 0;
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.7;
  text-wrap: pretty;
}

/* The flagship card has room for what is inside, so it lists it. */
.case-feats {
  display: grid;
  gap: 9px;
  margin: 18px 0 0;
  padding: 0;
  color: var(--text-soft);
  font-size: 13.5px;
  line-height: 1.5;
  list-style: none;
}

.case-feats li {
  position: relative;
  padding-left: 20px;
}

.case-feats li::before {
  position: absolute;
  top: 0.55em;
  left: 0;
  width: 7px;
  height: 7px;
  border-radius: 2px;
  background: var(--accent);
  box-shadow: 0 0 10px var(--accent);
  content: "";
  transform: rotate(45deg);
}

.case-proof {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin: 18px 0 22px;
  padding: 0;
  list-style: none;
}

.case-proof li {
  padding: 5px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--panel-2) 60%, transparent);
  color: var(--text-soft);
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 600;
}

.case-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: auto;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

.case-stack {
  min-width: 0;
  overflow: hidden;
  color: var(--faint);
  font-family: var(--mono);
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.case-cta {
  flex: none;
  color: var(--accent-strong);
  font-size: 12px;
  font-weight: 750;
  white-space: nowrap;
}

.case-cta i {
  display: inline-block;
  font-style: normal;
  transition: transform 0.25s var(--ease);
}

@media (hover: hover) {
  .case:hover {
    border-color: var(--accent-line);
    box-shadow: var(--shadow-1), 0 18px 48px color-mix(in srgb, var(--accent) 12%, transparent), var(--glow-1);
    transform: translateY(-6px);
  }

  .case:hover .case-cta i {
    transform: translateX(4px);
  }
}

html[data-lang="ru"] .case-meta,
html[data-lang="ru"] .case-copy h3 {
  hyphens: none;
}

html[data-lang="ru"] .case-copy h3 {
  letter-spacing: -0.03em;
}

@media (max-width: 960px) {
  .case-list {
    grid-template-columns: minmax(0, 1fr);
  }

  .case[data-featured="true"] {
    flex-direction: column;
  }

  .case[data-featured="true"] .case-media {
    padding: 12px 12px 0;
  }

  .case[data-featured="true"] .case-copy {
    justify-content: flex-start;
    padding: 20px 24px 22px;
  }

  .case[data-featured="true"] .case-copy h3 {
    font-size: clamp(26px, 5vw, 34px);
  }
}

@media (max-width: 768px) {
  .case {
    border-radius: 22px;
  }

  .case-frame {
    border-radius: 14px;
  }

  .case-copy,
  .case[data-featured="true"] .case-copy {
    padding: 18px 18px 20px;
  }

  .case-num {
    font-size: 28px;
  }

  .case-toggle {
    right: 10px;
    bottom: 30px;
  }
}

.capability-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.capability-card {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 315px;
  overflow: hidden;
  padding: 22px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background:
    radial-gradient(420px circle at var(--spot-x, 50%) var(--spot-y, 28%), var(--accent-soft), transparent 44%),
    linear-gradient(145deg, color-mix(in srgb, var(--panel-solid) 88%, transparent), color-mix(in srgb, var(--panel) 74%, transparent));
  box-shadow: inset 0 1px 0 var(--inner-top), inset 0 -1px 0 var(--inner-bottom);
  transition: transform 0.3s var(--ease), border-color 0.3s ease, box-shadow 0.3s ease;
}

.capability-card::before {
  position: absolute;
  right: -50px;
  bottom: -80px;
  width: 190px;
  height: 190px;
  border: 1px solid var(--accent-line);
  border-radius: 50%;
  background: radial-gradient(circle, var(--accent-soft), transparent 66%);
  content: "";
  opacity: 0.45;
  transition: transform 0.5s var(--ease), opacity 0.3s ease;
}

.capability-card:hover {
  border-color: var(--accent-line);
  box-shadow: var(--shadow-1), 0 12px 38px color-mix(in srgb, var(--accent) 10%, transparent), var(--glow-1);
  transform: translateY(-4px);
}

.capability-card:hover::before {
  opacity: 0.8;
  transform: scale(1.18);
}

.capability-top {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.capability-icon {
  display: grid;
  width: 42px;
  height: 42px;
  place-items: center;
  border: 1px solid var(--accent-line);
  border-radius: 14px;
  background: var(--accent-soft);
  color: var(--accent-strong);
  font-size: 18px;
  box-shadow: var(--glow-1);
}

.capability-number {
  color: var(--faint);
  font-family: var(--mono);
  font-size: 10px;
}

.capability-card h3 {
  position: relative;
  z-index: 1;
  max-width: 250px;
  margin: 38px 0 12px;
  font-size: 21px;
  line-height: 1.15;
  letter-spacing: -0.035em;
  /* Titles run to one or two lines depending on the language, so without a reserved
     block the description of a two-line card sat a full line lower than its neighbours
     and the row stopped reading as one line. 2lh tracks whatever line-height applies,
     including the Russian override, so the baseline stays shared. Only needed while the
     cards sit side by side, so the phone drops it again. */
  min-height: 2lh;
}

.capability-card p {
  position: relative;
  z-index: 1;
  max-width: 34ch;
  margin: 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.7;
}

.card-tags {
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: auto;
}

.card-tag {
  padding: 4px 7px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  color: var(--faint);
  font-family: var(--mono);
  font-size: 8px;
}

.stack-layout {
  display: grid;
  grid-template-columns: minmax(340px, 0.72fr) minmax(0, 1.28fr);
  align-items: start;
  gap: 14px;
}

.stack-terminal {
  position: sticky;
  top: 104px;
  overflow: hidden;
}

.terminal-state {
  justify-self: end;
  gap: 6px;
  color: var(--good);
}

.terminal-content {
  display: grid;
  gap: 14px;
  padding: 20px;
}

.terminal-profile {
  display: flex;
  align-items: center;
  gap: 12px;
}

.terminal-avatar {
  position: relative;
  display: grid;
  width: 52px;
  height: 52px;
  place-items: center;
  overflow: hidden;
  border: 1px solid var(--accent-line);
  border-radius: 16px;
  background: var(--accent-soft);
  color: var(--accent-strong);
  font-size: 21px;
  font-weight: 900;
  box-shadow: var(--glow-1);
}

.terminal-avatar img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Tuned to this portrait: the figure sits in the right third of the frame, so a centred
     crop framed an empty stretch of sea and lost the subject. These values put the hood
     and shoulders in the middle of the box. */
  object-position: 74% 56%;
  filter: saturate(0.82) contrast(1.05) brightness(0.9);
}

.terminal-profile strong,
.terminal-profile span {
  display: block;
}

.terminal-profile strong {
  font-size: 14px;
}

.terminal-profile span {
  margin-top: 2px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 9px;
}

.terminal-focus-list {
  display: grid;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: color-mix(in srgb, var(--panel-2) 38%, transparent);
}

.terminal-focus-list .profile-bar-row:first-child {
  border-top: 0;
}

.terminal-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

.terminal-tags span {
  padding: 5px 7px;
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--text-soft);
  font-family: var(--mono);
  font-size: 8px;
}

.terminal-footer {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 14px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 9px;
}

.terminal-footer span,
.terminal-footer i {
  color: var(--accent);
  font-style: normal;
}

.terminal-footer i {
  animation: cursorBlink 1s steps(1) infinite;
}

.stack-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.stack-card {
  position: relative;
  min-height: 184px;
  padding: 18px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--panel) 75%, transparent);
  transition: transform 0.25s var(--ease), border-color 0.25s ease, background 0.25s ease;
}

.stack-card::before {
  position: absolute;
  top: 0;
  left: 18px;
  width: 34px;
  height: 2px;
  border-radius: var(--r-pill);
  background: var(--stack-color, var(--accent));
  box-shadow: 0 0 12px var(--stack-color, var(--accent));
  content: "";
}

.stack-card[data-stack-color="amber"] { --stack-color: #ffcc21; }
.stack-card[data-stack-color="cyan"] { --stack-color: #22d3ee; }
.stack-card[data-stack-color="violet"] { --stack-color: #a78bfa; }
.stack-card[data-stack-color="green"] { --stack-color: #34d399; }
.stack-card[data-stack-color="blue"] { --stack-color: #60a5fa; }
.stack-card[data-stack-color="red"] { --stack-color: #fb7185; }

.stack-card:hover {
  border-color: color-mix(in srgb, var(--stack-color, var(--accent)) 35%, var(--line));
  background:
    radial-gradient(340px circle at var(--spot-x, 50%) var(--spot-y, 0%), color-mix(in srgb, var(--stack-color, var(--accent)) 16%, transparent), transparent 56%),
    color-mix(in srgb, var(--panel-solid) 80%, transparent);
  box-shadow: 0 12px 36px color-mix(in srgb, var(--stack-color, var(--accent)) 9%, transparent), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  transform: translateY(-3px);
}

.stack-card h3 {
  margin: 16px 0 14px;
  font-size: 14px;
  letter-spacing: -0.02em;
}

.stack-skills {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

.stack-skills span {
  padding: 5px 7px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: color-mix(in srgb, var(--panel-2) 48%, transparent);
  color: var(--text-soft);
  font-size: 9px;
}

.about-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(420px, 1.1fr);
  gap: clamp(50px, 8vw, 110px);
}

.about-copy > p:not(.section-kicker) {
  max-width: 62ch;
  margin: 24px 0 0;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.85;
}

.about-copy > p:nth-of-type(3) {
  color: var(--text-soft);
}

.about-links {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin-top: 30px;
}

.about-links a {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 2px 12px;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: color-mix(in srgb, var(--panel) 65%, transparent);
  transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s var(--ease);
}

.about-links a:hover {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  transform: translateY(-2px);
}

.about-link-icon {
  display: grid;
  grid-row: 1 / 3;
  width: 38px;
  height: 38px;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 11px;
  background: color-mix(in srgb, var(--panel-2) 78%, transparent);
  color: var(--text-soft);
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}

.about-links a:hover .about-link-icon {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-strong);
  box-shadow: var(--glow-1);
}

.about-link-icon svg {
  width: 19px;
  height: 19px;
  fill: currentcolor;
}

.about-link-copy {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.about-link-copy > span {
  color: var(--faint);
  font-size: 9px;
  text-transform: uppercase;
}

.about-link-copy strong {
  overflow: hidden;
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.about-links i {
  grid-row: 1 / 3;
  align-self: center;
  color: var(--accent);
  font-style: normal;
}

.process-list {
  position: relative;
  display: grid;
  gap: 8px;
  padding-left: 32px;
}

.process-item {
  position: relative;
  display: grid;
  grid-template-columns: 46px minmax(0, 0.65fr) minmax(0, 1.35fr);
  align-items: start;
  gap: 16px;
  min-height: 108px;
  padding: 18px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--panel) 70%, transparent);
  transition: border-color 0.25s ease, background 0.25s ease, transform 0.25s var(--ease);
}

.process-item:hover {
  border-color: var(--accent-line);
  background:
    radial-gradient(340px circle at var(--spot-x, 50%) var(--spot-y, 0%), var(--accent-soft), transparent 56%),
    color-mix(in srgb, var(--panel-solid) 80%, transparent);
  box-shadow: 0 12px 36px color-mix(in srgb, var(--accent) 8%, transparent), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  transform: translateX(4px);
}

.process-item > span {
  color: var(--accent);
  font-family: var(--mono);
  font-size: 10px;
}

.process-item h3 {
  margin: 0;
  font-size: 15px;
}

.process-item p {
  margin: 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.7;
}

.principles {
  display: grid;
  grid-template-columns: minmax(250px, 0.55fr) minmax(0, 1.45fr);
  gap: 32px;
  margin-top: 64px;
  padding: 28px;
}

.principle-intro {
  display: grid;
  align-content: start;
  gap: 10px;
}

.principle-intro span {
  color: var(--accent-strong);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.principle-intro strong {
  max-width: 24ch;
  font-size: 20px;
  line-height: 1.35;
  letter-spacing: -0.03em;
}

.principle-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}

.principle-grid article {
  min-height: 145px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: color-mix(in srgb, var(--panel-2) 54%, transparent);
}

.principle-grid span {
  color: var(--accent);
  font-family: var(--mono);
  font-size: 9px;
}

.principle-grid h3 {
  margin: 22px 0 7px;
  font-size: 13px;
}

.principle-grid p {
  margin: 0;
  color: var(--muted);
  font-size: 11px;
  line-height: 1.6;
}

.lab-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}

.lab-card {
  position: relative;
  min-height: 220px;
  padding: 20px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--panel) 68%, transparent);
  transition: transform 0.3s var(--ease), border-color 0.3s ease;
}

.lab-card:hover {
  border-color: var(--accent-line);
  background:
    radial-gradient(340px circle at var(--spot-x, 50%) var(--spot-y, 0%), var(--accent-soft), transparent 56%),
    color-mix(in srgb, var(--panel-solid) 80%, transparent);
  box-shadow: 0 12px 36px color-mix(in srgb, var(--accent) 9%, transparent), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  transform: translateY(-4px) rotateX(2deg);
}

.lab-icon {
  display: grid;
  width: 38px;
  height: 38px;
  place-items: center;
  border: 1px solid var(--accent-line);
  border-radius: 12px;
  background: var(--accent-soft);
  color: var(--accent-strong);
}

.lab-card h3 {
  margin: 36px 0 9px;
  font-size: 17px;
  letter-spacing: -0.025em;
  /* Same reason as .capability-card h3: a two-line Russian title used to push its
     description a whole line below the two one-line cards beside it. */
  min-height: 2lh;
}

.lab-card p {
  margin: 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.7;
}

.contact-section {
  padding-bottom: 0;
}

.contact-card {
  position: relative;
  /* Not clipped: the orb and the rings fade out on their own instead of being cut off
     by the card's edge, so the glow is never boxed in. */
  overflow: visible;
  padding: clamp(34px, 6vw, 76px);
  text-align: center;
}

.contact-grid {
  position: absolute;
  inset: 0;
  opacity: 0.35;
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 42px 42px;
  mask-image: radial-gradient(circle at center, black, transparent 76%);
  -webkit-mask-image: radial-gradient(circle at center, black, transparent 76%);
}

.contact-orb {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 540px;
  height: 540px;
  border: 0;
  border-radius: 50%;
  background: radial-gradient(circle, var(--accent-soft), color-mix(in srgb, var(--accent) 5%, transparent) 38%, transparent 72%);
  box-shadow: var(--glow-2);
  transform: translate(-50%, -50%);
  animation: contactPulse 7s ease-in-out infinite;
}

.contact-card > *:not(.contact-grid, .contact-orb, .contact-rings) {
  position: relative;
  z-index: 2;
}

.contact-card .section-kicker {
  justify-content: center;
}

.contact-card h2 {
  max-width: 900px;
  margin-inline: auto;
}

.contact-card > p:not(.section-kicker) {
  max-width: 70ch;
  margin: 24px auto 0;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.8;
}

.contact-actions {
  justify-content: center;
}

.contact-meta {
  justify-content: center;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 30px;
  color: var(--faint);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.site-footer {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 26px;
  padding-top: 24px;
  padding-bottom: 32px;
  border-top: 1px solid var(--line);
}

.footer-brand {
  display: flex;
  align-items: center;
  gap: 11px;
}

.footer-brand > div {
  display: grid;
  gap: 2px;
}

.footer-brand strong {
  font-size: 12px;
}

.footer-brand span:not(.brand-mark) {
  color: var(--faint);
  font-size: 10px;
}

.footer-links {
  display: flex;
  align-items: center;
  gap: 8px;
}

.footer-links a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 38px;
  padding: 0 16px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  color: var(--muted);
  font-size: 12px;
  font-weight: 650;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease, box-shadow 0.25s ease, transform 0.2s var(--ease);
}

.footer-links a:hover {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-strong);
  box-shadow: var(--glow-1);
  transform: translateY(-2px);
}

.site-footer > p {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--faint);
  font-family: var(--mono);
  font-size: 9px;
}

.project-dialog {
  width: min(920px, calc(100vw - 30px));
  max-height: min(820px, calc(100svh - 30px));
  padding: 0;
  overflow: visible;
  border: 0;
  background: transparent;
  color: var(--text);
}

.project-dialog::backdrop {
  background: rgba(2, 3, 8, 0.72);
  backdrop-filter: blur(10px);
}

.dialog-shell {
  position: relative;
  max-height: min(860px, calc(100svh - 30px));
  overflow: hidden auto;
}

.dialog-close {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 6;
  display: grid;
  width: 36px;
  height: 36px;
  padding: 0;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  color: var(--text-soft);
  cursor: pointer;
  font-size: 20px;
  backdrop-filter: blur(9px);
}

.dialog-close:hover {
  border-color: var(--accent-line);
  color: var(--accent-strong);
}

.dialog-visual {
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-bottom: 1px solid var(--line);
  background: #06070d;
}

.dialog-video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Summary and what is inside on the left, the stack and the actions on the right, so the
   case reads as a page with a sidebar rather than one long column under a picture. */
.dialog-content {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.6fr);
  gap: 40px;
  min-width: 0;
  padding: 34px 38px 38px;
}

.dialog-main,
.dialog-aside {
  min-width: 0;
}

.dialog-aside .dialog-section:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.dialog-aside .dialog-actions {
  margin-top: 28px;
}

.dialog-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-bottom: 20px;
  color: var(--faint);
  font-family: var(--mono);
  font-size: 9px;
  text-transform: uppercase;
}

.dialog-content h2 {
  margin: 0;
  font-size: clamp(34px, 5vw, 54px);
  line-height: 1;
  letter-spacing: -0.055em;
}

.dialog-summary {
  margin: 22px 0 0;
  color: var(--text-soft);
  font-size: 14px;
  line-height: 1.8;
}

.dialog-section {
  margin-top: 30px;
  padding-top: 24px;
  border-top: 1px solid var(--line);
}

.dialog-section h3 {
  margin: 0 0 14px;
  color: var(--accent-strong);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.dialog-list {
  display: grid;
  gap: 9px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.dialog-list li {
  position: relative;
  padding-left: 19px;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.6;
}

.dialog-list li::before {
  position: absolute;
  top: 0.65em;
  left: 0;
  width: 6px;
  height: 6px;
  border: 1px solid var(--accent);
  content: "";
  transform: rotate(45deg);
}

.dialog-stack {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.dialog-stack span {
  padding: 6px 9px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--panel-2);
  color: var(--text-soft);
  font-family: var(--mono);
  font-size: 9px;
}

.dialog-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 28px;
  color: var(--accent-strong);
  font-size: 12px;
  font-weight: 750;
}

/* The copy control had no rule at all, and the global button reset only restores the
   font, so the browser's own light chrome was showing through as a grey slab in the
   middle of a dark glass dialog. It is a compact secondary action, so it is built like
   one: pill, hairline, panel fill, accent halo on hover. */

.dialog-copy {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 34px;
  margin-top: 28px;
  padding: 0 16px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--panel-2) 65%, transparent);
  color: var(--text-soft);
  cursor: pointer;
  font-size: 12px;
  font-weight: 750;
backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: transform 0.2s var(--ease), box-shadow 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.dialog-copy:hover {
  border-color: var(--accent-line);
  color: var(--accent-strong);
  box-shadow: var(--glow-1);
  transform: translateY(-1px);
}

.dialog-copy:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.toast {
  position: fixed;
  right: max(20px, env(safe-area-inset-right));
  bottom: max(20px, env(safe-area-inset-bottom));
  z-index: 200;
  max-width: min(360px, calc(100vw - 40px));
  padding: 11px 14px;
  border: 1px solid var(--accent-line);
  border-radius: 13px;
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  box-shadow: var(--shadow-2), var(--glow-1);
  color: var(--text-soft);
  font-size: 11px;
  opacity: 0;
  pointer-events: none;
  transform: translateY(14px);
  transition: opacity 0.2s ease, transform 0.2s var(--ease);
  backdrop-filter: blur(9px);
}

.toast.visible {
  opacity: 1;
  transform: translateY(0);
}

.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}

.reveal.visible {
  opacity: 1;
  transform: translateY(0);
}

.stack-terminal.reveal.visible {
  transform: none;
}

/* Replaces the old background-position sweep: opacity is composited, background is not. */
@keyframes edgeGlow {
  0%, 100% { opacity: 0.58; }
  50% { opacity: 1; }
}

@keyframes orbDrift {
  to { transform: translate3d(9vw, 6vh, 0) scale(1.12); }
}

@keyframes auroraSpin {
  to { transform: rotate(360deg); }
}

/* The mesh crawls along a vector that is deliberately not parallel to its own cells, so the
   hexagons never appear to lock to the viewport. The offset is a whole cell, which keeps the
   tiling seamless across the loop. */
@keyframes hexDrift {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(56px, 48.5px, 0); }
}

@keyframes traceDrift {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(-140px, -70px, 0); }
}

/* A slow downward crawl of the grid, deliberately out of phase with the cell size so the
   pattern never appears to lock to the viewport. */
@keyframes gridDrift {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(-54px, 54px, 0); }
}

@keyframes accentMenuIn {
  from { opacity: 0; transform: translateY(-6px) scale(0.97); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes livePulse {
  50% { opacity: 0.45; transform: scale(0.78); }
}

@keyframes ribbonMove {
  to { transform: translateX(-50%); }
}

@keyframes profileBarGrow {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

@keyframes cursorBlink {
  50% { opacity: 0; }
}

@keyframes contactPulse {
  0%, 100% { opacity: 0.55; transform: translate(-50%, -50%) scale(0.96); }
  50% { opacity: 0.9; transform: translate(-50%, -50%) scale(1.06); }
}

@media (max-width: 1024px) {
  :root {
    --page: min(100% - 32px, 940px);
  }

  .hero {
    grid-template-columns: minmax(0, 1fr);
    gap: 56px;
    padding-top: 150px;
  }

  .hero-copy {
    max-width: 780px;
  }
}

.profile-card {
  position: relative;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  justify-self: stretch;
  overflow: hidden;
  transform-style: preserve-3d;
  transition: transform 0.16s ease-out, border-color 0.3s ease, box-shadow 0.3s ease;
}

.profile-card:hover {
  border-color: var(--accent-line);
  box-shadow: var(--shadow-2), var(--glow-1);
}

.profile-card-glow {
  position: absolute;
  top: -150px;
  right: -100px;
  width: 360px;
  height: 360px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--accent-soft), transparent 68%);
  pointer-events: none;
}

.profile-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 50px;
  padding: 0 16px;
  border-bottom: 1px solid var(--line);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.profile-card-live {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--accent-strong);
}

.profile-card-live i,
.profile-location i {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--good);
  box-shadow: 0 0 10px var(--good);
}

.profile-card-body {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 14px;
  padding: 18px;
}

.profile-identity {
  display: grid;
  grid-template-columns: 88px minmax(0, 1fr) auto;
  align-items: center;
  gap: 13px;
}

.profile-avatar {
  position: relative;
  display: grid;
  width: 88px;
  height: 88px;
  place-items: center;
  overflow: hidden;
  border: 1px solid var(--accent-line);
  border-radius: 22px;
  background: linear-gradient(145deg, var(--accent-soft), var(--panel-2));
  color: var(--accent-strong);
  box-shadow: var(--glow-1), inset 0 1px 0 rgba(255, 255, 255, 0.15);
  font-size: 11px;
  font-weight: 900;
}

.profile-avatar img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Tuned to this portrait: the figure sits in the right third of the frame, so a centred
     crop framed an empty stretch of sea and lost the subject. These values put the hood
     and shoulders in the middle of the box. */
  object-position: 74% 56%;
  filter: saturate(0.82) contrast(1.05) brightness(0.9);
}

.profile-avatar::after {
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.48), transparent 48%), linear-gradient(130deg, rgba(255, 255, 255, 0.2), transparent 34%);
  content: "";
  pointer-events: none;
}

.profile-avatar > span {
  position: relative;
  z-index: 2;
  align-self: end;
  justify-self: end;
  margin: 0 6px 5px 0;
  padding: 2px 4px;
  border: 1px solid rgba(255, 255, 255, 0.24);
  border-radius: 5px;
  background: rgba(5, 6, 10, 0.64);
  color: var(--accent-strong);
  font-family: var(--mono);
  font-size: 7px;
  backdrop-filter: blur(8px);
}

.profile-avatar i {
  position: absolute;
  right: 8px;
  bottom: 8px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 12px var(--accent);
}

.profile-identity-copy {
  min-width: 0;
}

.profile-identity-copy > span {
  color: var(--accent-strong);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.profile-identity-copy h2 {
  margin: 3px 0 1px;
  font-size: 23px;
  line-height: 1.1;
  letter-spacing: -0.04em;
}

.profile-identity-copy h2 small {
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0;
}

.profile-identity-copy p {
  margin: 0;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 9px;
}

.profile-location {
  display: flex;
  align-items: center;
  gap: 6px;
  align-self: start;
  margin-top: 6px;
  color: var(--muted);
  font-size: 9px;
  white-space: nowrap;
}

.profile-summary {
  margin: 0;
  padding: 12px 13px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: color-mix(in srgb, var(--panel-2) 50%, transparent);
  color: var(--text-soft);
  font-size: 11px;
  line-height: 1.65;
}

.profile-module,
.profile-useful {
  padding: 13px;
  border: 1px solid var(--line);
  border-radius: 15px;
  background: color-mix(in srgb, var(--panel-2) 38%, transparent);
}

.profile-module-head,
.profile-useful-head {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 8px;
  text-transform: uppercase;
}

.profile-module-head > span {
  color: var(--accent);
}

.profile-module-head strong {
  color: var(--text-soft);
  font-size: 9px;
  font-weight: 700;
}

.profile-module-head i {
  margin-left: auto;
  color: var(--good);
  font-style: normal;
}

.profile-stack {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 11px;
}

.profile-stack span {
  padding: 5px 7px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-2);
  color: var(--text-soft);
  font-family: var(--mono);
  font-size: 8px;
}

.profile-useful-head {
  justify-content: space-between;
  margin-bottom: 11px;
}

.profile-useful-head > span {
  color: var(--text-soft);
  font-weight: 800;
}

.profile-useful-head i {
  color: var(--faint);
  font-style: normal;
}

.profile-bar-row {
  display: grid;
  grid-template-columns: 104px minmax(80px, 1fr) 20px;
  align-items: center;
  gap: 9px;
  min-height: 38px;
  border-top: 1px solid var(--line);
}

.profile-bar-label {
  display: grid;
  min-width: 0;
}

.profile-bar-label strong {
  color: var(--text-soft);
  font-size: 10px;
}

.profile-bar-label small {
  overflow: hidden;
  color: var(--faint);
  font-size: 8px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.profile-bar {
  height: 5px;
  overflow: hidden;
  border-radius: var(--r-pill);
  background: var(--line);
}

.profile-bar i {
  position: relative;
  display: block;
  height: 100%;
  overflow: hidden;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--accent-2), var(--accent));
  box-shadow: 0 0 11px var(--accent);
  transform-origin: 0 50%;
  animation: profileBarGrow 0.9s var(--ease) both;
}

.profile-bar-row[data-level="82"] .profile-bar i { width: 82%; }
.profile-bar-row[data-level="90"] .profile-bar i { width: 90%; }
.profile-bar-row[data-level="76"] .profile-bar i { width: 76%; }

.profile-bar-row > span {
  color: var(--faint);
  font-family: var(--mono);
  font-size: 8px;
}

.profile-links {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 7px;
}

.profile-links a {
  display: grid;
  grid-template-columns: 32px minmax(0, 1fr) auto;
  align-items: center;
  gap: 9px;
  min-width: 0;
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: color-mix(in srgb, var(--panel-2) 44%, transparent);
  transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s var(--ease), box-shadow 0.2s ease;
}

.profile-links a:hover {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  box-shadow: var(--glow-1);
  transform: translateY(-1px);
}

.profile-link-icon {
  display: grid;
  width: 32px;
  height: 32px;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg-deep);
  color: var(--text);
}

.profile-link-icon svg {
  width: 17px;
  height: 17px;
  fill: currentColor;
}

.profile-link-copy {
  display: grid;
  min-width: 0;
}

.profile-link-copy small {
  color: var(--faint);
  font-size: 7px;
  text-transform: uppercase;
}

.profile-link-copy strong {
  overflow: hidden;
  color: var(--text-soft);
  font-size: 10px;
  text-overflow: ellipsis;
}

.profile-links > a > i {
  color: var(--accent);
  font-style: normal;
}

.profile-card-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 44px;
  padding: 0 16px;
  border-top: 1px solid var(--line);
  color: var(--faint);
  font-family: var(--mono);
  font-size: 8px;
}

.profile-card.reveal.visible {
  transform: none;
}

/* The clock is the first thing to go when the header runs out of room: at 820px in Russian
   the five nav labels plus the clock no longer fit one row, and the longest label wrapped
   onto two lines. Labels stay on one line (nowrap above); the clock yields instead. */
@media (max-width: 1100px) {
  .local-time {
    display: none;
  }
}

/* Between the phone menu (769px) and a laptop the header is a tight row: brand, five
   labels, four controls and the contact button. Tighter label padding and a shorter
   button keep all of it inside the bar in both languages; Russian labels run longer. */
@media (min-width: 769px) and (max-width: 940px) {
  .desktop-nav a {
    padding-inline: 8px;
  }

  .header-contact {
    padding-inline: 11px;
  }
}

/* Under 860px the button keeps its plane icon and gives up its label. The label is clipped
   rather than removed, so the link keeps its accessible name, and the same word is one tap
   away in the nav beside it. */
@media (min-width: 769px) and (max-width: 860px) {
  .header-contact {
    width: 34px;
    padding-inline: 0;
  }

  .header-contact .icon {
    margin-right: 0;
  }

  .header-contact > span {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

@media (max-width: 1024px) {
  .section-heading {
    grid-template-columns: 1fr;
    gap: 18px;
  }

  .section-heading > p {
    max-width: 64ch;
  }

  .capability-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* Four facts across a tablet column are too narrow to read, so they pair up. */
  .hero-facts {
    grid-template-columns: repeat(2, 1fr);
  }

  .stack-layout {
    grid-template-columns: 1fr;
  }

  .stack-terminal {
    position: relative;
    top: auto;
  }

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

  .about-copy {
    max-width: 760px;
  }

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

  .lab-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 768px) {
  :root {
    --page: calc(100% - 24px);
    --r-lg: 22px;
  }

  .site-header {
    top: max(10px, env(safe-area-inset-top));
    grid-template-columns: 1fr auto;
    min-height: 54px;
  }

  .desktop-nav,
  .header-contact,
  .local-time {
    display: none;
  }

  .menu-button {
    position: relative;
    display: inline-grid;
  }

  .mobile-menu {
    display: grid;
    opacity: 0;
    /* Opacity and pointer-events alone leave the links in the tab order, so a keyboard
       user could focus an invisible menu. Visibility takes the subtree out of focus too,
       and it flips back at the end of the transition when the menu closes. */
    visibility: hidden;
    pointer-events: none;
    transform: translateY(-8px) scale(0.98);
    transition: opacity 0.2s ease, transform 0.2s var(--ease), visibility 0.2s;
  }

  .mobile-menu.open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0) scale(1);
  }

  .ambient-orb {
    width: 72vw;
    opacity: 0.1;
  }

  #networkCanvas {
    opacity: 0.2;
  }

  .hero {
    min-height: auto;
    padding-top: 122px;
    padding-bottom: 64px;
  }

  /* The headline and both calls to action open the page; the profile card follows them.
     With the card first, the h1 started at y=950 and the primary button at y=1310 on an
     844px phone, so the first screen showed a card and no answer to "who is this". */
  .profile-card {
    width: 100%;
    max-width: 100%;
  }

  .profile-card-body {
    padding: 15px;
  }

  .tech-ribbon {
    width: 100%;
    transform: none;
  }

  .hero h1 {
    font-size: clamp(44px, 12.5vw, 68px);
  }

  .status-pill {
    align-items: flex-start;
    line-height: 1.45;
  }

  .status-pill i {
    margin-top: 5px;
  }

  .status-pill span {
    white-space: normal;
  }

  .hero-lead {
    margin-top: 22px;
    font-size: 15px;
    text-wrap: pretty;
  }

  .hero-facts {
    grid-template-columns: 1fr;
  }

  .profile-identity {
    grid-template-columns: 76px minmax(0, 1fr);
  }

  .profile-avatar {
    width: 60px;
    height: 60px;
    border-radius: 19px;
  }

  .profile-location {
    grid-column: 2;
    margin-top: -5px;
  }

  .profile-bar-row {
    grid-template-columns: 90px minmax(70px, 1fr) 18px;
  }

  .profile-card-footer {
    align-items: flex-start;
    flex-direction: column;
    justify-content: center;
    gap: 2px;
  }

  .hero-facts > div {
    grid-template-columns: 90px 1fr;
    align-items: center;
  }

  .section-shell:not(.hero) {
    padding-top: 108px;
  }

  .section-heading {
    margin-bottom: 34px;
  }

  .section-heading h2,
  .about-copy h2,
  .contact-card h2 {
    font-size: clamp(34px, 10vw, 50px);
  }

  .capability-grid,
  .lab-grid {
    grid-template-columns: 1fr;
  }

  .capability-card {
    min-height: 280px;
  }

  /* One card per row, so there is no shared baseline to protect and the reserved title
     line would only add dead space. */
  .capability-card h3 {
    min-height: 0;
  }

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

  .process-item {
    grid-template-columns: 38px 1fr;
  }

  .process-item p {
    grid-column: 2;
  }

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

  .dialog-content {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    padding: 26px 22px 30px;
  }

  .site-footer {
    grid-template-columns: 1fr;
  }

  .footer-links {
    flex-wrap: wrap;
  }
}

@media (max-width: 520px) {
  .brand-copy {
    display: none;
  }

  .header-actions {
    gap: 4px;
  }

  .hero-actions .button,
  .contact-actions .button {
    width: 100%;
  }

  .about-links {
    grid-template-columns: 1fr;
  }

  .principles,
  .contact-card {
    padding: 20px;
  }

  .contact-meta i {
    display: none;
  }

  .footer-links {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }

  .footer-links a:last-child {
    grid-column: 1 / -1;
    text-align: center;
  }

  #networkCanvas {
    display: none;
  }
}

@media (pointer: coarse) {
  .icon-button,
  .menu-button {
    width: 42px;
    height: 42px;
  }

  .filter-button {
    min-height: 42px;
  }

  .footer-links a {
    min-height: 38px;
  }
}

@media (max-width: 380px) {
  .site-header .glow-button,
  .site-header .motion-button {
    display: none;
  }
}

/* ---------------------------------------------------------------------------------------
   Alive. The page at rest used to be a set of still panels that only reacted to a pointer.
   Everything below moves or glows on its own, and every term follows the page rules: only
   transform and opacity are ever animated (a glow is an element whose opacity breathes, not
   a shadow whose size changes), the glow strength follows --g, and the global reduced-motion
   and data-anim="off" switches above cancel all of it.
   --------------------------------------------------------------------------------------- */

@keyframes haloBreath {
  0%, 100% { opacity: 0.1; }
  50% { opacity: 0.85; }
}

@keyframes iconBob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}

@keyframes ringPing {
  0% { opacity: 0.7; transform: scale(1); }
  70%, 100% { opacity: 0; transform: scale(1.7); }
}

@keyframes runnerX {
  from { transform: translateX(-130%); }
  to { transform: translateX(560%); }
}

@keyframes runnerCard {
  from { transform: translateX(-100%); }
  to { transform: translateX(260%); }
}

@keyframes idleShine {
  0%, 58% { transform: translateX(-140%) skewX(-16deg); }
  100% { transform: translateX(240%) skewX(-16deg); }
}

@keyframes railRun {
  from { transform: translateY(-100%); }
  to { transform: translateY(100%); }
}

@keyframes tickerY {
  from { transform: translateY(0); }
  to { transform: translateY(-50%); }
}

@keyframes ringOut {
  0% { opacity: 0.55; transform: scale(0.5); }
  100% { opacity: 0; transform: scale(2.4); }
}

@keyframes orbitSpin {
  to { transform: rotate(360deg); }
}

/* Section dividers: a hairline with a light that runs along it, so the seams between
   sections are part of the show instead of blank space. */
.section-shell:not(.hero)::before,
.section-shell:not(.hero)::after {
  position: absolute;
  top: 0;
  left: 0;
  height: 1px;
  content: "";
  pointer-events: none;
}

.section-shell:not(.hero)::before {
  right: 0;
  background: linear-gradient(90deg, transparent, var(--line) 18%, var(--line) 82%, transparent);
}

.section-shell:not(.hero)::after {
  width: 16%;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  box-shadow: 0 0 14px var(--accent);
  animation: runnerX 9s linear infinite;
}

/* Section numbers breathe. */
.section-kicker span:first-child::after {
  position: absolute;
  inset: -2px;
  border: 1px solid var(--accent);
  border-radius: 9px;
  box-shadow: 0 0 14px var(--accent);
  content: "";
  opacity: 0.1;
  animation: haloBreath 4.4s ease-in-out infinite;
}

/* Capability and lab icons: a slow float and a ring that pings out of the tile. Neighbours
   are offset so a row of six never pulses in unison. */
.capability-icon,
.lab-icon {
  position: relative;
}

.capability-icon::after,
.lab-icon::after {
  position: absolute;
  inset: -1px;
  border: 1px solid var(--accent);
  border-radius: inherit;
  content: "";
  opacity: 0;
  animation: ringPing 3.8s ease-out infinite;
}

.capability-icon .icon,
.lab-icon .icon {
  animation: iconBob 4.6s ease-in-out infinite;
}

.capability-card:nth-child(2) :is(.capability-icon, .capability-icon .icon),
.lab-card:nth-child(2) :is(.lab-icon, .lab-icon .icon) { animation-delay: -0.7s; }
.capability-card:nth-child(3) :is(.capability-icon, .capability-icon .icon),
.lab-card:nth-child(3) :is(.lab-icon, .lab-icon .icon) { animation-delay: -1.4s; }
.capability-card:nth-child(4) :is(.capability-icon, .capability-icon .icon),
.lab-card:nth-child(4) :is(.lab-icon, .lab-icon .icon) { animation-delay: -2.1s; }
.capability-card:nth-child(5) :is(.capability-icon, .capability-icon .icon),
.lab-card:nth-child(5) :is(.lab-icon, .lab-icon .icon) { animation-delay: -2.8s; }
.capability-card:nth-child(6) :is(.capability-icon, .capability-icon .icon),
.lab-card:nth-child(6) :is(.lab-icon, .lab-icon .icon) { animation-delay: -3.5s; }

/* Stack cards: the coloured bar on top gets a light that sweeps across the card. */
.stack-card::after {
  position: absolute;
  top: 0;
  left: 0;
  width: 38%;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--stack-color, var(--accent)), transparent);
  content: "";
  animation: runnerCard 6.5s linear infinite;
}

.stack-card:nth-child(2)::after { animation-delay: -1.1s; }
.stack-card:nth-child(3)::after { animation-delay: -2.2s; }
.stack-card:nth-child(4)::after { animation-delay: -3.3s; }
.stack-card:nth-child(5)::after { animation-delay: -4.4s; }
.stack-card:nth-child(6)::after { animation-delay: -5.5s; }

/* Pointer light on the cards that did not have it. The offsets are written by
   setupSpatialCards; without a pointer they rest at the top centre. */
/* Buttons. Every icon sits in the same small disc, so an arrow, a plane and the GitHub mark
   read as one family. The primary button also catches the light every few seconds. */
.button > i {
  display: grid;
  flex: none;
  width: 24px;
  height: 24px;
  margin-right: -6px;
  place-items: center;
  border-radius: 50%;
  background: color-mix(in srgb, currentColor 14%, transparent);
  font-size: 13px;
  line-height: 1;
}

.button > i .icon {
  width: 13px;
  height: 13px;
}

.button-primary::before {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 42%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.5), transparent);
  content: "";
  animation: idleShine 5.6s ease-in-out infinite;
  pointer-events: none;
}

/* The bars of the profile card carry a light that travels along the fill. */
.profile-bar i::after {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 40%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.7), transparent);
  content: "";
  animation: idleShine 4.2s ease-in-out infinite;
}

.profile-bar-row:nth-child(2) .profile-bar i::after { animation-delay: -1.2s; }
.profile-bar-row:nth-child(3) .profile-bar i::after { animation-delay: -2.4s; }

/* The contact orb carries a small moon on a slow orbit. */
.contact-orb::after {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  background: var(--accent-strong);
  box-shadow: 0 0 18px var(--accent), 0 0 42px var(--accent);
  content: "";
  transform: rotate(0deg) translateX(270px);
  animation: orbitSpin 16s linear infinite;
}

/* Signal rings leave the orb and spread across the card, so a wide card is never a wide
   empty one. Three rings, a third of the cycle apart. */
.contact-rings {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  pointer-events: none;
}

.contact-rings i {
  position: absolute;
  top: -270px;
  left: -270px;
  width: 540px;
  height: 540px;
  border: 1px solid color-mix(in srgb, var(--accent) 70%, transparent);
  border-radius: 50%;
  box-shadow: 0 0 40px color-mix(in srgb, var(--accent) 35%, transparent);
  opacity: 0;
  animation: ringOut 9s ease-out infinite;
}

.contact-rings i:nth-child(2) { animation-delay: -3s; }
.contact-rings i:nth-child(3) { animation-delay: -6s; }

/* A video that is playing is lit: the glow is a status, not decoration. */
.case-frame.is-playing {
  border-color: var(--accent-line);
  box-shadow: var(--shadow-2), 0 0 46px color-mix(in srgb, var(--accent) calc(var(--g) * 20%), transparent);
}

/* Headings pick up a soft halo in the dark theme. */
html[data-theme="dark"] .section-heading h2,
html[data-theme="dark"] .contact-card h2,
html[data-theme="dark"] .about-copy h2 {
  text-shadow: 0 0 56px color-mix(in srgb, var(--accent) calc(var(--g) * 20%), transparent);
}

/* Footer link icons: sized to the label, not to the 12px arrow they used to share. */
.footer-links .icon {
  width: 15px;
  height: 15px;
  vertical-align: baseline;
  opacity: 0.85;
}

/* The gutters. Past 1760px the content column leaves a wide margin on each side, which used
   to be plain dark. Two quiet rails fill it: a vertical line with a light running down it
   and a caption on the left, a ticker of the stack on the right. They are decoration, so
   they are hidden from assistive technology and never take the pointer. */
.side-rail {
  position: fixed;
  top: 50%;
  z-index: 4;
  display: none;
  height: min(64vh, 580px);
  transform: translateY(-50%);
  pointer-events: none;
}

@media (min-width: 1760px) {
  .side-rail {
    display: flex;
  }
}

.side-rail-left {
  left: calc((100vw - var(--page)) / 4 - 14px);
  flex-direction: column;
  align-items: center;
  gap: 16px;
  width: 28px;
}

.side-rail-right {
  right: calc((100vw - var(--page)) / 4 - 14px);
  width: 28px;
  justify-content: center;
}

.rail-line {
  position: relative;
  flex: 1;
  width: 1px;
  overflow: hidden;
  background: linear-gradient(transparent, var(--line), transparent);
}

.rail-line i {
  position: absolute;
  inset: 0 -1px;
  background: linear-gradient(transparent, var(--accent), transparent);
  box-shadow: 0 0 12px var(--accent);
  opacity: 0.9;
  transform: translateY(-100%);
  animation: railRun 5.2s ease-in-out infinite;
}

.rail-line:last-child i {
  animation-delay: -2.6s;
}

.rail-text {
  color: var(--faint);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.42em;
  text-transform: uppercase;
  transform: rotate(180deg);
  white-space: nowrap;
  writing-mode: vertical-rl;
}

.rail-ticker {
  height: 100%;
  overflow: hidden;
  mask-image: linear-gradient(transparent, #000 20%, #000 80%, transparent);
  -webkit-mask-image: linear-gradient(transparent, #000 20%, #000 80%, transparent);
}

.rail-track {
  display: flex;
  flex-direction: column;
  align-items: center;
  animation: tickerY 42s linear infinite;
}

.rail-track span {
  display: flex;
  flex: none;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding-bottom: 16px;
  color: var(--faint);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.38em;
  text-transform: uppercase;
  white-space: nowrap;
  writing-mode: vertical-rl;
}

.rail-track span::after {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 10px var(--accent);
  content: "";
}

/* ---------------------------------------------------------------------------------------
   Motion layer. These are the pieces motion.js drives: the intro curtain, headings that rise
   word by word, the cursor light, the timeline, and a light that crosses each card as it
   arrives. Animated properties are transform and opacity only; the stroke draw uses
   stroke-dashoffset, which the compositor handles for a path. Effects off removes them.
   --------------------------------------------------------------------------------------- */

@keyframes introDraw {
  to { stroke-dashoffset: 0; }
}

@keyframes introRise {
  to { opacity: 1; transform: none; }
}

@keyframes introBar {
  to { transform: scaleX(1); }
}

@keyframes introCurtain {
  to { transform: translateY(-101%); }
}

@keyframes introFailsafe {
  to { opacity: 0; }
}

@keyframes cardSweep {
  from { transform: translateX(-120%); }
  to { transform: translateX(120%); }
}

.intro {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: none;
  place-items: center;
  background:
    radial-gradient(900px 520px at 50% 44%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 70%),
    var(--bg);
  pointer-events: none;
}

/* The curtain is up from the first frame. If the script never starts it, this fades it out
   on its own, so a failed script can never leave the page covered. */
html[data-intro-run] .intro {
  display: grid;
  animation: introFailsafe 0.5s ease 3.6s forwards;
}

html[data-intro-run] .intro.is-playing {
  will-change: transform;
  animation: introCurtain 0.8s cubic-bezier(0.76, 0, 0.24, 1) 1.55s forwards;
}

.intro::after {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  box-shadow: 0 0 34px var(--accent);
  content: "";
}

.intro-core {
  display: grid;
  justify-items: center;
  gap: 12px;
}

.intro-mark {
  width: 104px;
  height: 104px;
  overflow: visible;
  filter: drop-shadow(0 0 14px color-mix(in srgb, var(--accent) 60%, transparent));
}

.im-g,
.im-x {
  stroke-width: 5;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.im-g {
  stroke: var(--text);
}

.im-x {
  stroke: var(--accent);
}

.intro.is-playing .im-g {
  animation: introDraw 0.8s var(--ease) 0.15s forwards;
}

.intro.is-playing .im-g:nth-child(2) {
  animation-delay: 0.7s;
  animation-duration: 0.3s;
}

.intro.is-playing .im-x {
  animation: introDraw 0.7s var(--ease) 0.8s forwards;
}

.intro-name,
.intro-tag {
  opacity: 0;
  transform: translateY(14px);
}

.intro-name {
  margin-top: 6px;
  font-size: 36px;
  font-weight: 850;
  letter-spacing: -0.055em;
}

.intro-tag {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.28em;
  text-transform: uppercase;
}

.intro.is-playing .intro-name {
  animation: introRise 0.6s var(--ease) 0.75s forwards;
}

.intro.is-playing .intro-tag {
  animation: introRise 0.6s var(--ease) 0.95s forwards;
}

.intro-bar {
  width: 168px;
  height: 2px;
  margin-top: 14px;
  overflow: hidden;
  border-radius: 2px;
  background: var(--line);
}

.intro-bar i {
  display: block;
  height: 100%;
  background: var(--accent);
  box-shadow: 0 0 14px var(--accent);
  transform: scaleX(0);
  transform-origin: left;
}

.intro.is-playing .intro-bar i {
  animation: introBar 1.35s cubic-bezier(0.6, 0, 0.2, 1) 0.1s forwards;
}

/* Headings: each word rises out of its own mask. */
.w {
  display: inline-block;
  margin: -0.2em -0.06em -0.22em;
  padding: 0.2em 0.06em 0.22em;
  overflow: hidden;
  vertical-align: top;
}

/* The word has the same padding as its mask, so a tight line-height cannot shave the tops
   off tall letters, and the gradient fill still covers the whole glyph. */
.wi {
  display: inline-block;
  margin: -0.2em -0.06em -0.22em;
  padding: 0.2em 0.06em 0.22em;
  transform: translateY(118%);
  transition: transform 0.9s var(--ease) calc(var(--i, 0) * 70ms + 60ms);
}

.split-in .wi {
  transform: translateY(0);
}

.split-done .w {
  overflow: visible;
}

/* The words carry the gradient themselves, so the heading's own text-clipped background is
   dropped; left on, it painted a second copy of the glyphs through the masks. */
.hero h1.is-split {
  background: none;
}

.hero h1 .wi {
  background: var(--h1-fill);
  background-clip: text;
  color: transparent;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* The same rule for the section headings: a text-clipped gradient on the heading itself
   cannot reach through the masked word boxes, so it smeared every word into one block. */
.section-heading h2.is-split,
.about-copy h2.is-split,
.contact-card h2.is-split {
  background: none;
}

.section-heading h2 .wi,
.about-copy h2 .wi,
.contact-card h2 .wi {
  background: linear-gradient(180deg, var(--text), color-mix(in srgb, var(--text) 78%, var(--accent) 22%));
  background-clip: text;
  color: transparent;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.is-split:not(.split-done) {
  text-shadow: none !important;
}

/* The process list draws a line down itself as it is read. */
.process-list::before,
.process-list::after {
  position: absolute;
  top: 14px;
  bottom: 14px;
  left: 9px;
  width: 2px;
  border-radius: 2px;
  content: "";
}

.process-list::before {
  background: var(--line);
}

.process-list::after {
  background: linear-gradient(var(--accent-2), var(--accent));
  box-shadow: 0 0 16px var(--accent);
  transform: scaleY(var(--tl, 0));
  transform-origin: top;
  transition: transform 0.25s linear;
}

.process-item::before {
  position: absolute;
  top: 24px;
  left: -29px;
  width: 12px;
  height: 12px;
  border: 2px solid var(--line);
  border-radius: 50%;
  background: var(--bg);
  content: "";
  transition: border-color 0.3s ease, background 0.3s ease, box-shadow 0.4s ease, transform 0.4s var(--ease);
}

.process-item.is-lit::before {
  border-color: var(--accent);
  background: var(--accent);
  box-shadow: 0 0 14px var(--accent);
  transform: scale(1.15);
}

/* A light crosses each card once as it arrives. */
.case::before {
  position: absolute;
  inset: 0;
  z-index: 4;
  background: linear-gradient(105deg, transparent 34%, color-mix(in srgb, var(--accent) 16%, transparent) 50%, transparent 66%);
  content: "";
  pointer-events: none;
  transform: translateX(-120%);
}

.case.visible::before {
  animation: cardSweep 1.3s var(--ease) 0.15s 1 both;
}

.case:nth-child(even).visible::before {
  animation-delay: 0.35s;
}

html[data-fx="off"] .case::before {
  display: none;
}

/* The header quick toggles for glow and motion live in the panel below 940px. */
@media (max-width: 940px) {
  #glowToggle,
  #motionToggle {
    display: none;
  }
}

/* Effects off. Each decorative layer is switched off by name, so turning the setting off
   removes the extra pieces and leaves the page itself exactly as it was designed. */
html[data-fx="off"] .section-shell:not(.hero)::after,
html[data-fx="off"] .section-kicker span:first-child::after,
html[data-fx="off"] .capability-icon::after,
html[data-fx="off"] .lab-icon::after,
html[data-fx="off"] .stack-card::after,
html[data-fx="off"] .button-primary::before,
html[data-fx="off"] .profile-bar i::after,
html[data-fx="off"] .contact-orb::after,
html[data-fx="off"] .contact-rings,
html[data-fx="off"] .side-rail {
  display: none;
}

html[data-fx="off"] .capability-icon .icon,
html[data-fx="off"] .lab-icon .icon {
  animation: none;
}

html[data-fx="off"] .section-heading h2,
html[data-fx="off"] .contact-card h2,
html[data-fx="off"] .about-copy h2 {
  text-shadow: none;
}

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

  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    animation: none !important;
    transition-duration: 0.01ms !important;
  }

  .reveal {
    opacity: 1;
    transform: none;
  }

  #networkCanvas {
    display: none;
  }
}

html[data-anim="off"] *,
html[data-anim="off"] *::before,
html[data-anim="off"] *::after {
  animation: none !important;
  transition-duration: 0.01ms !important;
  scroll-behavior: auto !important;
}

/* Scrolling is the one moment the page must not spend anything on ambience.
   Five ambient layers animate forever behind a dozen glass panels, and every one of those
   panels has to re-sample its backdrop on each frame the backdrop moves, so the GPU is
   saturated before the content scrolls. Pausing the layers for the duration of the input
   makes the backdrop static: the compositor then moves only the content, and the glass
   panels sample once. The neon returns a moment after the scroll settles.

   The layers are paused, not hidden. Hiding them instead would make the page blink
   between two compositions on every scroll, which reads as a bug rather than as
   performance, and it would throw away the whole background while the user is reading. */

html.is-scrolling .ambient-aurora,
html.is-scrolling .ambient-hex,
html.is-scrolling .ambient-traces,
html.is-scrolling .ambient-grid-drift,
html.is-scrolling .ambient-orb,
html.is-scrolling .tech-ribbon-track{
  animation-play-state: paused !important;
}

html[data-anim="off"] .reveal {
  opacity: 1;
  transform: none;
}

html[data-anim="off"] #networkCanvas,
html[data-anim="off"] .tech-ribbon-track {
  display: none;
}

/* Frozen at frame zero the aurora and the drifting grid would sit as flat washes with
   nothing to anchor them, so they are re-balanced into a single still composition: the
   grid snaps back to an exact cell offset (no half-cell seam) and the orbs gain a little
   opacity to carry the depth the movement used to provide. */
html[data-anim="off"] .ambient-orb {
  opacity: calc(var(--orb-opacity) * 1.5);
}

html[data-anim="off"] .ambient-grid-drift {
  transform: translate3d(-27px, 27px, 0);
}

@media (prefers-reduced-motion: reduce) {
  html .ambient-orb {
    opacity: calc(var(--orb-opacity) * 1.5);
  }
}

@media (hover: none) {
  .scroll-rail {
    display: none;
  }

  /* Touch has no hover, so a press briefly sticks :hover on whatever was tapped. The
     lift-offs below would otherwise leave a card frozen mid-jump until the next tap;
     the colour and border feedback stays, only the movement is dropped. */
  
.capability-card:hover,
  .lab-card:hover,
  .process-item:hover,
  .icon-button:hover,
  .menu-button:hover,
  .header-contact:hover,
  .button:hover,
  .filter-button:hover,
  .stack-card:hover,
  .about-links a:hover,
  .profile-links a:hover,
  .tech-ribbon:hover i{
    transform: none;
  }
}

/* Printing: strip motion/decoration, force revealed content, keep ink readable.
   The selectors here have to be the ones the document actually uses. They used to name
   .cursor-glow, .noise and .topbar, which exist nowhere, so the fixed header and the cursor
   glow were printed on every page. */
@media print {
  .ambient,
  .scroll-rail,
  .skip-link,
  .site-header,
  .mobile-menu,
  .tech-ribbon,
  #techRibbon,
  #controlDock,
  #networkCanvas,
  #projectDialog {
    display: none !important;
  }

  @page {
    margin: 14mm;
  }

  html,
  body {
    background: #ffffff;
    color: #14161c;
  }

  .reveal {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }

  .glass-card,
  .contact-card,
  .dialog-shell {
    background: #ffffff !important;
    border: 1px solid #d8d5cc !important;
    box-shadow: none !important;
    backdrop-filter: none !important;
    color: #14161c !important;
  }

  a {
    color: #14161c !important;
    text-decoration: underline;
  }

  section {
    break-inside: avoid;
    page-break-inside: avoid;
  }

  .hero {
    min-height: 0 !important;
    padding-bottom: 24px !important;
  }
}

