/* The Paper theme's handwriting. A browser only downloads a font face when text on the page
   uses it, so no other theme pays for this file. One weight exists: Paper turns off synthesis
   rather than let the browser smear a fake bold. Latin only, so an arrow falls back. */
@font-face {
  font-family: "Gloria Hallelujah";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/gloria-hallelujah-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* The Forest theme's headings. One weight, latin only, downloaded only when Forest is picked. */
@font-face {
  font-family: "Fraunces";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/fraunces-600-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* The Ember theme's headings, labels and buttons. One weight, latin only, downloaded only when
   Ember is picked. */
@font-face {
  font-family: "Josefin Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/josefin-sans-700-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Inter, the Professional theme's type. A family name of its own rather than "Inter": the site
   asks for Inter everywhere but never shipped it, so every other theme and the film pages show
   the visitor's system font, as each was approved; a face named "Inter" would quietly change
   them all. One variable file covers every weight from 400 to 900. Latin only. */
@font-face {
  font-family: "Inter Variable";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("../fonts/inter-variable-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* The Cassie theme's headings, labels and buttons. One weight, latin only, downloaded only when
   Cassie is picked. */
@font-face {
  font-family: "Fredoka";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/fredoka-600-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Archivo, the film pages' type and the Space theme's headings. Declared here because every
   page loads this file, film pages included, so it has one home. */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/archivo-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/archivo-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/archivo-700-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/archivo-700-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* IBM Plex Mono, the film pages' labels and the Cyber theme's every word. Here for the same
   reason as Archivo. */
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* The Cyber theme's headings and labels. A family name of its own rather than weight 600 of
   "IBM Plex Mono": the film pages draw bold Plex Mono (canvas captions at 600, buttons at 850)
   from the 400 face, and a real 600 face in that family would quietly redraw them. Latin only. */
@font-face {
  font-family: "IBM Plex Mono SemiBold";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-600-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --bg: #07111c;
  --bg-soft: #0c1826;
  --bg-panel: #101f31;
  --text: #e9f3fb;
  --muted: #92a9bd;
  --line: rgba(210, 236, 255, 0.14);
  --cyan: #4de1dc;
  --green: #7cffb2;
  --amber: #f5c86a;
  --rose: #ff6d8d;
  --violet: #9aa7ff;
  --max: 1180px;
  --shadow: 0 24px 90px rgba(0, 0, 0, 0.42);
  --mouse-x: 50%;
  --mouse-y: 20%;
  --bg-mid: #0b1726;
  --bg-deep: #060b12;
  --glow-a: rgba(77, 225, 220, 0.14);
  --glow-b: rgba(124, 255, 178, 0.1);
  --cursor-a: rgba(77, 225, 220, 0.16);
  --cursor-b: rgba(245, 200, 106, 0.08);
  --grid-a: rgba(77, 225, 220, 0.045);
  --grid-b: rgba(124, 255, 178, 0.035);
  --panel-bg: rgba(12, 24, 38, 0.72);
  --panel-bg-strong: rgba(12, 24, 38, 0.86);
  --panel-glow: rgba(77, 225, 220, 0.055);
  --nav-top-bg: linear-gradient(90deg, rgba(100, 255, 218, 0.96), rgba(88, 215, 255, 0.9));
  --nav-top-text: #07111c;
  --nav-scroll-bg: rgba(10, 17, 31, 0.72);
  --nav-link-top: rgba(7, 17, 28, 0.8);
  --backdrop-opacity: 0.9;

  /* Theme controls beyond colour. A theme block may set any of these; these are the defaults.
     Plain values only: a var() here would resolve against :root and ignore the theme. */
  --font-body: Inter, "Open Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-display: Inter, "Open Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-pill: 999px;
  /* Colour families for borders, veils and glows, as r, g, b so each use keeps its own strength. */
  --edge-rgb: 244, 239, 231;
  --hairline-rgb: 210, 236, 255;
  --veil-rgb: 255, 255, 255;
  --glow-green-rgb: 124, 255, 178;
  --glow-cyan-rgb: 77, 225, 220;
  --glow-sky-rgb: 88, 215, 255;
  --glow-gold-rgb: 255, 209, 102;
  --glow-mint-rgb: 102, 227, 166;
  --glow-amber-rgb: 245, 200, 106;
  /* Surfaces drawn dark on purpose for dark themes: the CV sheet and the drive button. */
  --sheet-bg: linear-gradient(180deg, rgba(16, 31, 49, 0.96), rgba(8, 16, 27, 0.98));
  --sheet-head-bg: rgba(9, 20, 34, 0.88);
  --sheet-shadow: 0 30px 100px rgba(0, 0, 0, 0.48);
  --cta-base: rgba(12, 24, 38, 0.7);
}

body[data-theme="professional"],
body.portfolio-v2:not([data-theme]) {
  /* Professional: the default. Navy and blue, the moving backdrop it always had, and Inter
     shipped for real (in the rule below). Its reactions (headings settling in, light on a card,
     a click of stars) are in assets/js/theme-life.js. */
  --truck-art: url("../images/truck-professional.svg");
  --bg: #08111f;
  --bg-mid: #0d1d31;
  --bg-deep: #050911;
  --bg-soft: #101b2c;
  --bg-panel: #142236;
  --text: #eef5ff;
  --muted: #9dafc2;
  --line: rgba(220, 235, 255, 0.16);
  --cyan: #78b7ff;
  --green: #75e0b5;
  --amber: #d8b56d;
  --rose: #f47b96;
  --violet: #aeb8ff;
  --glow-a: rgba(120, 183, 255, 0.13);
  --glow-b: rgba(117, 224, 181, 0.08);
  --cursor-a: rgba(120, 183, 255, 0.16);
  --cursor-b: rgba(216, 181, 109, 0.08);
  --grid-a: rgba(120, 183, 255, 0.04);
  --grid-b: rgba(117, 224, 181, 0.025);
  --panel-bg: rgba(13, 29, 49, 0.78);
  --panel-bg-strong: rgba(15, 35, 58, 0.92);
  --panel-glow: rgba(120, 183, 255, 0.06);
  --nav-top-bg: linear-gradient(90deg, #d7e8ff, #9ecbff);
  --nav-top-text: #07111f;
  --nav-scroll-bg: rgba(8, 17, 31, 0.76);
}

body[data-theme="professional"] {
  /* Professional's Inter, only once the page is set to Professional. A page has no theme until
     portfolio-v2.js runs at the end of it, and in that moment it takes the block above; Inter
     there would start the download for every visitor, whatever theme they picked. */
  --font-body: "Inter Variable", Inter, "Open Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-display: "Inter Variable", Inter, "Open Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

body[data-theme="light"] {
  /* Light mode: a clean, cool daytime page, unlike Paper's warm old sheet. Off-white with crisp
     white cards, soft grey shadows, navy text and one clear blue. Sunlight crosses the page and
     dust drifts in it; that life is in assets/js/theme-life.js. */
  /* The site's own fonts, written out because Professional ships Inter for itself. */
  --font-body: Inter, "Open Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-display: Inter, "Open Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --page-bg:
    radial-gradient(ellipse 70% 26rem at 85% -6rem, rgba(255, 214, 140, 0.22), transparent 70%) no-repeat,
    radial-gradient(ellipse 90% 30rem at 20% -8rem, rgba(150, 195, 255, 0.24), transparent 72%) no-repeat,
    linear-gradient(180deg, #f6f8fb 0, #f2f5f9 60%, #eceff4 100%);
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-pill: 999px;
  --shadow: 0 10px 30px rgba(20, 30, 50, 0.08), 0 2px 6px rgba(20, 30, 50, 0.06);
  --edge-rgb: 21, 32, 51;
  --hairline-rgb: 21, 32, 51;
  --veil-rgb: 21, 32, 51;
  --glow-green-rgb: 29, 95, 209;
  --glow-cyan-rgb: 29, 95, 209;
  --glow-sky-rgb: 29, 95, 209;
  --glow-gold-rgb: 180, 83, 9;
  --glow-mint-rgb: 29, 95, 209;
  --glow-amber-rgb: 180, 83, 9;
  --button-primary-fill: #1d5fd1;
  --button-primary-ink: #ffffff;
  --sheet-bg: #ffffff;
  --sheet-head-bg: #ffffff;
  --sheet-shadow: 0 24px 60px rgba(20, 30, 50, 0.12);
  --cta-base: #ffffff;
  --truck-art: url("../images/truck-light.svg");
  --wind-colour: rgba(120, 130, 145, 0.6);
  /* The road runs through sunny countryside: green hills under white clouds, leafy trees and
     a windmill. */
  --scene-far: url("../images/scene-light-hills.svg");
  --roadside-art: url("../images/roadside-light-tree.svg");
  --roadside-art-far: url("../images/roadside-light-windmill.svg");
  --scene-hill-a: #5fae58;
  --scene-hill-b: #4f9a4a;
  --scene-rock: #9aa3ad;
  --scene-road: #4b5563;
  --bg: #f4f6f9;
  --bg-mid: #eef1f5;
  --bg-deep: #e6eaf0;
  --bg-soft: #ffffff;
  --bg-panel: #ffffff;
  --text: #152033;
  --muted: #4a5568;
  --line: rgba(21, 32, 51, 0.14);
  /* A deeper blue than the main button's, so blue words stay readable on the pale sky glow. */
  --cyan: #1a4fb8;
  --green: #1a4fb8;
  --amber: #b45309;
  --rose: #be123c;
  --violet: #4f46e5;
  --glow-a: rgba(150, 195, 255, 0.18);
  --glow-b: rgba(255, 214, 140, 0.14);
  --cursor-a: rgba(29, 95, 209, 0.12);
  --cursor-b: rgba(255, 200, 120, 0.1);
  --grid-a: transparent;
  --grid-b: transparent;
  --panel-bg: rgba(255, 255, 255, 0.92);
  --panel-bg-strong: #ffffff;
  --panel-glow: transparent;
  --nav-top-bg: rgba(255, 255, 255, 0.72);
  --nav-top-edge: rgba(21, 32, 51, 0.1);
  --nav-top-shadow: none;
  --nav-top-text: #152033;
  --nav-link-top: rgba(21, 32, 51, 0.8);
  --nav-scroll-bg: rgba(255, 255, 255, 0.9);
}

body[data-theme="forest"] {
  /* Forest: night in a forest by a river. Tree lines in three depths along the foot of the
     screen, mist between them, shafts of light from above, river stone corners and soft serif
     headings. Fireflies, falling leaves and the rest of its life are in assets/js/theme-life.js. */
  /* The site's own body font, written out because Professional ships Inter for itself. */
  --font-body: Inter, "Open Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-display: "Fraunces", Georgia, "Times New Roman", serif;
  /* Fraunces ships one weight; headings take it as it is rather than a smeared fake bold. */
  --font-synthesis: style small-caps;
  --radius-sm: 10px;
  --radius-md: 14px;
  --radius-pill: 999px;
  --shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
  --edge-rgb: 214, 232, 205;
  --hairline-rgb: 214, 232, 205;
  --veil-rgb: 255, 255, 255;
  --glow-green-rgb: 143, 190, 120;
  --glow-cyan-rgb: 124, 196, 184;
  --glow-sky-rgb: 124, 196, 184;
  --glow-gold-rgb: 242, 210, 122;
  --glow-mint-rgb: 143, 190, 120;
  --glow-amber-rgb: 242, 210, 122;
  --sheet-bg: linear-gradient(180deg, rgba(12, 30, 22, 0.97), rgba(6, 16, 11, 0.98));
  --sheet-head-bg: rgba(9, 24, 17, 0.92);
  --sheet-shadow: 0 30px 100px rgba(0, 0, 0, 0.55);
  --cta-base: rgba(10, 26, 19, 0.8);
  --truck-art: url("../images/truck-forest.svg");
  --wind-colour: rgba(200, 220, 200, 0.45);
  --scene-far: url("../images/scene-forest-hills.svg");
  --roadside-art: url("../images/roadside-forest-pine.svg");
  --roadside-art-far: url("../images/roadside-forest-oak.svg");
  --scene-hill-a: #0f2418;
  --scene-hill-b: #0b1c13;
  --scene-rock: #3c4a3e;
  --scene-road: #040907;
  --bg: #0a1a13;
  --bg-mid: #08150f;
  --bg-deep: #040b07;
  --bg-soft: #0f241a;
  --bg-panel: #122a1f;
  --text: #eef3e6;
  --muted: #a9b8a3;
  --line: rgba(214, 232, 205, 0.13);
  --cyan: #7cc4b8;
  --green: #a9cf8a;
  --amber: #f2d27a;
  --rose: #e59a8a;
  --violet: #b5c3e8;
  --glow-a: rgba(124, 196, 184, 0.06);
  --glow-b: rgba(242, 210, 122, 0.035);
  --cursor-a: rgba(242, 210, 122, 0.1);
  --cursor-b: rgba(124, 196, 184, 0.06);
  --grid-a: transparent;
  --grid-b: transparent;
  --panel-bg: rgba(10, 26, 19, 0.82);
  --panel-bg-strong: rgba(14, 34, 25, 0.93);
  --panel-glow: transparent;
  --nav-top-bg: transparent;
  --nav-top-edge: rgba(214, 232, 205, 0.08);
  --nav-top-shadow: none;
  --nav-top-text: #eef3e6;
  --nav-link-top: rgba(238, 243, 230, 0.76);
  --nav-scroll-bg: rgba(6, 15, 10, 0.84);
}

body[data-theme="space"] {
  /* Space: a black sky with sparse stars, one dim planet lit from the side and a small moon,
     plain bold headings, and small labels in spaced capitals like a mission control panel.
     The stars that drift and twinkle, and the shooting stars, are in assets/js/theme-life.js. */
  /* The site's own body font, written out because Professional ships Inter for itself. */
  --font-body: Inter, "Open Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --page-bg:
    url("../images/space-planet.svg") right -120px top 90px / 520px 520px no-repeat,
    url("../images/space-moon.svg") left 7% top 1640px / 54px 54px no-repeat,
    radial-gradient(ellipse at 12% 0, rgba(110, 130, 190, 0.07), transparent 42rem) no-repeat,
    url("../images/space-stars-near.svg") 300px 410px / 1093px 1093px,
    url("../images/space-stars-far.svg") 0 0 / 720px 720px,
    #040506;
  --font-display: "Archivo", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-controls: "Archivo", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-controls-size: 0.76rem;
  --label-case: uppercase;
  --label-tracking: 0.12em;
  --label-size: 0.76rem;
  --radius-sm: 3px;
  --radius-md: 4px;
  --radius-pill: 4px;
  --shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
  --edge-rgb: 226, 232, 245;
  --hairline-rgb: 226, 232, 245;
  --veil-rgb: 255, 255, 255;
  --glow-green-rgb: 240, 194, 122;
  --glow-cyan-rgb: 156, 194, 255;
  --glow-sky-rgb: 156, 194, 255;
  --glow-gold-rgb: 240, 194, 122;
  --glow-mint-rgb: 156, 194, 255;
  --glow-amber-rgb: 240, 194, 122;
  --sheet-bg: linear-gradient(180deg, rgba(14, 17, 20, 0.97), rgba(7, 8, 10, 0.98));
  --sheet-head-bg: rgba(10, 12, 14, 0.92);
  --sheet-shadow: 0 30px 100px rgba(0, 0, 0, 0.6);
  --cta-base: rgba(11, 14, 16, 0.8);
  --truck-art: url("../images/truck-space.svg");
  --wheels-art: url("../images/wheels-space.svg");
  --wheels-filter: none;
  --wind-colour: rgba(190, 196, 210, 0.5);
  --scene-far: url("../images/scene-moon-ridge.svg");
  --roadside-art: url("../images/roadside-moon-base.svg");
  --roadside-art-far: url("../images/roadside-moon-mast.svg");
  --roadside-width: 60px;
  --scene-hill-a: #3b3e44;
  --scene-hill-b: #33363b;
  --scene-rock: #8a8d94;
  --scene-road: #2a2c32;
  --bg: #050607;
  --bg-mid: #070809;
  --bg-deep: #020303;
  --bg-soft: #101416;
  --bg-panel: #151a1d;
  --text: #edf1ff;
  --muted: #a3abbd;
  --line: rgba(226, 232, 245, 0.13);
  --cyan: #9cc2ff;
  --green: #f0c27a;
  --amber: #f0c27a;
  --rose: #ff8f86;
  --violet: #b3bcff;
  --glow-a: rgba(156, 194, 255, 0.05);
  --glow-b: rgba(240, 194, 122, 0.035);
  --cursor-a: rgba(156, 194, 255, 0.12);
  --cursor-b: rgba(240, 194, 122, 0.06);
  --grid-a: transparent;
  --grid-b: transparent;
  --panel-bg: rgba(10, 12, 14, 0.84);
  --panel-bg-strong: rgba(15, 18, 21, 0.94);
  --panel-glow: transparent;
  --nav-top-bg: transparent;
  --nav-top-edge: rgba(226, 232, 245, 0.08);
  --nav-top-shadow: none;
  --nav-top-text: #edf1ff;
  --nav-link-top: rgba(237, 241, 255, 0.74);
  --nav-scroll-bg: rgba(4, 5, 6, 0.84);
}

body[data-theme="cyber"] {
  /* Cyber: a Kali terminal in purple. Plum black with a faint magenta and violet aura at the
     top, every word in IBM Plex Mono, labels written as a shell prompt, # before section
     headings, buttons in brackets, square corners and thin violet rules. Colours keep the jobs
     a terminal gives them: purple for the prompt, magenta for the cursor and what is chosen,
     cyan for links, red only for errors. The typing, the log and the rest of its life are in
     assets/js/theme-life.js. */
  --page-bg:
    radial-gradient(ellipse 46rem 26rem at 22% -6rem, rgba(214, 72, 186, 0.13), transparent) no-repeat,
    radial-gradient(ellipse 52rem 30rem at 78% -8rem, rgba(124, 84, 232, 0.15), transparent) no-repeat,
    linear-gradient(180deg, #130e1d 0, #100b19 60%, #0b0812 100%);
  --font-body: "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
  --font-display: "IBM Plex Mono SemiBold", "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
  --font-controls: "IBM Plex Mono SemiBold", "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
  --font-controls-size: 0.82rem;
  --label-size: 0.84rem;
  --eyebrow-case: none;
  --eyebrow-tracking: 0;
  --label-prefix: "abdullah@kali:~$ " / "";
  --heading-prefix: "# " / "";
  --button-open: "[ " / "";
  --button-close: " ]" / "";
  --prompt-colour: #bd93f9;
  --prompt-glow: 0 0 10px rgba(189, 147, 249, 0.45);
  --headline-glow: 0 0 22px rgba(255, 121, 198, 0.28), 0 0 2px rgba(255, 255, 255, 0.25);
  --headline-scale: 0.74;
  --radius-sm: 4px;
  --radius-md: 4px;
  --radius-pill: 4px;
  --shadow: 0 18px 50px rgba(0, 0, 0, 0.5);
  --edge-rgb: 189, 147, 249;
  --hairline-rgb: 189, 147, 249;
  --veil-rgb: 189, 147, 249;
  --glow-green-rgb: 189, 147, 249;
  --glow-cyan-rgb: 139, 233, 253;
  --glow-sky-rgb: 139, 233, 253;
  --glow-gold-rgb: 255, 121, 198;
  --glow-mint-rgb: 189, 147, 249;
  --glow-amber-rgb: 255, 121, 198;
  --sheet-bg: linear-gradient(180deg, rgba(27, 21, 40, 0.97), rgba(15, 11, 24, 0.98));
  --sheet-head-bg: rgba(22, 17, 33, 0.94);
  --sheet-shadow: 0 30px 100px rgba(0, 0, 0, 0.55);
  --cta-base: rgba(27, 21, 40, 0.86);
  --truck-art: url("../images/truck-cyber.svg");
  --wheels-art: url("../images/wheels-cyber.svg");
  --wheels-filter: none;
  --wind-colour: rgba(189, 147, 249, 0.55);
  /* The road scene is drawn in characters, like everything else in a terminal. */
  --scene-far: url("../images/scene-ascii-ridge.svg");
  --roadside-art: url("../images/roadside-ascii-pine.svg");
  --roadside-art-far: url("../images/roadside-ascii-tree.svg");
  --roadside-width: 46px;
  --scene-hill-a: transparent;
  --scene-hill-b: transparent;
  --scene-rock: url("../images/rock-ascii.svg") center / contain no-repeat;
  --scene-road: #bd93f9;
  --bg: #130e1d;
  --bg-mid: #100b19;
  --bg-deep: #0b0812;
  --bg-soft: #1b1528;
  --bg-panel: #1b1528;
  --text: #ece6f7;
  --muted: #b3a8c9;
  --line: rgba(189, 147, 249, 0.2);
  --cyan: #8be9fd;
  --green: #bd93f9;
  --amber: #ff79c6;
  --rose: #ff5555;
  --violet: #bd93f9;
  --glow-a: rgba(189, 147, 249, 0.06);
  --glow-b: rgba(255, 121, 198, 0.04);
  --cursor-a: rgba(255, 121, 198, 0.1);
  --cursor-b: rgba(189, 147, 249, 0.07);
  --grid-a: transparent;
  --grid-b: transparent;
  --panel-bg: rgba(27, 21, 40, 0.9);
  --panel-bg-strong: rgba(31, 24, 46, 0.96);
  --panel-glow: transparent;
  --nav-top-bg: transparent;
  --nav-top-edge: rgba(189, 147, 249, 0.22);
  --nav-top-shadow: none;
  --nav-top-text: #ece6f7;
  --nav-link-top: rgba(236, 230, 247, 0.78);
  --nav-scroll-bg: rgba(19, 14, 29, 0.92);
}

body[data-theme="ember"] {
  /* Ember: the warm layered colour of the Firewatch posters, not their landscape. Deep maroon
     with a sunset glowing in bands at the top, amber labels in spaced capitals, Josefin Sans
     headings like a 1930s park poster, and amber ribbon buttons. The rising embers, the heat
     along the bottom and the rest of its life are in assets/js/theme-life.js. */
  /* The site's own body font, written out because Professional ships Inter for itself. */
  --font-body: Inter, "Open Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --page-bg:
    radial-gradient(ellipse 120% 30rem at 50% -8rem, rgba(255, 182, 39, 0.26), transparent 70%) no-repeat,
    radial-gradient(ellipse 140% 44rem at 50% -10rem, rgba(240, 110, 40, 0.18), transparent 75%) no-repeat,
    radial-gradient(ellipse 160% 60rem at 50% -12rem, rgba(170, 45, 30, 0.2), transparent 80%) no-repeat,
    linear-gradient(180deg, #210807 0, #1a0605 55%, #120303 100%);
  --font-display: "Josefin Sans", Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-controls: "Josefin Sans", Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --eyebrow-font: "Josefin Sans", Inter, system-ui, sans-serif;
  /* Josefin Sans ships one weight; everything bold takes it as it is rather than a fake bold. */
  --font-synthesis: style small-caps;
  --font-controls-size: 0.84rem;
  --label-case: uppercase;
  --label-tracking: 0.16em;
  --label-size: 0.84rem;
  --eyebrow-tracking: 0.22em;
  --button-shape: polygon(0 0, 100% 0, calc(100% - 9px) 50%, 100% 100%, 0 100%, 9px 50%);
  --button-primary-fill: #ffb627;
  --button-primary-ink: #2a0806;
  --radius-sm: 2px;
  --radius-md: 6px;
  --radius-pill: 2px;
  --shadow: 0 22px 60px rgba(0, 0, 0, 0.5);
  --edge-rgb: 255, 182, 39;
  --hairline-rgb: 255, 182, 39;
  --veil-rgb: 255, 220, 180;
  --glow-green-rgb: 255, 182, 39;
  --glow-cyan-rgb: 255, 160, 77;
  --glow-sky-rgb: 255, 160, 77;
  --glow-gold-rgb: 255, 182, 39;
  --glow-mint-rgb: 255, 182, 39;
  --glow-amber-rgb: 255, 122, 61;
  --sheet-bg: linear-gradient(180deg, rgba(46, 16, 11, 0.97), rgba(26, 6, 5, 0.98));
  --sheet-head-bg: rgba(38, 11, 8, 0.94);
  --sheet-shadow: 0 30px 100px rgba(0, 0, 0, 0.55);
  --cta-base: rgba(46, 16, 11, 0.85);
  --truck-art: url("../images/truck-ember.svg");
  --wind-colour: rgba(255, 200, 140, 0.5);
  /* The road runs below sunset ridges in bands, past black pines and a fire lookout. */
  --scene-far: url("../images/scene-ember-ridges.svg");
  --roadside-art: url("../images/roadside-ember-pine.svg");
  --roadside-art-far: url("../images/roadside-ember-tower.svg");
  --scene-hill-a: #3a0f0a;
  --scene-hill-b: #300c08;
  --scene-rock: #5a2418;
  --scene-road: #0f0302;
  --bg: #210807;
  --bg-mid: #1a0605;
  --bg-deep: #120303;
  --bg-soft: #2a0d09;
  --bg-panel: #2e100b;
  --text: #fbe9d2;
  --muted: #d6b79c;
  --line: rgba(255, 182, 39, 0.18);
  --cyan: #ffa04d;
  --green: #ffb627;
  --amber: #ff8a3d;
  --rose: #ff5a36;
  --violet: #f2a07b;
  --glow-a: rgba(255, 160, 60, 0.08);
  --glow-b: rgba(255, 90, 40, 0.06);
  --cursor-a: rgba(255, 138, 61, 0.16);
  --cursor-b: rgba(255, 200, 104, 0.08);
  --grid-a: transparent;
  --grid-b: transparent;
  --panel-bg: rgba(46, 16, 11, 0.84);
  --panel-bg-strong: rgba(52, 18, 12, 0.94);
  --panel-glow: rgba(255, 138, 61, 0.05);
  --nav-top-bg: transparent;
  --nav-top-edge: rgba(255, 182, 39, 0.16);
  --nav-top-shadow: none;
  --nav-top-text: #fbe9d2;
  --nav-link-top: rgba(251, 233, 210, 0.82);
  --nav-scroll-bg: rgba(26, 6, 5, 0.9);
}

body[data-theme="paper"] {
  /* Paper: an old, yellowed sheet of lined paper written on in blue ballpoint, with red pen for
     the things a hand would mark, and cards that are index cards laid on the sheet. Its life
     (writing that draws itself, scribbles, the crumple) is in assets/js/theme-life.js. */
  --text-rule: 32px;
  --page-gutter: 200px;
  /* The red margin line, left of the text column, and the punched holes left of that. */
  --margin-x: max(56px, calc(50vw - min(var(--max), 100vw - var(--page-gutter)) / 2 - 34px));
  --page-bg:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='260' height='260'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 .45  0 0 0 0 .38  0 0 0 0 .25  0 0 0 .22 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>") 0 0 / 260px 260px,
    linear-gradient(#8aa3c2, #8aa3c2) 0 calc(var(--text-rule) * 3 - 2px) / 100% 2px no-repeat,
    linear-gradient(#f1e5c2, #f1e5c2) 0 0 / 100% calc(var(--text-rule) * 3) no-repeat,
    linear-gradient(90deg, transparent calc(var(--margin-x) - 1px), rgba(205, 95, 90, 0.72) calc(var(--margin-x) - 1px) calc(var(--margin-x) + 1px), transparent calc(var(--margin-x) + 1px)) 0 0 / 100% 64px repeat-y,
    radial-gradient(circle at 46% 44%, #2a2521 0 5px, #3f3a35 9px, rgba(60, 45, 20, 0.5) 10.2px, transparent 12px) calc(var(--margin-x) / 2 - 14px) 140px / 28px 320px repeat-y,
    radial-gradient(circle at 83% 640px, rgba(150, 105, 40, 0.1) 0 26px, transparent 60px) 0 0 / 100% 2200px repeat-y,
    radial-gradient(circle at 23% 1180px, rgba(150, 105, 40, 0.08) 0 18px, transparent 44px) 0 0 / 100% 2200px repeat-y,
    radial-gradient(circle at 64% 1700px, rgba(150, 105, 40, 0.07) 0 34px, transparent 70px) 0 0 / 100% 2200px repeat-y,
    linear-gradient(to bottom, transparent calc(var(--text-rule) - 1px), rgba(120, 150, 190, 0.55) calc(var(--text-rule) - 1px)) 0 0 / 100% var(--text-rule) repeat-y,
    #f1e5c2;
  --font-body: "Gloria Hallelujah", "Comic Sans MS", cursive;
  --font-display: "Gloria Hallelujah", "Comic Sans MS", cursive;
  --font-synthesis: none;
  --font-controls: "Gloria Hallelujah", "Comic Sans MS", cursive;
  --font-controls-size: 1rem;
  /* Boxes drawn by hand: no two corners the same. */
  --radius-sm: 255px 15px 225px 15px / 15px 225px 15px 255px;
  --radius-md: 255px 15px 225px 15px / 15px 225px 15px 255px;
  --radius-pill: 255px 25px 225px 25px / 25px 225px 25px 255px;
  --card-turn-a: -0.6deg;
  --card-turn-b: 0.4deg;
  --card-turn-c: 0.8deg;
  --shadow: 0 2px 4px rgba(60, 45, 20, 0.16), 0 12px 26px rgba(60, 45, 20, 0.14);
  --edge-rgb: 34, 51, 111;
  --hairline-rgb: 34, 51, 111;
  --veil-rgb: 34, 51, 111;
  --glow-green-rgb: 168, 50, 42;
  --glow-cyan-rgb: 34, 51, 111;
  --glow-sky-rgb: 34, 51, 111;
  --glow-gold-rgb: 168, 50, 42;
  --glow-mint-rgb: 34, 51, 111;
  --glow-amber-rgb: 140, 85, 18;
  --sheet-bg: #fbf6e3;
  --sheet-head-bg: #fbf6e3;
  --sheet-shadow: 0 2px 4px rgba(60, 45, 20, 0.16), 0 18px 40px rgba(60, 45, 20, 0.16);
  --cta-base: #fbf6e3;
  --truck-art: url("../images/truck-paper.svg");
  --wheels-art: url("../images/wheels-paper.svg");
  --wheels-filter: none;
  --wind-colour: rgba(70, 66, 62, 0.7);
  /* The road scene is folded paper: origami mountains, trees and truck. */
  --scene-far: url("../images/scene-origami-mountains.svg");
  --roadside-art: url("../images/roadside-origami-pine.svg");
  --roadside-art-far: url("../images/roadside-origami-tree.svg");
  --scene-hill-a: #a8bf94;
  --scene-hill-b: #93ad7f;
  --scene-rock: #b6a98d;
  --scene-road: #22336f;
  --bg: #f1e5c2;
  --bg-mid: #f1e5c2;
  --bg-deep: #e6d6ab;
  --bg-soft: #f7efd6;
  --bg-panel: #fbf6e3;
  --text: #22336f;
  --muted: #3d4b80;
  --line: rgba(34, 51, 111, 0.5);
  --cyan: #1d4f91;
  --green: #a8322a;
  --amber: #8c5512;
  --rose: #9d3b50;
  --violet: #4f5aa8;
  --glow-a: transparent;
  --glow-b: transparent;
  --cursor-a: transparent;
  --cursor-b: transparent;
  --grid-a: transparent;
  --grid-b: transparent;
  --panel-bg: #fbf6e3;
  --panel-bg-strong: #fffaea;
  --panel-glow: transparent;
  --nav-top-bg: transparent;
  --nav-top-edge: transparent;
  --nav-top-shadow: none;
  --nav-top-text: #22336f;
  --nav-scroll-bg: rgba(241, 229, 194, 0.95);
  --nav-link-top: #22336f;
}

body[data-theme="cassie"] {
  /* Cassie: pink and black, cute rather than girly, with Hello Cassie, a silver tabby drawn after
     the owner's own cat, peeking up from the corner of the screen, asleep on the footer and
     driving the van. Round Fredoka headings, pill buttons, a faint pattern of hearts and dots.
     Its floating hearts, the cat's eyes and the rest of its life are in assets/js/theme-life.js. */
  /* The site's own body font, written out because Professional ships Inter for itself. */
  --font-body: Inter, "Open Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --page-bg:
    radial-gradient(ellipse 110% 34rem at 50% -8rem, rgba(255, 90, 168, 0.2), transparent 70%) no-repeat,
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><path d='M30 34 C30 30.5 34.5 29.6 35.6 32.6 C36.7 29.6 41.2 30.5 41.2 34 C41.2 37.4 35.6 40.8 35.6 40.8 C35.6 40.8 30 37.4 30 34 Z' fill='%23ff5aa8' fill-opacity='.09'/><circle cx='92' cy='88' r='2.2' fill='%23ff9cc9' fill-opacity='.1'/><circle cx='80' cy='22' r='1.4' fill='%23ff9cc9' fill-opacity='.1'/><circle cx='18' cy='96' r='1.6' fill='%23ff9cc9' fill-opacity='.09'/></svg>") 0 0 / 120px 120px,
    linear-gradient(180deg, #120a0f 0, #0d0a0c 50%, #080607 100%);
  --font-display: "Fredoka", Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-controls: "Fredoka", Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --eyebrow-font: "Fredoka", Inter, system-ui, sans-serif;
  /* Fredoka ships one weight here; everything bold takes it as it is rather than a fake bold. */
  --font-synthesis: style small-caps;
  --font-controls-size: 0.95rem;
  --label-size: 0.98rem;
  --eyebrow-tracking: 0.18em;
  --button-primary-fill: #ff5aa8;
  --button-primary-ink: #1d1218;
  --headline-glow: 0 0 26px rgba(255, 90, 168, 0.32);
  --radius-sm: 14px;
  --radius-md: 22px;
  --radius-pill: 999px;
  --button-radius: 999px;
  --shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
  --edge-rgb: 255, 122, 182;
  --hairline-rgb: 255, 122, 182;
  --veil-rgb: 255, 214, 234;
  --glow-green-rgb: 255, 90, 168;
  --glow-cyan-rgb: 255, 156, 201;
  --glow-sky-rgb: 255, 156, 201;
  --glow-gold-rgb: 246, 178, 74;
  --glow-mint-rgb: 255, 90, 168;
  --glow-amber-rgb: 246, 178, 74;
  --sheet-bg: linear-gradient(180deg, rgba(34, 20, 28, 0.97), rgba(16, 10, 13, 0.98));
  --sheet-head-bg: rgba(29, 17, 24, 0.94);
  --sheet-shadow: 0 30px 100px rgba(0, 0, 0, 0.55);
  --cta-base: rgba(29, 17, 24, 0.86);
  --truck-art: url("../images/truck-cassie.svg");
  --wheels-art: url("../images/wheels-cassie.svg");
  --wheels-filter: none;
  --wind-colour: rgba(255, 182, 213, 0.6);
  /* The road runs past candy pink hills and cherry blossoms, and the rock is a ball of yarn. */
  --scene-far: url("../images/scene-cassie-hills.svg");
  --roadside-art: url("../images/roadside-cassie-blossom.svg");
  --roadside-art-far: url("../images/roadside-cassie-tall.svg");
  --scene-hill-a: #5a1d3f;
  --scene-hill-b: #4a1734;
  --scene-rock: url("../images/rock-cassie.svg") center / contain no-repeat;
  --scene-road: #ff5aa8;
  --bg: #0d0a0c;
  --bg-mid: #110b0f;
  --bg-deep: #080607;
  --bg-soft: #1a1016;
  --bg-panel: #1d1118;
  --text: #fff0f6;
  --muted: #e2b8cb;
  --line: rgba(255, 122, 182, 0.22);
  --cyan: #ff9cc9;
  --green: #ff5aa8;
  --amber: #f6b24a;
  --rose: #ff3d6e;
  --violet: #ffb8d8;
  --glow-a: rgba(255, 90, 168, 0.1);
  --glow-b: rgba(255, 182, 213, 0.06);
  --cursor-a: rgba(255, 90, 168, 0.14);
  --cursor-b: rgba(255, 182, 213, 0.08);
  --grid-a: transparent;
  --grid-b: transparent;
  --panel-bg: rgba(29, 17, 24, 0.88);
  --panel-bg-strong: rgba(34, 20, 28, 0.95);
  --panel-glow: rgba(255, 90, 168, 0.05);
  --nav-top-bg: transparent;
  --nav-top-edge: rgba(255, 122, 182, 0.2);
  --nav-top-shadow: none;
  --nav-top-text: #fff0f6;
  --nav-link-top: rgba(255, 240, 246, 0.82);
  --nav-scroll-bg: rgba(13, 10, 12, 0.9);
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: 82px;
}

body.portfolio-v2 {
  /* animations.css draws the truck scene's mountain and hill with these. That file is shared
     byte-for-byte with classic/, which defines them in common.css; root pages do not load
     common.css, so without these the variables are undefined, the box-shadow is dropped as
     invalid, and the scenery silently disappears with no error anywhere.

     Declared here rather than on :root on purpose: --cyan is themed on body[data-theme], and an
     alias declared at :root resolves against the base value, freezing the scenery on one colour
     whatever theme is picked. Declared on the same element as the theme, it follows the theme. */
  --primary-color: var(--cyan);
  --text-on-primary: var(--text);
  margin: 0;
  /* --page-bg lets a theme draw the page itself (Paper's lined sheet). The fallback is the
     glow and gradient every other theme uses. */
  background: var(--page-bg,
    radial-gradient(circle at 12% 8%, var(--glow-a), transparent 28rem),
    radial-gradient(circle at 86% 18%, var(--glow-b), transparent 30rem),
    linear-gradient(180deg, var(--bg) 0%, var(--bg-mid) 46%, var(--bg-deep) 100%));
  color: var(--text);
  font-family: var(--font-body);
  font-synthesis: var(--font-synthesis, weight style small-caps);
  line-height: 1.6;
}

/* Zero specificity on purpose: any page or film rule that sets a heading font still wins. */
:where(h1, h2, h3) {
  font-family: var(--font-display);
}

body.portfolio-v2::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(circle at var(--mouse-x) var(--mouse-y), var(--cursor-a), transparent 18rem),
    radial-gradient(circle at calc(var(--mouse-x) + 10%) calc(var(--mouse-y) + 8%), var(--cursor-b), transparent 16rem);
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity 180ms ease;
}

body[data-cursor="spotlight"]::after {
  opacity: 0.85;
}

body[data-cursor="plasma"]::after {
  opacity: 0.42;
  filter: blur(8px) saturate(1.35);
}

body[data-cursor="pulse"]::after,
body[data-cursor="ink"]::after {
  opacity: 0.28;
}

body.portfolio-v2::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    linear-gradient(var(--grid-a) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-b) 1px, transparent 1px);
  background-size: 54px 54px;
  mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.75), transparent 70%);
  animation: pageGridDrift 28s linear infinite;
}

body.portfolio-v2 main,
.site-header,
.site-footer {
  position: relative;
  z-index: 1;
}

.theme-backdrop {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.cursor-effects {
  position: fixed;
  inset: 0;
  z-index: 45;
  overflow: hidden;
  pointer-events: none;
}

.theme-layer {
  position: absolute;
  inset: -12%;
  opacity: var(--backdrop-opacity);
}

.layer-a {
  background:
    radial-gradient(circle at 18% 24%, var(--glow-a), transparent 20rem),
    radial-gradient(circle at 84% 18%, var(--glow-b), transparent 22rem);
  animation: backdropFloat 24s ease-in-out infinite alternate;
}

.layer-b {
  background:
    linear-gradient(115deg, transparent 0 44%, rgba(255, 255, 255, 0.035) 45% 46%, transparent 47%),
    repeating-linear-gradient(90deg, transparent 0 80px, var(--grid-a) 81px 82px);
  animation: backdropSlide 34s linear infinite;
}

.layer-c {
  inset: auto -8% -18%;
  height: 42vh;
  background: radial-gradient(ellipse at center, var(--glow-b), transparent 58%);
  filter: blur(18px);
  animation: horizonPulse 9s ease-in-out infinite;
}

/* Forest: the scenery is the backdrop, fixed behind the page so the reader stays among the trees.
   Back to front: light shafts (a), the far tree line in mist (b), the middle tree line (c), the
   river (c::before) and the nearest trees on its bank (c::after). Each tree layer is wider than
   the screen by more than one tile, so theme-life.js can slide it sideways as the page scrolls.
   Not on film pages, where the film is the scenery. */
body[data-theme="forest"]:not(.film) .layer-a {
  inset: 0;
  opacity: 1;
  transform-origin: 0 0;
  background:
    linear-gradient(112deg, transparent 18%, rgba(236, 226, 170, 0.045) 21%, transparent 25%),
    linear-gradient(118deg, transparent 30%, rgba(236, 226, 170, 0.035) 34%, transparent 39%),
    linear-gradient(108deg, transparent 44%, rgba(236, 226, 170, 0.025) 46%, transparent 49%);
  animation: forestShafts 16s ease-in-out infinite alternate;
}

/* Every fourteen seconds a wide beam of moonlight drifts across the whole forest. */
body[data-theme="forest"]:not(.film) .layer-a::after {
  content: "";
  position: absolute;
  inset: -10% -40%;
  background: linear-gradient(104deg, transparent 36%, rgba(220, 236, 200, 0.08) 45%, rgba(240, 232, 190, 0.14) 50%, rgba(220, 236, 200, 0.08) 55%, transparent 64%);
  transform: translateX(-60%);
  animation: forestMoonbeam 14s ease-in-out 2s infinite;
}

/* The play state is not inherited, so the beam is paused with its layer at Calm. */
body[data-motion="calm"][data-theme="forest"] .layer-a::after {
  animation-play-state: paused;
}

body[data-theme="forest"]:not(.film) .layer-b,
body[data-theme="forest"]:not(.film) .layer-c {
  inset: auto auto 0 0;
  width: calc(100% + 1400px);
  opacity: 1;
  filter: none;
  animation: none;
}

body[data-theme="forest"]:not(.film) .layer-b {
  height: 340px;
  background:
    url("../images/forest-trees-far.svg") left bottom 62px / 900px 230px repeat-x,
    linear-gradient(to top, rgba(130, 175, 155, 0.09), transparent 75%);
}

body[data-theme="forest"]:not(.film) .layer-c {
  height: 300px;
  background:
    linear-gradient(to top, rgba(130, 175, 155, 0.06), transparent 45%),
    url("../images/forest-trees-mid.svg") left bottom 34px / 1100px 250px repeat-x;
}

body[data-theme="forest"]:not(.film) .layer-c::before,
body[data-theme="forest"]:not(.film) .layer-c::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
}

/* The river: dark water with glints that drift downstream. */
body[data-theme="forest"]:not(.film) .layer-c::before {
  bottom: 16px;
  height: 24px;
  background:
    linear-gradient(90deg, transparent 60px, rgba(160, 220, 210, 0.16) 60px 74px, transparent 74px) 0 7px / 150px 2px repeat-x,
    linear-gradient(90deg, transparent 25px, rgba(160, 220, 210, 0.1) 25px 33px, transparent 33px) 0 15px / 110px 2px repeat-x,
    linear-gradient(#123430, #0a211e);
  animation: forestRiver 9s linear infinite;
}

body[data-theme="forest"]:not(.film) .layer-c::after {
  bottom: 0;
  height: 260px;
  transform: translateX(var(--forest-near, 0px));
  background: url("../images/forest-trees-near.svg") left bottom -6px / 1300px 260px repeat-x;
}

/* Motion Off hides every backdrop because backdrops move. Forest's is its scenery, so it stays,
   standing still. */
body[data-motion="off"][data-theme="forest"]:not(.film) .theme-backdrop {
  display: block !important;
}

/* Space: the sky itself is the page background. The backdrop adds only a faint haze that
   breathes, and theme-life.js puts its drifting stars on a canvas inside it. */
body[data-theme="space"] .layer-a {
  inset: 0;
  opacity: 1;
  background: radial-gradient(ellipse at 78% 70%, rgba(120, 140, 200, 0.06), transparent 55%);
  animation: spaceHaze 22s ease-in-out infinite alternate;
}

body[data-theme="space"] .layer-b,
body[data-theme="space"] .layer-c {
  background: none;
  filter: none;
  animation: none;
}

/* Cyber: the aura at the top of the screen breathes (a), the faint lines of an old monitor cross
   the screen (b), and a refresh beam rolls down it every ten seconds (c). theme-life.js adds the
   falling characters and the terminal log in the corner. */
body[data-theme="cyber"] .layer-a {
  inset: 0;
  opacity: 1;
  background:
    radial-gradient(ellipse 54rem 26rem at 25% -4rem, rgba(214, 72, 186, 0.22), transparent),
    radial-gradient(ellipse 60rem 30rem at 76% -6rem, rgba(124, 84, 232, 0.26), transparent);
  animation: cyberAura 7s ease-in-out infinite alternate;
}

body[data-theme="cyber"] .layer-c {
  inset: 0 0 auto;
  height: 36vh;
  opacity: 1;
  filter: none;
  background: linear-gradient(to bottom, transparent, rgba(189, 147, 249, 0.09) 40%, rgba(255, 121, 198, 0.17) 50%, rgba(189, 147, 249, 0.09) 60%, transparent);
  transform: translateY(-40vh);
  animation: cyberBeam 10s linear infinite;
}

body[data-theme="cyber"] .layer-b {
  inset: 0;
  opacity: 1;
  background: repeating-linear-gradient(180deg, rgba(220, 200, 255, 0.025) 0 1px, transparent 1px 3px);
  -webkit-mask-image: linear-gradient(to bottom, #000, rgba(0, 0, 0, 0.45) 60%);
  mask-image: linear-gradient(to bottom, #000, rgba(0, 0, 0, 0.45) 60%);
  animation: none;
}

/* Ember: heat breathing along the bottom of the screen (a), and every so often a band of warm
   light crossing it (b). theme-life.js adds the rising embers. */
body[data-theme="ember"] .layer-a {
  inset: 0;
  opacity: 1;
  background:
    radial-gradient(ellipse 90% 34vh at 50% 108%, rgba(255, 110, 30, 0.26), transparent 70%),
    radial-gradient(ellipse 60% 22vh at 20% 104%, rgba(255, 170, 60, 0.14), transparent 70%);
  animation: emberHeat 5s ease-in-out infinite alternate;
}

body[data-theme="ember"] .layer-b {
  inset: -20%;
  opacity: 1;
  background: linear-gradient(105deg, transparent 40%, rgba(255, 190, 110, 0.07) 47%, rgba(255, 210, 140, 0.12) 50%, rgba(255, 190, 110, 0.07) 53%, transparent 60%);
  transform: translateX(-70%);
  animation: lightSweep 12s ease-in-out 2s infinite;
}

body[data-theme="ember"] .layer-c {
  background: none;
  filter: none;
  animation: none;
}

/* Cassie: a pink glow that breathes at the top and bottom of the screen (a), and every so often a
   soft pink shimmer crossing it (b). theme-life.js adds the floating hearts, bows and sparkles. */
body[data-theme="cassie"] .layer-a {
  inset: 0;
  opacity: 1;
  background:
    radial-gradient(ellipse 70% 26vh at 50% -4vh, rgba(255, 90, 168, 0.18), transparent 70%),
    radial-gradient(ellipse 80% 30vh at 50% 106%, rgba(255, 90, 168, 0.16), transparent 70%);
  animation: glowBreathe 5s ease-in-out infinite alternate;
}

body[data-theme="cassie"] .layer-b {
  inset: -20%;
  opacity: 1;
  background: linear-gradient(110deg, transparent 40%, rgba(255, 170, 210, 0.07) 47%, rgba(255, 210, 232, 0.13) 50%, rgba(255, 170, 210, 0.07) 53%, transparent 60%);
  transform: translateX(-70%);
  animation: lightSweep 11s ease-in-out 1s infinite;
}

body[data-theme="cassie"] .layer-c {
  background: none;
  filter: none;
  animation: none;
}

/* Light mode: a sky glow that breathes at the top of the screen (a), and every so often a beam of
   sunlight crossing it (b). theme-life.js adds the drifting dust. */
body[data-theme="light"] .layer-a {
  inset: 0;
  opacity: 1;
  background:
    radial-gradient(ellipse 60% 28vh at 80% -4vh, rgba(255, 210, 130, 0.3), transparent 70%),
    radial-gradient(ellipse 70% 30vh at 20% -6vh, rgba(140, 190, 255, 0.28), transparent 70%);
  animation: glowBreathe 6s ease-in-out infinite alternate;
}

body[data-theme="light"] .layer-b {
  inset: -20%;
  opacity: 1;
  background:
    linear-gradient(118deg, transparent 38%, rgba(255, 236, 190, 0.32) 46%, rgba(255, 246, 220, 0.5) 50%, rgba(255, 236, 190, 0.32) 54%, transparent 62%),
    linear-gradient(118deg, transparent 56%, rgba(255, 236, 190, 0.18) 60%, transparent 64%);
  transform: translateX(-70%);
  animation: lightSweep 10s ease-in-out 1s infinite;
}

body[data-theme="light"] .layer-c {
  background: none;
  filter: none;
  animation: none;
}

/* Paper: edges gone brown with age, a faint crinkle of light that crosses the sheet now and
   then, and the creases that show while the sheet is crumpled (theme-life.js adds is-crunching). */
body[data-theme="paper"] .layer-a {
  inset: 0;
  opacity: 1;
  background: radial-gradient(ellipse at 50% 45%, transparent 55%, rgba(140, 95, 30, 0.2) 100%);
  animation: none;
}

body[data-theme="paper"] .layer-b {
  inset: -20%;
  opacity: 1;
  mix-blend-mode: soft-light;
  background: linear-gradient(105deg, transparent 42%, rgba(255, 250, 230, 0.55) 48%, rgba(120, 90, 40, 0.18) 52%, transparent 58%);
  transform: translateX(-70%);
  animation: paperSheen 16s ease-in-out 3s infinite;
}

body[data-theme="paper"] .layer-c {
  inset: 0;
  height: auto;
  opacity: 0;
  filter: none;
  mix-blend-mode: multiply;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='900' height='900'><filter id='c'><feTurbulence type='turbulence' baseFrequency='.008' numOctaves='5' seed='7'/><feDiffuseLighting lighting-color='%23f5ecd2' surfaceScale='9'><feDistantLight azimuth='225' elevation='38'/></feDiffuseLighting></filter><rect width='100%' height='100%' filter='url(%23c)'/></svg>") center / cover;
  animation: none;
}

body[data-theme="paper"].is-crunching .layer-c {
  animation: paperCreases 1400ms ease-out;
}

body[data-theme="paper"].is-crunching main,
body[data-theme="paper"].is-crunching .site-footer {
  transform-origin: 50% 30vh;
  animation: paperCrunch 900ms cubic-bezier(0.3, 0.7, 0.2, 1);
}

/* A theme may turn its cards a little, as if laid down by hand. No turn unless it says so. */
.kinetic-card:nth-child(3n + 1),
.proof-lane:nth-child(3n + 1) {
  rotate: var(--card-turn-a, none);
}

.kinetic-card:nth-child(3n + 2),
.proof-lane:nth-child(3n + 2) {
  rotate: var(--card-turn-b, none);
}

.kinetic-card:nth-child(3n),
.proof-lane:nth-child(3n) {
  rotate: var(--card-turn-c, none);
}

/* Paper's life. theme-life.js adds these elements and classes while Paper is the theme and
   removes them when it is not. theme-life-still is on body when Motion is not Full: everything
   is shown already drawn and nothing animates. */
.paper-word {
  display: inline-block;
  clip-path: inset(-30% 100% -30% -4%);
}

.is-written .paper-word {
  clip-path: inset(-30% -6% -30% -4%);
  transition: clip-path var(--write-time) linear var(--write-delay);
}

/* A highlighter swept across each word of a section heading once it is written. The padding
   and the matching negative margin let neighbouring words' strokes meet across the space. */
.paper-marked .paper-word {
  margin: 0 -0.14em;
  padding: 0 0.14em;
  background: linear-gradient(transparent 50%, rgba(255, 220, 60, 0.6) 50% 88%, transparent 88%) left / 0% 100% no-repeat;
}

.paper-marked.is-written .paper-word {
  background-size: 100% 100%;
  transition: clip-path var(--write-time) linear var(--write-delay), background-size 500ms ease-out calc(var(--write-delay) + var(--write-time) + 150ms);
}

.paper-link,
.paper-circled {
  position: relative;
}

.paper-scribble {
  position: absolute;
  left: -4px;
  bottom: -2px;
  width: calc(100% + 8px);
  height: 10px;
  overflow: visible;
  pointer-events: none;
}

.paper-scribble path,
.paper-ring path {
  fill: none;
  stroke: var(--green);
  stroke-linecap: round;
}

.paper-scribble path {
  stroke-width: 2.2;
  transition: stroke-dashoffset 380ms ease-out;
}

.paper-link:hover .paper-scribble path,
.paper-link:focus-visible .paper-scribble path,
.paper-link.active .paper-scribble path {
  stroke-dashoffset: 0 !important;
}

.paper-ring {
  position: absolute;
  top: -9px;
  left: -14px;
  width: calc(100% + 28px);
  height: calc(100% + 17px);
  overflow: visible;
  pointer-events: none;
}

.paper-ring path {
  stroke-width: 2.4;
  transition: stroke-dashoffset 500ms ease-in-out;
}

.paper-circled.is-active .paper-ring path {
  stroke-dashoffset: 0 !important;
}

.paper-tape {
  position: absolute;
  top: -12px;
  z-index: 3;
  width: 96px;
  height: 28px;
  background: rgba(250, 244, 222, 0.62);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
  rotate: var(--tape-turn);
  pointer-events: none;
}

.paper-tape-left {
  left: -24px;
}

.paper-tape-right {
  right: -24px;
}

.paper-margin {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  pointer-events: none;
}

.paper-doodle {
  position: absolute;
  width: var(--doodle-size);
  height: var(--doodle-size);
  overflow: visible;
  rotate: var(--doodle-turn);
}

.paper-doodle path {
  fill: none;
  stroke: rgba(70, 66, 62, 0.72);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke-dashoffset 1800ms ease-in-out;
}

.paper-doodle.is-written path {
  stroke-dashoffset: 0 !important;
}

.theme-life-still .paper-word {
  clip-path: none;
  transition: none;
}

.theme-life-still .paper-marked .paper-word {
  background-size: 100% 100%;
}

.theme-life-still .paper-doodle path {
  stroke-dashoffset: 0 !important;
  transition: none;
}

/* Parts the lives in theme-life.js share: a box for marks placed inside an element, the canvas
   in the backdrop, and the layer click bursts are drawn on. */
.life-anchor {
  position: relative;
}

.space-sky,
.forest-flies,
.cyber-rain,
.ember-sparks,
.cassie-floats,
.light-dust {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.life-bursts {
  position: fixed;
  inset: 0;
  z-index: 46;
  pointer-events: none;
}

.life-burst {
  position: absolute;
  width: 0;
  height: 0;
}

.life-burst i {
  position: absolute;
  border-radius: 50%;
}

/* Space's life. */

/* The starlight on a card's edge: a light that follows the pointer, masked to a 1px ring. */
.space-glint {
  position: absolute;
  inset: 0;
  z-index: 2;
  padding: 1px;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(240px circle at var(--light-x, 50%) var(--light-y, 50%), #f2f6ff, rgba(214, 228, 255, 0.45) 40%, transparent 80%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  transition: opacity 280ms ease;
}

.kinetic-card:hover > .space-glint,
.proof-lane:hover > .space-glint {
  opacity: 1;
}

.space-rise {
  opacity: 0;
  transform: scaleX(1.05) translateY(10px);
  transition: opacity 900ms ease, transform 1200ms cubic-bezier(0.2, 0.7, 0.2, 1);
}

.space-rise.is-seen {
  opacity: 1;
  transform: none;
}

.space-burst i {
  left: -1.5px;
  top: -1.5px;
  width: 3px;
  height: 3px;
  background: #eef3ff;
  box-shadow: 0 0 6px rgba(188, 208, 255, 0.9);
  animation: spaceSpark 700ms cubic-bezier(0.2, 0.8, 0.3, 1) forwards;
}

.space-orbit {
  position: absolute;
  top: 0;
  left: 0;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--amber);
  box-shadow: 0 0 6px rgba(var(--glow-amber-rgb), 0.8);
  pointer-events: none;
  opacity: 0;
  offset-path: inset(-5px round 7px);
  offset-rotate: 0deg;
  animation: spaceOrbit 5s linear infinite;
  transition: opacity 300ms ease;
}

.filter-button.is-active > .space-orbit {
  opacity: 1;
}

.theme-life-still .space-glint {
  display: none;
}

.theme-life-still .space-rise {
  opacity: 1;
  transform: none;
  transition: none;
}

.theme-life-still .space-orbit {
  offset-distance: 8%;
  animation: none;
}

/* Forest's life. */
.forest-leaf {
  position: absolute;
  top: -24px;
  width: var(--leaf-size, 13px);
  height: calc(var(--leaf-size, 13px) * 0.7);
  border-radius: 0 100% 0 100%;
  background: var(--leaf-paint, linear-gradient(135deg, #8a9a4a, #5e6e2c));
  opacity: var(--leaf-light, 0.55);
  animation: forestLeafFall var(--leaf-fall, 12s) linear forwards;
}

/* A warm light inside the card, nearest the pointer, as if a firefly came close. */
.forest-glow {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(260px circle at var(--light-x, 50%) var(--light-y, 50%), rgba(242, 210, 122, 0.1), transparent 70%);
  transition: opacity 400ms ease;
}

.kinetic-card:hover > .forest-glow,
.proof-lane:hover > .forest-glow {
  opacity: 1;
}

.forest-rise {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 1200ms ease, transform 1400ms cubic-bezier(0.2, 0.7, 0.2, 1);
}

.forest-rise.is-seen {
  opacity: 1;
  transform: none;
}

.forest-burst i {
  left: -2px;
  top: -2px;
  width: 4px;
  height: 4px;
  background: #fff1b8;
  box-shadow: 0 0 8px rgba(242, 210, 122, 0.95), 0 0 16px rgba(242, 210, 122, 0.5);
  animation: forestSpeck 1500ms ease-out forwards;
}

.forest-firefly {
  position: absolute;
  top: -4px;
  right: -3px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #fff1b8;
  box-shadow: 0 0 8px rgba(242, 210, 122, 0.95), 0 0 18px rgba(242, 210, 122, 0.45);
  pointer-events: none;
  opacity: 0;
  transition: opacity 600ms ease;
}

.filter-button.is-active > .forest-firefly {
  opacity: 1;
  animation: forestBlink 3.2s ease-in-out infinite;
}

.theme-life-still .forest-glow {
  display: none;
}

.theme-life-still .forest-rise {
  opacity: 1;
  transform: none;
  transition: none;
}

.theme-life-still .forest-firefly {
  animation: none;
}

/* Cyber's life. */

/* A heading's words, clipped to what has been typed so far. */
.cyber-word {
  display: inline-block;
  clip-path: inset(-25% calc(100% - var(--typed, 0px)) -25% -4px);
}

.cyber-word.is-typed {
  clip-path: none;
}

.cyber-caret {
  position: absolute;
  width: 0.55em;
  background: #ff79c6;
  box-shadow: 0 0 12px rgba(255, 121, 198, 0.7);
  pointer-events: none;
}

.is-typed > .cyber-caret {
  animation: cyberBlink 1060ms step-end infinite;
}

/* A section heading's cursor blinks a few times once it is typed and goes; the headline's stays. */
main h2.is-typed > .cyber-caret {
  animation-iteration-count: 3;
  animation-fill-mode: forwards;
}

/* A terminal window behind the page's own windows, so where cards cover it, it reads as one
   window behind another. */
.cyber-log {
  position: absolute;
  right: 28px;
  bottom: 24px;
  width: 50ch;
  padding: 34px 14px 10px;
  border: 1px solid rgba(189, 147, 249, 0.4);
  border-radius: 6px;
  background: rgba(11, 8, 18, 0.8);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11.5px;
  line-height: 1.55;
  color: #b3a8c9;
  opacity: 0.42;
  white-space: pre;
  overflow: hidden;
}

.cyber-log::before {
  content: "abdullah@kali: ~";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  padding: 5px 14px;
  border-bottom: 1px solid rgba(189, 147, 249, 0.3);
  background: rgba(189, 147, 249, 0.1);
  color: #ece6f7;
  text-align: center;
}

.cyber-prompt {
  color: #bd93f9;
}

.cyber-info {
  color: #8be9fd;
}

.cyber-alert {
  color: #ff79c6;
}

.cyber-good {
  color: #50fa7b;
}

.cyber-line:last-child::after {
  content: "";
  display: inline-block;
  width: 0.6em;
  height: 1.1em;
  margin-left: 2px;
  vertical-align: -0.2em;
  background: #ff79c6;
  animation: cyberBlink 1060ms step-end infinite;
}

/* The card under the pointer: a magenta outline, and [+] in the corner. */
.cyber-frame {
  position: absolute;
  inset: 0;
  z-index: 2;
  border: 1px solid #ff79c6;
  border-radius: inherit;
  box-shadow: 0 0 18px rgba(255, 121, 198, 0.18);
  pointer-events: none;
  opacity: 0;
  transition: opacity 160ms ease;
}

.cyber-frame::after {
  content: "[+]";
  position: absolute;
  top: 6px;
  right: 9px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px;
  line-height: 1;
  color: #50fa7b;
  text-shadow: 0 0 8px rgba(80, 250, 123, 0.55);
}

.kinetic-card:hover > .cyber-frame,
.proof-lane:hover > .cyber-frame {
  opacity: 1;
}

.cyber-burst i {
  left: 0;
  top: 0;
  border-radius: 0;
  transform: translate(-50%, -50%);
  animation: cyberBlink 400ms step-end 3 forwards;
}

.cyber-burst i::before {
  content: "[*]";
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 13px;
  font-style: normal;
  color: #ff79c6;
  text-shadow: 0 0 8px rgba(255, 121, 198, 0.8);
  white-space: pre;
}

/* A project filter as a checkbox: [ ] beside each, [x] beside the chosen one. */
.filter-button:has(> .cyber-check) {
  padding-left: calc(12px + 4ch);
}

.cyber-check {
  position: absolute;
  top: 50%;
  left: 11px;
  transform: translateY(-50%);
  color: var(--muted);
  pointer-events: none;
}

.cyber-check::before {
  content: "[ ]";
  white-space: pre;
}

.filter-button.is-active > .cyber-check {
  color: #ff79c6;
}

.filter-button.is-active > .cyber-check::before {
  content: "[x]";
}

.theme-life-still .cyber-word {
  clip-path: none;
}

.theme-life-still .cyber-caret {
  display: none;
}

.theme-life-still .cyber-line:last-child::after {
  animation: none;
}

/* Ember's life. */

/* A heading arrives hot: it rises into place while a flare blooms behind it and dies down. */
.ember-kindle {
  /* Its own stacking context, so the flare behind the words stays in front of the panel. */
  isolation: isolate;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 900ms ease, transform 1200ms cubic-bezier(0.2, 0.7, 0.2, 1);
}

.ember-kindle.is-seen {
  opacity: 1;
  transform: none;
}

.ember-kindle::after {
  content: "";
  position: absolute;
  inset: -30% -6%;
  z-index: -1;
  background: radial-gradient(ellipse at 30% 55%, rgba(255, 150, 50, 0.32), rgba(255, 90, 30, 0.12) 45%, transparent 72%);
  opacity: 0;
  pointer-events: none;
}

.ember-kindle.is-seen::after {
  animation: emberFlare 2600ms ease-out forwards;
}

/* The card under the pointer warms from below, brightest under the pointer. */
.ember-heat {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(320px 220px at var(--light-x, 50%) 100%, rgba(255, 120, 40, 0.2), transparent 75%);
  box-shadow: inset 0 -2px 0 rgba(255, 150, 50, 0.55);
  transition: opacity 300ms ease;
}

.kinetic-card:hover > .ember-heat,
.proof-lane:hover > .ember-heat {
  opacity: 1;
}

.ember-burst i {
  left: -2px;
  top: -2px;
  width: 4px;
  height: 4px;
  background: #ffd27a;
  box-shadow: 0 0 6px rgba(255, 160, 50, 0.95), 0 0 14px rgba(255, 90, 30, 0.6);
  animation: emberSpark 900ms cubic-bezier(0.2, 0.8, 0.3, 1) forwards;
}

.ember-coal {
  position: absolute;
  top: -4px;
  right: -3px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #ffb627;
  box-shadow: 0 0 8px rgba(255, 140, 40, 0.95), 0 0 18px rgba(255, 90, 30, 0.5);
  pointer-events: none;
  opacity: 0;
  transition: opacity 500ms ease;
}

.filter-button.is-active > .ember-coal {
  opacity: 1;
  animation: softPulse 2.4s ease-in-out infinite;
}

.theme-life-still .ember-kindle {
  opacity: 1;
  transform: none;
  transition: none;
}

.theme-life-still .ember-kindle::after,
.theme-life-still .ember-heat {
  display: none;
}

.theme-life-still .ember-coal {
  animation: none;
}

/* Cassie's life. */

/* A heading pops into place with a little bounce. */
.cassie-pop {
  opacity: 0;
  transform: scale(0.92) translateY(10px);
  transform-origin: 0 100%;
  transition: opacity 450ms ease, transform 750ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.cassie-pop.is-seen {
  opacity: 1;
  transform: none;
}

/* The card under the pointer glows pink from the pointer, and a bow pops onto its corner. */
.cassie-glow {
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(280px circle at var(--light-x, 50%) var(--light-y, 50%), rgba(255, 90, 168, 0.12), transparent 70%);
  box-shadow: inset 0 0 0 1px rgba(255, 122, 182, 0.55);
  transition: opacity 260ms ease;
}

.cassie-glow::after {
  content: "";
  position: absolute;
  top: -9px;
  right: 14px;
  width: 30px;
  height: 18px;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 18'><path d='M13 9 L2 2 Q0 9 2 16 Z M17 9 L28 2 Q30 9 28 16 Z' fill='%23ff5aa8' stroke='%231d1218' stroke-width='1.2' stroke-linejoin='round'/><circle cx='15' cy='9' r='3.6' fill='%23ff9cc9' stroke='%231d1218' stroke-width='1.2'/></svg>") center / contain no-repeat;
  transform: scale(0.4) rotate(-12deg);
  transition: transform 380ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.kinetic-card:hover > .cassie-glow,
.proof-lane:hover > .cassie-glow {
  opacity: 1;
}

.kinetic-card:hover > .cassie-glow::after,
.proof-lane:hover > .cassie-glow::after {
  transform: none;
}

.cassie-burst i {
  left: -6px;
  top: -5px;
  width: 12px;
  height: 11px;
  border-radius: 0;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 11'><path d='M6 10.4 C6 10.4 0.4 7 0.4 3.4 C0.4 0.6 4.4 -0.4 6 2.4 C7.6 -0.4 11.6 0.6 11.6 3.4 C11.6 7 6 10.4 6 10.4 Z' fill='%23ff5aa8'/></svg>") center / contain no-repeat;
  animation: cassieHeart 1100ms cubic-bezier(0.2, 0.8, 0.3, 1) forwards;
}

/* The chosen project filter wears a bow. */
.cassie-bow {
  position: absolute;
  top: -10px;
  right: -8px;
  width: 26px;
  height: 16px;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 18'><path d='M13 9 L2 2 Q0 9 2 16 Z M17 9 L28 2 Q30 9 28 16 Z' fill='%23ff5aa8' stroke='%231d1218' stroke-width='1.2' stroke-linejoin='round'/><circle cx='15' cy='9' r='3.6' fill='%23ff9cc9' stroke='%231d1218' stroke-width='1.2'/></svg>") center / contain no-repeat;
  pointer-events: none;
  opacity: 0;
  transform: scale(0.4) rotate(14deg);
  transition: opacity 250ms ease, transform 420ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.filter-button.is-active > .cassie-bow {
  opacity: 1;
  transform: rotate(14deg);
}

/* Hello Cassie peeking up over the bottom edge of the screen. The pupils sit over the drawing's
   empty eyes (see cassie-cat-peek.svg for where they are) and are moved toward the pointer;
   the lids close over the eyes for a blink. */
.cassie-peek {
  position: fixed;
  right: 4px;
  bottom: 0;
  z-index: 44;
  width: 88px;
  height: 69px;
  background: url("../images/cassie-cat-peek.svg") center bottom / contain no-repeat;
  pointer-events: none;
  transform: translateY(100%);
  transition: transform 800ms cubic-bezier(0.34, 1.4, 0.64, 1);
}

.cassie-peek.is-up {
  transform: translateY(6px);
}

/* The cat can be petted: it rises to meet the pointer, and a click closes its eyes and makes it
   wiggle with a purr. */
.cassie-peek.is-up {
  pointer-events: auto;
  cursor: pointer;
}

.cassie-peek.is-up:hover {
  transform: translateY(-4px);
}

.cassie-peek.is-petted .cassie-lid {
  transform: scaleY(1);
  animation: none;
}

.cassie-peek.is-petted {
  animation: cassieWiggle 450ms ease-in-out 2;
}

.cassie-purr {
  position: absolute;
  left: 30%;
  top: -6px;
  font-family: "Fredoka", Inter, system-ui, sans-serif;
  font-weight: 600;
  font-size: 13px;
  color: #ff9cc9;
  pointer-events: none;
  animation: cassieSleepZ 1400ms ease-out forwards;
}

/* The right paw, a piece of its own so it can lift and bat at a pointer that comes close. */
.cassie-paw {
  position: absolute;
  left: 58.6%;
  top: 87.3%;
  width: 22.9%;
  height: 16.4%;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 18'><ellipse cx='16' cy='9' rx='15.5' ry='8.5' fill='%23f7f7f9' stroke='%23c8cad3'/><path d='M11 4 L11 8 M16 3.5 L16 8 M21 4 L21 8' stroke='%23c8cad3' stroke-width='1.2' stroke-linecap='round'/></svg>") center / 100% 100% no-repeat;
  transform-origin: 50% 100%;
}

.cassie-peek.is-batting .cassie-paw {
  animation: cassieBat 480ms ease-in-out;
}

/* The sleeping cat wakes when the pointer comes to it, and stretches when clicked. */
.cassie-sleep-eye {
  position: absolute;
  top: 65.9%;
  width: 5%;
  height: 9.8%;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 55%, #16161a 0 34%, #e6a03a 36%);
  box-shadow: 0 0 0 1px #2b2d33;
  opacity: 0;
  transition: opacity 200ms ease;
}

.cassie-sleep-eye.is-left {
  left: 19.4%;
}

.cassie-sleep-eye.is-right {
  left: 29.4%;
}

.cassie-sleep.is-awake .cassie-sleep-eye {
  opacity: 1;
}

.cassie-sleep.is-awake::after {
  animation: none;
  opacity: 0;
}

.cassie-sleep.is-stretching {
  animation: cassieStretch 800ms ease-in-out;
}

.cassie-pupil {
  position: absolute;
  width: 6%;
  height: 11.8%;
  border-radius: 50%;
  background: #16161a;
  transform: translate(var(--look-x, 0px), var(--look-y, 0px));
  transition: transform 160ms ease-out;
}

.cassie-pupil::after {
  content: "";
  position: absolute;
  top: 18%;
  left: 52%;
  width: 40%;
  height: 26%;
  border-radius: 50%;
  background: #ffffff;
}

.cassie-pupil.is-left {
  left: 34.1%;
  top: 52.3%;
}

.cassie-pupil.is-right {
  left: 59.9%;
  top: 52.3%;
}

.cassie-lid {
  position: absolute;
  top: 48.2%;
  width: 15.6%;
  height: 20.2%;
  border-radius: 50%;
  background: #d1d5dc;
  border-bottom: 1.5px solid #2b2d33;
  transform: scaleY(0);
  transform-origin: 50% 0;
}

.cassie-lid.is-left {
  left: 29.3%;
}

.cassie-lid.is-right {
  left: 55.1%;
}

.cassie-peek.is-blinking .cassie-lid {
  animation: cassieBlink 240ms ease-in-out;
}

/* Hello Cassie asleep in the corner of the footer, breathing, with a z now and then. */
.cassie-sleep {
  position: absolute;
  left: 0;
  bottom: 4px;
  width: 132px;
  height: 68px;
  background: url("../images/cassie-cat-sleep.svg") center bottom / contain no-repeat;
  /* The pointer wakes it (see .is-awake). */
  cursor: pointer;
  transform-origin: 50% 100%;
  animation: cassieBreathe 3.6s ease-in-out infinite;
}

.cassie-sleep::after {
  content: "z";
  position: absolute;
  left: 30px;
  top: 6px;
  font-family: "Fredoka", Inter, system-ui, sans-serif;
  font-weight: 600;
  font-size: 15px;
  color: #ff9cc9;
  opacity: 0;
  animation: cassieSleepZ 3.6s ease-out infinite;
}

/* Professional's life: reactions only; its backdrop is the moving one every theme started with. */

.pro-settle {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 700ms ease, transform 900ms cubic-bezier(0.2, 0.7, 0.2, 1);
}

.pro-settle.is-seen {
  opacity: 1;
  transform: none;
}

/* A soft blue light inside the card under the pointer, and a brighter edge. */
.pro-light {
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(260px circle at var(--light-x, 50%) var(--light-y, 50%), rgba(120, 183, 255, 0.12), transparent 70%);
  box-shadow: inset 0 0 0 1px rgba(120, 183, 255, 0.35);
  transition: opacity 260ms ease;
}

.kinetic-card:hover > .pro-light,
.proof-lane:hover > .pro-light {
  opacity: 1;
}

.pro-burst i {
  left: -1.5px;
  top: -1.5px;
  width: 3px;
  height: 3px;
  background: #d7e8ff;
  box-shadow: 0 0 6px rgba(120, 183, 255, 0.9);
  animation: spaceSpark 750ms cubic-bezier(0.2, 0.8, 0.3, 1) forwards;
}

.pro-mark {
  position: absolute;
  top: -4px;
  right: -3px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #78b7ff;
  box-shadow: 0 0 8px rgba(120, 183, 255, 0.8);
  pointer-events: none;
  opacity: 0;
  transition: opacity 300ms ease;
}

.filter-button.is-active > .pro-mark {
  opacity: 1;
}

.theme-life-still .pro-settle {
  opacity: 1;
  transform: none;
  transition: none;
}

.theme-life-still .pro-light {
  transition: none;
}

/* Light mode's life. */

/* A heading slides up into place. */
.light-rise {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 700ms ease, transform 900ms cubic-bezier(0.2, 0.7, 0.2, 1);
}

.light-rise.is-seen {
  opacity: 1;
  transform: none;
}

/* The card under the pointer catches the light: a sheen that follows the pointer, and a deeper
   shadow under the card. */
.light-sheen {
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(300px circle at var(--light-x, 50%) var(--light-y, 50%), rgba(255, 255, 255, 0.55), transparent 70%);
  box-shadow: 0 18px 40px rgba(20, 30, 50, 0.14);
  transition: opacity 260ms ease;
}

.kinetic-card:hover > .light-sheen,
.proof-lane:hover > .light-sheen {
  opacity: 1;
}

/* A click sends out a soft ring, like a drop on water. */
.light-ripple i {
  left: -20px;
  top: -20px;
  width: 40px;
  height: 40px;
  border: 2px solid rgba(29, 95, 209, 0.45);
  animation: lightRipple 700ms ease-out forwards;
}

/* A small sun on the chosen project filter. */
.light-sun {
  position: absolute;
  top: -5px;
  right: -4px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #f5b335;
  box-shadow: 0 0 0 3px rgba(245, 179, 53, 0.22), 0 0 10px rgba(245, 179, 53, 0.7);
  pointer-events: none;
  opacity: 0;
  transition: opacity 300ms ease;
}

.filter-button.is-active > .light-sun {
  opacity: 1;
  animation: softPulse 3s ease-in-out infinite;
}

.theme-life-still .light-rise {
  opacity: 1;
  transform: none;
  transition: none;
}

.theme-life-still .light-sheen {
  transition: none;
}

.theme-life-still .light-sun {
  animation: none;
}

/* Motion Off shows no moving parts, and the peeking cat moves by being there. */
body[data-motion="off"] .cassie-peek {
  display: none;
}

/* The cat sits in the margin beside the 1180px column; below 1364px wide there is no margin
   wide enough, and a cat over the words is worse than no cat. */
@media (max-width: 1363px) {
  .cassie-peek {
    display: none;
  }
}

.theme-life-still .cassie-pop {
  opacity: 1;
  transform: none;
  transition: none;
}

.theme-life-still .cassie-sleep,
.theme-life-still .cassie-sleep::after,
.theme-life-still .cassie-lid,
.theme-life-still .cassie-peek,
.theme-life-still .cassie-paw,
.theme-life-still .cassie-purr {
  animation: none;
}

.theme-life-still .cassie-peek,
.theme-life-still .cassie-pupil,
.theme-life-still .cassie-glow::after,
.theme-life-still .cassie-bow {
  transition: none;
}

body.portfolio-v2 a {
  color: inherit;
}

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 30;
  border-bottom: 1px solid var(--nav-top-edge, rgba(10, 25, 47, 0.22));
  background: var(--nav-top-bg);
  color: var(--nav-top-text);
  box-shadow: var(--nav-top-shadow, 0 10px 28px rgba(0, 0, 0, 0.16));
  transition: background 220ms ease, color 220ms ease, box-shadow 220ms ease, border-color 220ms ease;
}

.site-header.is-scrolled {
  border-bottom-color: rgba(var(--edge-rgb), 0.12);
  background: var(--nav-scroll-bg);
  color: var(--text);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.24);
  backdrop-filter: blur(18px);
}

.site-nav,
.section-inner,
.site-footer-inner {
  /* --page-gutter: room a theme keeps either side of the text column (Paper's margin). */
  width: min(var(--max), calc(100% - var(--page-gutter, 32px)));
  margin: 0 auto;
}

.site-nav {
  width: 100%;
  min-height: 64px;
  padding: 0 clamp(16px, 4vw, 56px);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

.brand {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  color: inherit;
  font-size: 1.08rem;
  font-weight: 900;
  letter-spacing: 0.01em;
  transition: transform 180ms ease, color 180ms ease;
}

.brand:hover {
  transform: translateY(-1px);
  text-decoration: none;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
}

.nav-links a,
.mobile-nav a {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  color: var(--nav-link-top);
  text-decoration: none;
  font-family: var(--font-controls, revert);
  font-weight: 850;
  font-size: var(--label-size, 0.95rem);
  /* A theme may set its small labels (menu, buttons, filters) in spaced capitals. */
  text-transform: var(--label-case, revert);
  letter-spacing: var(--label-tracking, revert);
  transition: color 180ms ease, background 180ms ease, transform 180ms ease;
}

.site-header.is-scrolled .nav-links a,
.mobile-nav a {
  color: var(--muted);
}

/* The drive link is green, but at the top of the page the header bar is pale and the theme's green
   is meant for a dark page, so there it takes the bar's text colour, as its neighbours do. It turns green once the
   header goes dark on scroll, and in the mobile menu, which is always on the page colour. */
.nav-links a[href="play/"] {
  color: var(--nav-top-text);
}

.site-header.is-scrolled .nav-links a[href="play/"],
.mobile-nav a[href="play/"] {
  color: var(--green);
}

.nav-links a:hover,
.nav-links a.active,
.mobile-nav a:hover,
.mobile-nav a.active {
  color: var(--nav-top-text);
  background: color-mix(in srgb, var(--nav-top-text) 10%, transparent);
  text-decoration: none;
  transform: translateY(-1px);
}

.site-header.is-scrolled .nav-links a:hover,
.site-header.is-scrolled .nav-links a.active,
.mobile-nav a:hover,
.mobile-nav a.active {
  color: var(--text);
  background: rgba(var(--veil-rgb), 0.08);
}

.nav-toggle {
  display: none;
  min-width: 58px;
  height: 42px;
  padding: 0 10px;
  border: 1px solid color-mix(in srgb, var(--nav-top-text) 28%, transparent);
  border-radius: var(--radius-sm);
  color: var(--nav-top-text);
  background: color-mix(in srgb, var(--nav-top-text) 8%, transparent);
  cursor: pointer;
  font-weight: 800;
}

.site-header.is-scrolled .nav-toggle {
  border-color: var(--line);
  color: var(--text);
  background: rgba(var(--veil-rgb), 0.06);
}

.style-control {
  position: relative;
  z-index: 40;
}

.style-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 42px;
  padding: 0 14px;
  border: 1px solid color-mix(in srgb, var(--nav-top-text) 28%, transparent);
  border-radius: var(--radius-pill);
  color: var(--nav-top-text);
  background: color-mix(in srgb, var(--nav-top-text) 8%, transparent);
  cursor: pointer;
  font: inherit;
  font-family: var(--font-controls, inherit);
  font-size: var(--label-size, inherit);
  font-weight: 900;
  text-transform: var(--label-case, revert);
  letter-spacing: var(--label-tracking, revert);
}

.site-header.is-scrolled .style-toggle {
  border-color: var(--line);
  color: var(--text);
  background: rgba(var(--veil-rgb), 0.06);
}

.style-panel {
  position: absolute;
  top: calc(100% + 12px);
  right: 0;
  width: min(330px, calc(100vw - 24px));
  display: none;
  gap: 14px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--panel-bg-strong);
  color: var(--text);
  box-shadow: var(--shadow);
  backdrop-filter: blur(18px);
}

.style-control.is-open .style-panel {
  display: grid;
}

.style-panel label {
  display: grid;
  gap: 6px;
}

.style-panel span {
  color: var(--muted);
  font-size: 0.82rem;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.style-panel select {
  width: 100%;
  min-height: 42px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text);
  background: var(--bg-soft);
  padding: 0 10px;
  font: inherit;
  font-weight: 800;
}

.style-reset {
  min-height: 42px;
  border: 1px solid color-mix(in srgb, var(--amber) 36%, transparent);
  border-radius: var(--radius-sm);
  color: var(--text);
  background: color-mix(in srgb, var(--amber) 10%, transparent);
  cursor: pointer;
  font: inherit;
  font-weight: 900;
}

.style-reset:hover,
.style-reset:focus-visible {
  border-color: color-mix(in srgb, var(--amber) 62%, transparent);
  background: color-mix(in srgb, var(--amber) 18%, transparent);
}

a:focus-visible,
button:focus-visible,
select:focus-visible,
input:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--amber) 78%, white);
  outline-offset: 3px;
}

.mobile-nav {
  display: none;
  width: min(var(--max), calc(100% - 32px));
  margin: 0 auto 14px;
  padding: 8px;
  border: 1px solid var(--line);
  background: var(--panel-bg-strong);
}

.mobile-nav.is-open {
  display: grid;
}

.hero {
  min-height: calc(100vh - 72px);
  display: grid;
  align-items: center;
  position: relative;
  overflow: hidden;
  padding: 84px 0 54px;
}

body[data-motion="calm"] .theme-layer {
  opacity: 0.52;
}

body[data-motion="calm"] .hero::after,
body[data-motion="calm"] .orbit-ring {
  animation-duration: 24s;
}

body[data-motion="calm"] .cursor-effects {
  opacity: 0.72;
}

body[data-motion="calm"] .theme-layer,
body[data-motion="calm"] .hero::after,
body[data-motion="calm"] .profile-chip,
body[data-motion="calm"] .lite-video::before {
  animation-play-state: paused;
}

.loop-wrapper.is-animation-paused *,
body[data-motion="calm"] .loop-wrapper:not(:hover) * {
  animation-play-state: paused !important;
}

body[data-motion="off"] {
  scroll-behavior: auto;
}

body[data-motion="off"] *,
body[data-motion="off"] *::before,
body[data-motion="off"] *::after {
  animation-duration: 1ms !important;
  animation-iteration-count: 1 !important;
  scroll-behavior: auto !important;
  transition-duration: 1ms !important;
}

body[data-motion="off"]::after,
body[data-motion="off"] .theme-backdrop,
body[data-motion="off"] .cursor-effects {
  display: none !important;
}

.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(100deg, transparent 0%, rgba(var(--glow-gold-rgb), 0.08) 48%, transparent 54%);
  transform: translateX(-110%);
  animation: heroScan 7.5s ease-in-out infinite;
}

.hero .section-inner {
  position: relative;
  z-index: 1;
}

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.12fr) minmax(320px, 0.88fr);
  gap: 42px;
  align-items: center;
}

.eyebrow {
  color: var(--green);
  font-family: var(--eyebrow-font, inherit);
  font-size: 0.82rem;
  font-weight: 900;
  letter-spacing: var(--eyebrow-tracking, 0.12em);
  text-transform: var(--eyebrow-case, uppercase);
}

/* Marks a theme may write around the page's words without changing them (Cyber: a shell
   prompt before each label, a # before each section heading, brackets around buttons). The
   text after the slash is what a screen reader says for the mark: nothing. No mark unless a
   theme sets one. */
.eyebrow::before {
  content: var(--label-prefix, none);
  color: var(--prompt-colour, inherit);
  text-shadow: var(--prompt-glow, none);
  text-transform: none;
  letter-spacing: 0;
}

main h2::before {
  content: var(--heading-prefix, none);
  color: var(--prompt-colour, inherit);
  text-shadow: var(--prompt-glow, none);
}

.button::before {
  content: var(--button-open, none);
  white-space: pre;
}

.button::after {
  content: var(--button-close, none);
  white-space: pre;
}

.hero h1,
.page-hero h1 {
  margin: 14px 0 18px;
  max-width: 920px;
  /* A theme with wide letters (Cyber's monospace) may scale headlines down so they do not take
     the whole first screen. */
  font-size: calc(var(--headline-scale, 1) * clamp(2.65rem, 7vw, 6.25rem));
  line-height: 0.93;
  letter-spacing: 0;
  text-shadow: var(--headline-glow, none);
}

.hero h1.hero-title-compact {
  max-width: 860px;
  /* Scaled too, so the buttons under the home headline still show on the first screen. */
  font-size: calc(var(--headline-scale, 1) * clamp(2.15rem, 5.4vw, 4.7rem));
  line-height: 0.98;
}

.hero-lede,
.page-lede {
  max-width: 760px;
  color: var(--muted);
  font-size: clamp(1.05rem, 2vw, 1.32rem);
}

.hero-actions,
.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 28px;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid rgba(var(--edge-rgb), 0.22);
  /* A theme may round its buttons on their own (Cassie: pills) without rounding every input. */
  border-radius: var(--button-radius, var(--radius-sm));
  background: rgba(var(--edge-rgb), 0.08);
  color: var(--text);
  text-decoration: none;
  /* A link button inherits the page font and a <button> gets the browser's, unless a theme
     names a font for its controls. */
  font-family: var(--font-controls, revert);
  font-weight: 850;
  font-size: var(--label-size, revert);
  text-transform: var(--label-case, revert);
  letter-spacing: var(--label-tracking, revert);
  cursor: pointer;
  transition: transform 180ms ease, border-color 180ms ease, background 180ms ease, box-shadow 180ms ease;
}

/* A theme may cut its buttons to a shape (Ember: a ribbon with notched ends). Not while a button
   has keyboard focus, because the cut would cut its focus ring off too. */
.button:not(:focus-visible) {
  clip-path: var(--button-shape, none);
}

.button.primary {
  border-color: rgba(var(--glow-mint-rgb), 0.55);
  background: var(--button-primary-fill, linear-gradient(135deg, rgba(var(--glow-mint-rgb), 0.22), rgba(var(--glow-sky-rgb), 0.18)));
  color: var(--button-primary-ink, var(--text));
}

.button.ghost {
  background: transparent;
}

.button.arcade-cta {
  border-color: rgba(var(--glow-green-rgb), 0.58);
  background:
    linear-gradient(135deg, rgba(var(--glow-green-rgb), 0.18), rgba(120, 183, 255, 0.14)),
    var(--cta-base);
  color: var(--text);
  box-shadow: 0 0 0 1px rgba(var(--glow-green-rgb), 0.12), 0 0 28px rgba(var(--glow-green-rgb), 0.12);
}

.button.arcade-cta:hover,
.button.arcade-cta:focus-visible {
  border-color: rgba(var(--glow-green-rgb), 0.9);
  box-shadow: 0 0 0 1px rgba(var(--glow-green-rgb), 0.2), 0 0 34px rgba(var(--glow-green-rgb), 0.2);
}

.button:hover {
  transform: translateY(-1px);
  border-color: rgba(var(--glow-cyan-rgb), 0.42);
  box-shadow: 0 12px 28px rgba(var(--glow-cyan-rgb), 0.12);
}

.profile-panel {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  overflow: hidden;
  background:
    linear-gradient(145deg, var(--panel-glow), transparent 38%),
    var(--panel-bg);
  box-shadow: var(--shadow);
}

.profile-stage {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.profile-stage::before {
  content: "";
  position: absolute;
  inset: 10%;
  z-index: 0;
  border: 1px solid rgba(var(--glow-cyan-rgb), 0.26);
  transform: rotateX(60deg) rotateZ(-18deg);
  filter: blur(0.2px);
}

.profile-panel picture {
  position: relative;
  z-index: 1;
  display: block;
}

.profile-panel img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 4.6;
  object-fit: cover;
  filter: saturate(1.04) contrast(1.04);
}

.orbit-ring {
  position: absolute;
  inset: 12%;
  z-index: 2;
  border: 1px solid rgba(var(--glow-green-rgb), 0.26);
  border-radius: 50%;
  transform: rotateX(68deg) rotateZ(-16deg);
  animation: orbitSpin 14s linear infinite;
}

.orbit-chip {
  position: absolute;
  z-index: 3;
  display: inline-flex;
  min-height: 30px;
  align-items: center;
  padding: 0 10px;
  border: 1px solid rgba(var(--hairline-rgb), 0.22);
  border-radius: var(--radius-pill);
  background: rgba(7, 17, 28, 0.68);
  color: var(--text);
  font-size: 0.78rem;
  font-weight: 900;
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.28);
  backdrop-filter: blur(10px);
}

.chip-one {
  left: 8%;
  top: 18%;
  animation: chipFloat 4.8s ease-in-out infinite;
}

.chip-two {
  right: 7%;
  top: 36%;
  animation: chipFloat 5.4s ease-in-out 0.35s infinite;
}

.chip-three {
  left: 14%;
  bottom: 16%;
  animation: chipFloat 5.8s ease-in-out 0.7s infinite;
}

.proof-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 1px;
  margin-top: 42px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--line);
}

.proof-item {
  padding: 18px;
  background: var(--panel-bg-strong);
}

.proof-item strong {
  display: block;
  color: var(--text);
  font-size: 1.45rem;
}

.proof-item span {
  color: var(--muted);
  font-size: 0.92rem;
}

.proof-lane-section {
  padding-top: 52px;
}

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

.proof-lane {
  position: relative;
  min-height: 290px;
  padding: 24px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background:
    linear-gradient(145deg, var(--panel-glow), transparent 48%),
    var(--panel-bg);
  overflow: hidden;
}

.proof-lane::after {
  content: "";
  position: absolute;
  inset: auto 18px 18px auto;
  width: 90px;
  height: 90px;
  border: 1px solid color-mix(in srgb, var(--cyan) 20%, transparent);
  border-radius: 50%;
  opacity: 0.58;
}

.proof-lane-mark {
  display: inline-flex;
  min-height: 32px;
  align-items: center;
  padding: 0 10px;
  border: 1px solid color-mix(in srgb, var(--green) 32%, transparent);
  border-radius: var(--radius-pill);
  color: var(--green);
  background: color-mix(in srgb, var(--green) 8%, transparent);
  font-weight: 950;
}

.proof-lane h3 {
  margin: 18px 0 10px;
  color: var(--text);
  font-size: clamp(1.35rem, 2.5vw, 1.85rem);
}

.proof-lane p {
  color: var(--muted);
}

.stats-section {
  padding: 32px 0;
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

.stat-card {
  min-height: 150px;
  display: grid;
  align-content: center;
  gap: 6px;
  padding: 22px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background:
    linear-gradient(135deg, var(--panel-glow), transparent 48%),
    var(--panel-bg);
  overflow: hidden;
}

.stat-card strong {
  color: var(--amber);
  font-size: clamp(2.2rem, 7vw, 4rem);
  line-height: 1;
}

.stat-card span {
  color: var(--muted);
  font-weight: 850;
}

.featured-grid .work-card {
  contain-intrinsic-size: 420px;
}

.featured-grid .work-card > img,
.featured-grid .work-media,
.featured-grid .work-placeholder {
  min-height: 150px;
}

.featured-grid .work-card h3 {
  font-size: 1.1rem;
}

.featured-grid .work-card p {
  font-size: 0.95rem;
}

.about-snapshot {
  max-width: 980px;
  padding: 34px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--panel-bg);
  box-shadow: var(--shadow);
}

.about-snapshot h2 {
  margin: 0 0 14px;
  font-size: clamp(2rem, 5vw, 3.8rem);
}

.about-snapshot p {
  max-width: 860px;
  color: var(--muted);
  font-size: 1.08rem;
}

.section {
  padding: 72px 0;
}

.section.alt {
  background: rgba(var(--veil-rgb), 0.025);
  border-block: 1px solid rgba(var(--edge-rgb), 0.08);
}

.section-heading {
  display: flex;
  justify-content: space-between;
  align-items: end;
  gap: 24px;
  margin-bottom: 28px;
}

.section-heading h2,
.page-hero h1 {
  margin: 0;
}

.section-heading p {
  max-width: 620px;
  margin: 8px 0 0;
  color: var(--muted);
}

.grid {
  display: grid;
  gap: 18px;
}

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

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

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

.skill-item,
.service-card {
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--panel-bg);
}

.skill-item {
  display: grid;
  gap: 12px;
  padding: 18px;
}

.skill-name {
  color: var(--text);
  font-weight: 850;
}

.skill-bar {
  height: 11px;
  overflow: hidden;
  border-radius: var(--radius-pill);
  background: rgba(var(--veil-rgb), 0.08);
}

.skill-bar span {
  display: block;
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--green), var(--cyan), var(--amber));
  transition: width 900ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.skill-item.is-visible .skill-bar span {
  width: var(--level, 70%);
}

.service-card {
  padding: 22px;
  transition: transform 180ms ease, border-color 180ms ease, background 180ms ease;
}

.service-card:hover {
  transform: translateY(-3px);
  border-color: rgba(var(--glow-gold-rgb), 0.3);
  background: var(--panel-bg-strong);
}

.service-card p {
  color: var(--muted);
}

.panel,
.work-card,
.lab-card,
.comment-shell,
.timeline-item,
.todo-column {
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background:
    linear-gradient(150deg, var(--panel-glow), transparent 42%),
    var(--panel-bg);
}

.panel,
.work-card,
.lab-card,
.comment-shell,
.todo-column {
  padding: 22px;
}

.kinetic-card,
.panel,
.work-card,
.service-card,
.skill-item,
.stat-card,
.todo-column,
.download-card {
  transform-style: preserve-3d;
}

.kinetic-card.is-tilting {
  will-change: transform;
  transform: perspective(900px) rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg)) translateY(var(--lift, 0));
}

.panel h3,
.work-card h3,
.lab-card h3,
.timeline-item h3,
.todo-column h3 {
  margin: 0 0 10px;
  color: var(--text);
}

.panel p,
.work-card p,
.lab-card p,
.timeline-item p,
.todo-column li {
  color: var(--muted);
}

.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 16px;
}

.tag {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 0 9px;
  border: 1px solid rgba(var(--edge-rgb), 0.16);
  border-radius: var(--radius-pill);
  color: var(--muted);
  font-size: 0.82rem;
  font-weight: 700;
}

.tag.accent {
  border-color: rgba(var(--glow-gold-rgb), 0.38);
  color: var(--amber);
}

.timeline {
  display: grid;
  gap: 14px;
}

.timeline-item {
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr);
  gap: 24px;
  padding: 20px;
}

.timeline-date {
  color: var(--green);
  font-weight: 900;
}

.timeline-controls {
  display: flex;
  gap: 8px;
}

.timeline-control {
  display: inline-grid;
  width: 42px;
  min-width: 42px;
  height: 42px;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--text);
  background: rgba(var(--veil-rgb), 0.06);
  font-weight: 900;
}

.timeline-shell {
  position: relative;
  padding: 18px 0 10px;
}

.timeline-rail {
  position: absolute;
  left: 0;
  right: 0;
  top: 64px;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--cyan), var(--green), var(--amber), transparent);
  opacity: 0.7;
}

.story-timeline {
  position: relative;
  display: flex;
  gap: 18px;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 22px 4px 18px;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
  scrollbar-color: rgba(var(--glow-cyan-rgb), 0.5) transparent;
}

.story-timeline .timeline-item {
  position: relative;
  flex: 0 0 min(380px, calc(100vw - 54px));
  display: grid;
  align-content: start;
  gap: 16px;
  min-height: 310px;
  padding: 0;
  border: 0;
  background: transparent;
  scroll-snap-align: start;
}

.story-timeline .timeline-item::before {
  content: "";
  width: 16px;
  height: 16px;
  margin-left: 18px;
  border: 3px solid var(--bg);
  border-radius: 50%;
  background: var(--amber);
  box-shadow: 0 0 0 5px rgba(var(--glow-amber-rgb), 0.22);
}

.story-timeline .timeline-date {
  display: inline-flex;
  width: max-content;
  max-width: 100%;
  min-height: 30px;
  align-items: center;
  justify-self: start;
  position: relative;
  z-index: 2;
  padding: 0 10px;
  border: 1px solid rgba(var(--glow-green-rgb), 0.24);
  border-radius: var(--radius-pill);
  color: var(--green);
  background: var(--panel-bg-strong);
  box-shadow: 0 0 0 4px var(--bg);
  font-weight: 900;
  white-space: nowrap;
}

.timeline-card {
  min-height: 220px;
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background:
    linear-gradient(135deg, var(--panel-glow), transparent 45%),
    var(--panel-bg-strong);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.22);
  transition: transform 180ms ease, border-color 180ms ease, background 180ms ease;
}

.timeline-card:hover {
  transform: translateY(-4px);
  border-color: rgba(var(--glow-green-rgb), 0.34);
  background:
    linear-gradient(135deg, var(--panel-glow), transparent 48%),
    var(--panel-bg-strong);
}

.work-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

/* The projects page in two groups, Featured and More projects. The filters reach both, and a
   group with nothing left to show goes, heading and all, instead of leaving a title over a gap. */
.project-group + .project-group {
  margin-top: 56px;
}

.project-group-title {
  margin: 0 0 18px;
  color: var(--text);
  font-size: clamp(1.4rem, 2.6vw, 2rem);
}

.project-group:not(:has(.work-card:not(.is-filtered-out))) {
  display: none;
}

/* Every project is the same card: picture, name, kind and dates, one sentence, one main button in
   the same place, then small links. Featured cards are the same card, larger. Scoped to this page's
   grids, so the home page's cards keep their own look. */
.project-cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 22px;
}

/* Two across, so the drawn pictures show near the 620px they were drawn at and their words stay
   readable; at three across they shrank to about 380px. */
.project-cards.featured-projects {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
}

.project-cards .work-card {
  gap: 0;
  padding: 0;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: var(--panel-bg);
  contain-intrinsic-size: 460px;
}

.project-cards .work-card:hover {
  transform: none;
  border-color: rgba(var(--glow-sky-rgb), 0.4);
}

.work-shot {
  display: block;
  overflow: hidden;
  border-bottom: 1px solid var(--line);
  background: var(--bg-soft);
}

/* The img's height attribute would otherwise set its height and win over the 16:9 shape. */
.work-shot > img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

@media (prefers-reduced-motion: no-preference) {
  .work-shot > img {
    transition: transform 320ms ease;
  }

  .project-cards .work-card:hover .work-shot > img {
    transform: scale(1.03);
  }
}

.work-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: 18px 20px 20px;
}

.project-cards h3 {
  margin: 0;
  font-size: 1.05rem;
  line-height: 1.25;
}

.project-cards.featured-projects h3 {
  font-size: 1.3rem;
}

.project-cards h3 a {
  color: inherit;
  text-decoration: none;
}

.project-cards h3 a:hover,
.project-cards h3 a:focus-visible {
  text-decoration: underline;
  text-underline-offset: 4px;
}

.project-cards .work-meta {
  gap: 0;
  margin-top: 6px;
  font-size: 0.85rem;
}

.project-cards .work-meta span + span::before {
  content: "\00b7";
  margin: 0 8px;
}

.project-cards .work-card p {
  margin: 10px 0 0;
  font-size: 0.92rem;
}

/* The one main button, always in the same place: the bottom of the words. */
.work-action {
  margin-top: auto;
  padding-top: 18px;
}

.work-action .button {
  min-height: 40px;
  font-size: 0.9rem;
}

/* Sits where a button would, says there is nothing to open, and does not pretend to be a link. */
.button.work-private {
  opacity: 0.55;
  cursor: default;
}

.button.work-private:hover {
  transform: none;
  box-shadow: none;
}

.work-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 12px;
  margin-top: 12px;
  font-size: 0.86rem;
}

.work-links .work-link,
.work-links .video-play,
.work-comments summary {
  color: var(--muted);
  font-weight: 750;
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-underline-offset: 4px;
}

.work-links .work-link:hover,
.work-links .work-link:focus-visible,
.work-links .video-play:hover,
.work-links .video-play:focus-visible,
.work-comments summary:hover,
.work-comments summary:focus-visible {
  color: var(--text);
  text-decoration-color: currentColor;
}

/* The demo video's button, worn as a link here. */
.work-links .video-play {
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  font-size: inherit;
}

.work-links .video-play:hover,
.work-links .video-play:focus-visible {
  transform: none;
  background: none;
}

/* Each project's comments sit behind a link-sized toggle at the end of its links. Opened, the
   thread takes the whole width under them. It only loads once opened: comments.js waits until
   it is on screen, and inside a closed toggle it never is. */
.work-comments summary {
  cursor: pointer;
  list-style: none;
}

.work-comments summary::-webkit-details-marker {
  display: none;
}

.work-comments[open] {
  flex-basis: 100%;
}

@media (max-width: 920px) {
  .project-cards,
  .project-cards.featured-projects {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 620px) {
  .project-cards,
  .project-cards.featured-projects {
    grid-template-columns: 1fr;
  }
}

.filter-button {
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 9px 12px;
  color: var(--muted);
  background: rgba(var(--veil-rgb), 0.04);
  cursor: pointer;
  /* The browser's own button font unless a theme writes its buttons by hand like its text. */
  font-family: var(--font-controls, revert);
  font-size: var(--font-controls-size, revert);
  font-weight: 800;
  text-transform: var(--label-case, revert);
  letter-spacing: var(--label-tracking, revert);
  transition: transform 160ms ease, border-color 160ms ease, background 160ms ease, color 160ms ease;
}

.filter-button:hover,
.filter-button:focus-visible {
  transform: translateY(-1px);
  border-color: rgba(var(--glow-gold-rgb), 0.46);
  color: var(--text);
}

.filter-button.is-active {
  color: var(--text);
  border-color: rgba(var(--glow-sky-rgb), 0.5);
  background: rgba(var(--glow-sky-rgb), 0.13);
}

.filter-status {
  min-height: 1.4em;
  margin: 0 0 22px;
  color: var(--muted);
  font-size: 0.95rem;
}

.work-card {
  content-visibility: auto;
  contain-intrinsic-size: 540px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: 100%;
  transition: opacity 180ms ease, transform 180ms ease, border-color 180ms ease;
}

.work-card:hover {
  transform: translateY(-3px);
  border-color: rgba(var(--glow-sky-rgb), 0.28);
}

.work-card.is-filtered-out {
  display: none;
}

.work-card > img,
.work-media,
.work-placeholder {
  width: 100%;
  min-height: 220px;
  border-radius: var(--radius-sm);
  aspect-ratio: 16 / 9;
  background: var(--bg-soft);
}

.work-card > img,
.work-media iframe {
  border: 0;
  object-fit: cover;
}

.work-card > img {
  display: block;
}

.work-media {
  position: relative;
  display: grid;
  place-items: center;
  overflow: hidden;
}

.work-media iframe {
  width: 100%;
  height: 100%;
  display: block;
}

.lite-video {
  isolation: isolate;
  border: 1px solid rgba(var(--edge-rgb), 0.12);
  background:
    radial-gradient(circle at 22% 18%, rgba(var(--glow-sky-rgb), 0.32), transparent 28%),
    radial-gradient(circle at 78% 28%, rgba(var(--glow-gold-rgb), 0.2), transparent 24%),
    linear-gradient(135deg, rgba(8, 13, 21, 0.94), rgba(17, 25, 39, 0.82));
}

.lite-video::before,
.lite-video::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

.lite-video::before {
  background:
    linear-gradient(90deg, rgba(var(--veil-rgb), 0.05) 1px, transparent 1px),
    linear-gradient(0deg, rgba(var(--veil-rgb), 0.04) 1px, transparent 1px);
  background-size: 34px 34px;
  mask-image: radial-gradient(circle at center, black, transparent 76%);
}

.lite-video::after {
  inset: auto 0 0;
  height: 48%;
  background: linear-gradient(0deg, rgba(var(--glow-sky-rgb), 0.18), transparent);
}

.video-play {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-height: 52px;
  padding: 12px 18px;
  border: 1px solid rgba(var(--edge-rgb), 0.2);
  border-radius: var(--radius-pill);
  /* Light on every theme: the pill and the panel behind it are dark on every theme too, and the
     theme's text colour turns dark on the light themes, which made the label unreadable. */
  color: #f4efe7;
  background: rgba(5, 8, 13, 0.64);
  box-shadow: 0 18px 42px rgba(0, 0, 0, 0.34);
  cursor: pointer;
  font: inherit;
  font-size: 0.92rem;
  font-weight: 900;
  transition: transform 180ms ease, border-color 180ms ease, background 180ms ease;
}

.video-play:hover,
.video-play:focus-visible {
  transform: translateY(-2px);
  border-color: rgba(var(--glow-sky-rgb), 0.72);
  background: rgba(8, 13, 21, 0.82);
}

/*
  A screen, not a play triangle. The button opens a video in a window of its own, and on the
  Cyber Sentinel page a play triangle already means "run the film". The screen is a box with a
  stand under it, drawn in the theme's cyan: the triangle it replaces was painted in an
  --accent that no stylesheet defines, so it was not drawn at all.
*/
.video-play-icon {
  position: relative;
  flex: none;
  width: 22px;
  height: 15px;
  margin-bottom: 5px;
  border: 2px solid var(--cyan);
  border-radius: 2px;
}

.video-play-icon::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -7px;
  width: 10px;
  height: 2px;
  transform: translateX(-50%);
  background: var(--cyan);
}

/* The video's own window: centred in the browser's top layer, above everything on the page. */
.video-dialog {
  width: min(1040px, 92vw, calc((100vh - 140px) * 16 / 9));
  max-width: none;
  max-height: none;
  padding: 0;
  border: 1px solid var(--line);
  color: var(--text);
  background: var(--bg-deep);
  box-shadow: var(--shadow);
}

.video-dialog::backdrop {
  background: rgba(3, 6, 10, 0.78);
}

/* The page behind an open video holds still. On a page whose film follows the scroll, a wheel
   behind the dialog would otherwise fly the camera on while the reader watches. */
html:has(.video-dialog[open]) {
  overflow: hidden;
  scrollbar-gutter: stable;
}

.video-dialog-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
}

.video-dialog-title {
  margin: 0;
  font-size: 0.92rem;
  font-weight: 800;
}

.video-dialog-close {
  padding: 6px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  color: var(--text);
  background: none;
  cursor: pointer;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 800;
}

.video-dialog-close:hover,
.video-dialog-close:focus-visible {
  border-color: var(--cyan);
}

.video-dialog-frame {
  aspect-ratio: 16 / 9;
  background: #000;
}

.video-dialog-frame iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.work-placeholder {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 8px;
  overflow: hidden;
  padding: 18px;
  border: 1px solid rgba(var(--edge-rgb), 0.12);
  color: var(--text);
}

.work-placeholder::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(120deg, rgba(var(--glow-sky-rgb), 0.18), transparent 42%),
    radial-gradient(circle at 82% 20%, rgba(var(--glow-gold-rgb), 0.22), transparent 28%),
    repeating-linear-gradient(135deg, rgba(var(--veil-rgb), 0.05) 0 1px, transparent 1px 13px);
}

.work-placeholder span,
.work-placeholder strong {
  position: relative;
  z-index: 1;
}

.work-placeholder span {
  color: var(--green);
  font-size: 0.8rem;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.work-placeholder strong {
  max-width: 18rem;
  font-size: 1.22rem;
  line-height: 1.2;
}

.work-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  color: var(--muted);
  font-size: 0.92rem;
}

.page-hero {
  padding: 84px 0 44px;
}

/* Tight above the projects, so the featured row's first names are on a laptop's first screen. */
.project-page-hero {
  padding-bottom: 8px;
  text-align: center;
}

.project-section {
  padding-top: 24px;
}

.project-page-hero h1 {
  max-width: 860px;
  margin-inline: auto;
  font-size: clamp(1.75rem, 3.8vw, 3rem);
  line-height: 1.05;
}

.case-hero {
  min-height: calc(84vh - 72px);
  display: grid;
  align-items: center;
  padding: 96px 0 54px;
}

.case-hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(340px, 0.8fr);
  gap: 34px;
  align-items: center;
}

.case-hero h1 {
  margin: 14px 0 18px;
  max-width: 860px;
  color: var(--text);
  font-size: clamp(2.6rem, 7vw, 6rem);
  line-height: 0.94;
}

.case-video {
  width: 100%;
  aspect-ratio: 16 / 9;
}

.case-video {
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--bg-soft);
  box-shadow: var(--shadow);
}

.case-metrics {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

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

.case-panel,
.case-step {
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background:
    linear-gradient(145deg, var(--panel-glow), transparent 46%),
    var(--panel-bg);
}

.case-panel {
  padding: 28px;
}

.case-panel h2 {
  margin: 8px 0 14px;
  color: var(--text);
  font-size: clamp(1.8rem, 4vw, 3rem);
  line-height: 1.02;
}

.case-panel p,
.case-panel li,
.case-step p {
  color: var(--muted);
}

.case-flow {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

.case-step {
  min-height: 210px;
  padding: 22px;
}

.case-step span {
  display: inline-flex;
  min-height: 30px;
  align-items: center;
  padding: 0 9px;
  border: 1px solid color-mix(in srgb, var(--cyan) 32%, transparent);
  border-radius: var(--radius-pill);
  color: var(--cyan);
  background: color-mix(in srgb, var(--cyan) 8%, transparent);
  font-weight: 950;
}

.case-step h3 {
  margin: 18px 0 8px;
  color: var(--text);
}

.decision-list {
  margin: 0;
  padding-left: 20px;
}

.decision-list li + li {
  margin-top: 8px;
}

.cv-sheet-section {
  padding-top: 36px;
}

.cv-paper {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-md);
  border: 1px solid rgba(var(--hairline-rgb), 0.16);
  background:
    linear-gradient(150deg, rgba(var(--glow-cyan-rgb), 0.1), transparent 30%),
    var(--sheet-bg);
  color: var(--text);
  box-shadow: var(--sheet-shadow);
}

.cv-paper a {
  color: var(--cyan);
}

.cv-paper-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 0.55fr);
  gap: 24px;
  padding: 34px;
  border-bottom: 1px solid rgba(var(--hairline-rgb), 0.14);
  background:
    radial-gradient(circle at 8% 15%, rgba(var(--glow-green-rgb), 0.2), transparent 22rem),
    linear-gradient(135deg, rgba(var(--glow-cyan-rgb), 0.16), rgba(var(--glow-amber-rgb), 0.06) 58%, transparent),
    var(--sheet-head-bg);
}

.cv-paper-head h2 {
  margin: 0;
  color: var(--text);
  font-size: clamp(2.5rem, 7vw, 5.5rem);
  line-height: 0.95;
}

.cv-headline {
  max-width: 780px;
  margin: 16px 0 0;
  color: var(--muted);
  font-size: 1.05rem;
}

.cv-kicker {
  margin: 0 0 10px;
  color: var(--green);
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.cv-contact-line {
  display: grid;
  gap: 8px;
  align-content: center;
  color: var(--muted);
  font-weight: 750;
}

.cv-highlight-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  border-bottom: 1px solid rgba(var(--hairline-rgb), 0.12);
  background: rgba(var(--hairline-rgb), 0.12);
}

.cv-highlight-strip div {
  padding: 18px 22px;
  background: rgba(var(--veil-rgb), 0.035);
}

.cv-highlight-strip strong,
.cv-highlight-strip span {
  display: block;
}

.cv-highlight-strip strong {
  color: var(--amber);
  font-size: 1.18rem;
}

.cv-highlight-strip span {
  color: var(--muted);
  font-size: 0.88rem;
}

.cv-paper-grid {
  display: grid;
  grid-template-columns: minmax(240px, 0.34fr) minmax(0, 0.66fr);
}

.cv-sidebar {
  display: grid;
  align-content: start;
  gap: 22px;
  padding: 30px;
  background:
    linear-gradient(180deg, var(--panel-glow), transparent 44%),
    var(--panel-bg-strong);
  border-right: 1px solid rgba(var(--hairline-rgb), 0.12);
}

.cv-main {
  display: grid;
  gap: 22px;
  padding: 30px;
}

.cv-block {
  break-inside: avoid;
}

.cv-block + .cv-block {
  padding-top: 18px;
  border-top: 1px solid rgba(var(--hairline-rgb), 0.12);
}

.cv-block h3 {
  margin: 0 0 12px;
  color: var(--cyan);
  font-size: 1rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.cv-block h4,
.cv-block p,
.cv-block ul {
  margin-top: 0;
}

.cv-block p,
.cv-block li,
.cv-role-head span {
  color: var(--muted);
}

.cv-block ul {
  padding-left: 18px;
}

.cv-block li + li {
  margin-top: 5px;
}

.cv-pill-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 0;
  list-style: none;
}

.cv-pill-list li,
.cv-project-grid span {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 6px 9px;
  border: 1px solid rgba(var(--hairline-rgb), 0.16);
  border-radius: var(--radius-sm);
  background: rgba(var(--veil-rgb), 0.055);
  color: var(--text);
  font-size: 0.88rem;
  font-weight: 750;
}

.cv-role + .cv-role {
  margin-top: 16px;
}

.cv-role {
  padding: 16px;
  border: 1px solid rgba(var(--hairline-rgb), 0.12);
  border-radius: var(--radius-md);
  background: rgba(var(--veil-rgb), 0.035);
}

.cv-role-head {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 6px;
}

.cv-role-head h4 {
  margin-bottom: 0;
  color: var(--text);
}

.cv-role-head span {
  flex: 0 0 auto;
  font-size: 0.88rem;
  font-weight: 900;
}

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

.cv-skill-groups {
  display: grid;
  gap: 8px;
}

.cv-skill-groups p {
  margin: 0;
  padding: 12px;
  border: 1px solid rgba(var(--hairline-rgb), 0.1);
  border-radius: var(--radius-sm);
  background: rgba(var(--veil-rgb), 0.035);
}

.cv-downloads {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 16px;
}

.download-card {
  padding: 22px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--panel-bg);
}

.download-card p {
  color: var(--muted);
}

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

.todo-column ul {
  margin: 0;
  padding-left: 20px;
}

.todo-column li + li {
  margin-top: 8px;
}

.comment-shell {
  margin-top: 24px;
}

.work-card .comment-shell,
.lab-card .comment-shell {
  border: 0;
  background: transparent;
  padding: 0;
  margin-top: 8px;
}

.comment-shell h3 {
  margin: 0 0 6px;
}

.comment-note {
  color: var(--muted);
  margin: 0 0 18px;
}

.comment-note:empty {
  display: none;
}

.comment-form {
  display: grid;
  gap: 10px;
  margin-bottom: 18px;
}

.comment-form input,
.comment-form textarea {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--bg-soft) 82%, transparent);
  color: var(--text);
  padding: 12px;
  font: inherit;
}

.comment-form textarea {
  min-height: 120px;
  resize: vertical;
}

.comment-form .hidden-field {
  display: none;
}

.comment-list {
  display: grid;
  gap: 10px;
}

.comment-item {
  border: 1px solid rgba(var(--edge-rgb), 0.1);
  border-radius: var(--radius-sm);
  padding: 14px;
  background: color-mix(in srgb, var(--bg-soft) 78%, transparent);
}

.comment-item strong {
  display: block;
}

.comment-item time {
  color: var(--muted);
  font-size: 0.84rem;
}

.comment-item p {
  margin: 8px 0 0;
  color: var(--text);
  white-space: pre-wrap;
}

.comment-status {
  color: var(--amber);
  min-height: 24px;
}

.admin-layout {
  display: grid;
  grid-template-columns: minmax(260px, 0.36fr) minmax(0, 0.64fr);
  gap: 18px;
}

.admin-visitors,
.admin-health {
  grid-column: 1 / -1;
}

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

.health-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
}

.health-row:last-child {
  border-bottom: 0;
}

.health-label {
  display: block;
  color: var(--text);
}

.health-detail {
  display: block;
  color: var(--muted);
  font-size: 0.84rem;
}

.health-state {
  font-weight: 600;
  font-size: 0.76rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-align: right;
}

.health-state.is-ok { color: var(--green); }
.health-state.is-down { color: var(--rose); }
.health-state.is-checking { color: var(--amber); }
.health-state.is-idle { color: var(--muted); }

.admin-panel,
.admin-comment {
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background:
    linear-gradient(145deg, var(--panel-glow), transparent 42%),
    var(--panel-bg);
}

.admin-panel {
  padding: 22px;
}

.admin-panel h2 {
  margin-top: 0;
  color: var(--text);
}

.admin-comment-list {
  display: grid;
  gap: 12px;
}

.admin-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
  margin-bottom: 16px;
}

.admin-stat {
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: rgba(var(--veil-rgb), 0.04);
}

.admin-stat strong {
  display: block;
  color: var(--amber);
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  line-height: 1;
}

.admin-stat span {
  color: var(--muted);
  font-weight: 850;
}

.admin-visitor-list {
  display: grid;
  gap: 10px;
}

.admin-visitor {
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: rgba(var(--veil-rgb), 0.035);
}

.admin-visitor code {
  color: var(--green);
  overflow-wrap: anywhere;
}

.admin-comment {
  padding: 16px;
}

.admin-comment-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 10px;
}

.admin-comment-head strong {
  color: var(--text);
}

.admin-comment-head span,
.admin-comment time,
.admin-comment p {
  color: var(--muted);
}

/* The Pencil trail's canvas, drawn by setupPointerEffects in portfolio-v2.js. */
.cursor-pencil {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.cursor-point {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 2;
  --cursor-rotate: 0deg;
  --cursor-scale: 1;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  pointer-events: none;
  background: var(--cyan);
  box-shadow: 0 0 22px var(--cyan);
  transform: translate(-50%, -50%) rotate(var(--cursor-rotate)) scale(var(--cursor-scale));
  animation: cursorFade 760ms ease-out forwards;
  will-change: opacity, transform;
}

body[data-cursor="comet"] .cursor-point {
  width: 12px;
  height: 12px;
  background: var(--cyan);
  box-shadow: 0 0 28px var(--cyan), 0 0 40px var(--green);
}

body[data-cursor="sparks"] .cursor-point {
  width: 6px;
  height: 6px;
  background: var(--amber);
  box-shadow: 0 0 20px var(--amber);
  animation-duration: 900ms;
}

body[data-cursor="ribbon"] .cursor-point {
  width: 22px;
  height: 5px;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--cyan), var(--green), transparent);
  box-shadow: 0 0 20px var(--cyan);
}

body[data-cursor="embers"] .cursor-point {
  width: 8px;
  height: 8px;
  background: var(--rose);
  box-shadow: 0 0 18px var(--rose), 0 0 32px var(--amber);
  animation-duration: 1100ms;
}

body[data-cursor="fireflies"] .cursor-point {
  width: 4px;
  height: 4px;
  background: #fff1b8;
  box-shadow: 0 0 8px rgba(242, 210, 122, 0.95), 0 0 18px rgba(242, 210, 122, 0.45);
  animation: cursorFirefly 2200ms ease-in-out forwards;
}

/* Terminal: a block cursor that leaves an underscore where it stood, fading like old output. The
   block and the underscore are separate so each only changes its opacity. */
body[data-cursor="terminal"] .cursor-point {
  width: 9px;
  height: 16px;
  border-radius: 1px;
  background: transparent;
  box-shadow: none;
  transform: translate(-50%, -50%);
  animation: none;
}

body[data-cursor="terminal"] .cursor-point::before,
body[data-cursor="terminal"] .cursor-point::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  background: var(--amber);
}

body[data-cursor="terminal"] .cursor-point::before {
  top: 0;
  bottom: 0;
  box-shadow: 0 0 12px var(--amber);
  animation: cursorTerminalBlock 900ms linear forwards;
}

body[data-cursor="terminal"] .cursor-point::after {
  bottom: 0;
  height: 2px;
  box-shadow: 0 0 6px var(--amber);
  opacity: 0;
  animation: cursorTerminalMark 900ms linear forwards;
}

/* Paws: a trail of little paw prints in the theme's label colour, each turned its own way. */
body[data-cursor="paws"] .cursor-point {
  width: 14px;
  height: 14px;
  border-radius: 0;
  background: var(--green);
  box-shadow: none;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><ellipse cx='10' cy='13.5' rx='5' ry='4.2'/><ellipse cx='4' cy='8' rx='2' ry='2.6'/><ellipse cx='8' cy='4.6' rx='2' ry='2.6'/><ellipse cx='12' cy='4.6' rx='2' ry='2.6'/><ellipse cx='16' cy='8' rx='2' ry='2.6'/></svg>") center / contain no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><ellipse cx='10' cy='13.5' rx='5' ry='4.2'/><ellipse cx='4' cy='8' rx='2' ry='2.6'/><ellipse cx='8' cy='4.6' rx='2' ry='2.6'/><ellipse cx='12' cy='4.6' rx='2' ry='2.6'/><ellipse cx='16' cy='8' rx='2' ry='2.6'/></svg>") center / contain no-repeat;
  transform: translate(-50%, -50%) rotate(var(--paw-turn, 0deg));
  animation: cursorPawFade 1300ms ease-out forwards;
}

body[data-cursor="plasma"] .cursor-point {
  width: 26px;
  height: 26px;
  background:
    radial-gradient(circle, color-mix(in srgb, var(--cyan) 82%, white) 0 18%, var(--cyan) 20% 44%, transparent 70%);
  filter: blur(2px) saturate(1.35);
  box-shadow: 0 0 30px var(--cyan), 0 0 54px var(--violet);
  animation: cursorPlasmaFade 1050ms ease-out forwards;
}

body[data-cursor="ink"] .cursor-point {
  width: 34px;
  height: 34px;
  border: 1px solid color-mix(in srgb, var(--text) 64%, transparent);
  background: transparent;
  box-shadow:
    inset 0 0 18px color-mix(in srgb, var(--cyan) 20%, transparent),
    0 0 18px color-mix(in srgb, var(--text) 18%, transparent);
  animation: cursorRingFade 980ms ease-out forwards;
}

body[data-cursor="constellation"] .cursor-point {
  width: 4px;
  height: 4px;
  background: color-mix(in srgb, var(--text) 88%, var(--cyan));
  box-shadow:
    12px -8px 0 -1px color-mix(in srgb, var(--cyan) 74%, transparent),
    -11px 10px 0 -1px color-mix(in srgb, var(--violet) 72%, transparent),
    0 0 16px var(--cyan);
  animation: cursorConstellationFade 1220ms ease-out forwards;
}

body[data-cursor="constellation"] .cursor-point::before {
  content: "";
  position: absolute;
  left: -12px;
  top: 1px;
  width: 34px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--cyan), transparent);
  transform: rotate(var(--cursor-rotate));
  opacity: 0.58;
}

body[data-cursor="scanner"] .cursor-point {
  width: 42px;
  height: 2px;
  border-radius: 999px;
  background: linear-gradient(90deg, transparent, var(--cyan), var(--rose), transparent);
  box-shadow: 0 0 18px var(--cyan), 0 0 28px var(--rose);
  animation: cursorScannerFade 680ms ease-out forwards;
}

body[data-cursor="scanner"] .cursor-point::before,
body[data-cursor="scanner"] .cursor-point::after {
  content: "";
  position: absolute;
  left: 6px;
  right: 6px;
  height: 1px;
  background: color-mix(in srgb, var(--cyan) 70%, transparent);
}

body[data-cursor="scanner"] .cursor-point::before {
  top: -8px;
}

body[data-cursor="scanner"] .cursor-point::after {
  bottom: -8px;
}

body[data-cursor="orbit"] .cursor-point {
  width: 32px;
  height: 32px;
  border: 1px solid color-mix(in srgb, var(--cyan) 44%, transparent);
  background: transparent;
  box-shadow: 0 0 22px color-mix(in srgb, var(--cyan) 42%, transparent);
  animation: cursorOrbitFade 1080ms ease-out forwards;
}

body[data-cursor="orbit"] .cursor-point span {
  position: absolute;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--amber);
  box-shadow: 0 0 16px var(--amber);
}

body[data-cursor="orbit"] .cursor-point span:first-child {
  left: 50%;
  top: -3px;
}

body[data-cursor="orbit"] .cursor-point span:last-child {
  right: -3px;
  bottom: 50%;
  background: var(--cyan);
  box-shadow: 0 0 16px var(--cyan);
}

body[data-cursor="snow"] .cursor-point {
  width: 7px;
  height: 7px;
  background: color-mix(in srgb, var(--text) 92%, var(--cyan));
  box-shadow:
    0 0 12px color-mix(in srgb, var(--cyan) 62%, transparent),
    8px 6px 0 -3px color-mix(in srgb, var(--text) 80%, transparent);
  clip-path: polygon(50% 0, 62% 34%, 98% 35%, 69% 56%, 80% 92%, 50% 70%, 20% 92%, 31% 56%, 2% 35%, 38% 34%);
  animation: cursorSnowFade 1450ms ease-out forwards;
}

body[data-cursor="pulse"] .cursor-point {
  width: 28px;
  height: 28px;
  border: 1px solid var(--cyan);
  background: color-mix(in srgb, var(--cyan) 12%, transparent);
  box-shadow: 0 0 24px color-mix(in srgb, var(--cyan) 50%, transparent);
  animation: cursorPulseFade 940ms ease-out forwards;
}

.cursor-reticle {
  position: fixed;
  left: var(--mouse-x);
  top: var(--mouse-y);
  z-index: 1;
  width: 46px;
  height: 46px;
  border: 1px solid var(--cyan);
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -50%);
  box-shadow: 0 0 24px color-mix(in srgb, var(--cyan) 44%, transparent);
}

.cursor-reticle::before,
.cursor-reticle::after {
  content: "";
  position: absolute;
  background: var(--cyan);
  opacity: 0.6;
}

.cursor-reticle::before {
  left: 50%;
  top: -18px;
  width: 1px;
  height: 82px;
}

.cursor-reticle::after {
  top: 50%;
  left: -18px;
  width: 82px;
  height: 1px;
}

body[data-cursor="reticle"] .cursor-reticle {
  opacity: 0.75;
}

.site-footer {
  border-top: 1px solid var(--line);
  background:
    linear-gradient(180deg, var(--panel-bg-strong), var(--bg-deep)),
    var(--bg-deep);
}

.site-footer-inner {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(180px, 0.4fr) minmax(180px, 0.4fr);
  gap: 28px;
  padding: 38px 0;
}

.footer-section {
  display: grid;
  align-content: start;
  gap: 8px;
}

.footer-section h3 {
  margin: 0 0 4px;
  color: var(--text);
  font-size: 0.95rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.footer-section a,
.footer-links a {
  color: var(--muted);
  text-decoration: none;
  width: max-content;
  max-width: 100%;
}

.footer-section a:hover,
.footer-links a:hover {
  color: var(--text);
  text-decoration: none;
}

.footer-note {
  color: var(--muted);
}

.legacy-link {
  color: var(--amber);
  font-weight: 900;
}

/* Every page puts the nav, five links and the style controls before the content, so without
   this a keyboard or screen-reader user tabs through all of it on every page. Hidden off-screen
   until focused rather than display:none, which would take it out of the tab order entirely. */
.skip-link {
  position: absolute;
  left: 12px;
  top: -64px;
  z-index: 999;
  padding: 10px 18px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg-panel);
  color: var(--text);
  font-weight: 600;
  text-decoration: none;
  transition: top 140ms ease;
}

.skip-link:focus {
  top: 12px;
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

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

@media (prefers-reduced-motion: no-preference) {
  .reveal {
    opacity: 0;
    transform: translateY(24px);
    transition: opacity 520ms ease, transform 520ms ease;
  }

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

  .profile-panel {
    animation: panelFloat 7s ease-in-out infinite;
  }
}

@keyframes pageGridDrift {
  from {
    background-position: 0 0, 0 0;
  }

  to {
    background-position: 54px 54px, 54px 54px;
  }
}

@keyframes backdropFloat {
  from {
    transform: translate3d(-1.5%, -1%, 0) scale(1);
  }

  to {
    transform: translate3d(1.5%, 1%, 0) scale(1.05);
  }
}

@keyframes backdropSlide {
  from {
    background-position: 0 0, 0 0;
  }

  to {
    background-position: 220px 0, 360px 0;
  }
}

@keyframes horizonPulse {
  0%,
  100% {
    opacity: 0.6;
    transform: translateY(0);
  }

  50% {
    opacity: 1;
    transform: translateY(-18px);
  }
}

@keyframes forestShafts {
  from {
    opacity: 0.7;
    transform: rotate(-1.2deg);
  }

  to {
    opacity: 1;
    transform: rotate(1.2deg);
  }
}

/* 1650px is a whole number of both glint tiles (150px and 110px), so the loop has no jump. */
@keyframes forestRiver {
  from {
    background-position: 0 7px, 0 15px, 0 0;
  }

  to {
    background-position: 1650px 7px, 1650px 15px, 0 0;
  }
}

@keyframes spaceSpark {
  from {
    opacity: 1;
    transform: rotate(var(--speck-turn)) translateX(0) scale(1);
  }

  to {
    opacity: 0;
    transform: rotate(var(--speck-turn)) translateX(var(--speck-reach)) scale(0.4);
  }
}

/* Fireflies sent up by a click: out, then drifting upwards as they fade. */
@keyframes forestSpeck {
  from {
    opacity: 1;
    transform: rotate(var(--speck-turn)) translateX(0);
  }

  60% {
    opacity: 0.9;
  }

  to {
    opacity: 0;
    transform: rotate(var(--speck-turn)) translateX(var(--speck-reach)) translateY(-10px);
  }
}

@keyframes forestBlink {
  0%,
  100% {
    opacity: 1;
    transform: translateY(0);
  }

  50% {
    opacity: 0.35;
    transform: translateY(-2px);
  }
}

@keyframes cyberAura {
  from {
    opacity: 0.45;
  }

  to {
    opacity: 1;
  }
}

/* The beam crosses in six seconds, then the screen rests for four. */
@keyframes cyberBeam {
  0% {
    transform: translateY(-40vh);
  }

  60%,
  100% {
    transform: translateY(110vh);
  }
}

/* A beam of moonlight drifts across the forest, then the night rests. */
@keyframes forestMoonbeam {
  0%,
  30% {
    transform: translateX(-60%);
  }

  100% {
    transform: translateX(60%);
  }
}

@keyframes cyberBlink {
  0% {
    opacity: 1;
  }

  50%,
  100% {
    opacity: 0;
  }
}

@keyframes forestLeafFall {
  from {
    transform: translate3d(0, 0, 0) rotate(0deg);
  }

  to {
    transform: translate3d(var(--leaf-drift, 0px), 110vh, 0) rotate(var(--leaf-turn, 360deg));
  }
}

@keyframes spaceOrbit {
  from {
    offset-distance: 0%;
  }

  to {
    offset-distance: 100%;
  }
}

@keyframes spaceHaze {
  from {
    opacity: 0.55;
  }

  to {
    opacity: 1;
  }
}

@keyframes lightRipple {
  from {
    opacity: 1;
    transform: scale(0.3);
  }

  to {
    opacity: 0;
    transform: scale(1.6);
  }
}

@keyframes glowBreathe {
  from {
    opacity: 0.55;
  }

  to {
    opacity: 1;
  }
}

/* A blink: the lids close and open again. */
@keyframes cassieBlink {
  0%,
  100% {
    transform: scaleY(0);
  }

  45%,
  55% {
    transform: scaleY(1);
  }
}

/* Petted: the head wiggles from side to side, peeking from the same place. */
@keyframes cassieWiggle {
  0%,
  100% {
    transform: translateY(-4px) rotate(0deg);
  }

  30% {
    transform: translateY(-4px) rotate(-6deg);
  }

  70% {
    transform: translateY(-4px) rotate(6deg);
  }
}

/* A swipe of the paw: up, out toward the pointer, and back down. */
@keyframes cassieBat {
  0%,
  100% {
    transform: none;
  }

  45% {
    transform: translate(-4px, -30px) rotate(-28deg);
  }
}

@keyframes cassieStretch {
  0%,
  100% {
    transform: none;
  }

  50% {
    transform: scale(1.08, 0.93);
  }
}

/* Asleep: the cat's side rises and falls. */
@keyframes cassieBreathe {
  0%,
  100% {
    transform: scaleY(1);
  }

  50% {
    transform: scaleY(1.035);
  }
}

@keyframes cassieSleepZ {
  0% {
    opacity: 0;
    transform: translate(0, 0) scale(0.6);
  }

  25% {
    opacity: 0.9;
  }

  100% {
    opacity: 0;
    transform: translate(18px, -34px) scale(1.15);
  }
}

/* A heart flies out of a click along its own direction, drifting up as it fades. */
@keyframes cassieHeart {
  from {
    opacity: 1;
    transform: rotate(var(--speck-turn)) translateX(0) rotate(calc(-1 * var(--speck-turn))) scale(0.6);
  }

  to {
    opacity: 0;
    transform: translateY(-16px) rotate(var(--speck-turn)) translateX(var(--speck-reach)) rotate(calc(-1 * var(--speck-turn))) scale(1.1);
  }
}

@keyframes emberHeat {
  from {
    opacity: 0.5;
  }

  to {
    opacity: 1;
  }
}

/* A band of light crosses the page in a few seconds, then the page rests (Ember, Cassie). */
@keyframes lightSweep {
  0%,
  55% {
    transform: translateX(-70%);
  }

  100% {
    transform: translateX(70%);
  }
}

/* A heading's flare blooms behind it as it arrives and dies down. */
@keyframes emberFlare {
  0% {
    opacity: 0;
  }

  25% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

/* A spark flies out along its own direction, rising as it cools. */
@keyframes emberSpark {
  from {
    opacity: 1;
    transform: rotate(var(--speck-turn)) translateX(0) scale(1);
  }

  to {
    opacity: 0;
    transform: translateY(-34px) rotate(var(--speck-turn)) translateX(var(--speck-reach)) scale(0.4);
  }
}

@keyframes softPulse {
  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.45;
  }
}

@keyframes paperSheen {
  0%,
  72% {
    transform: translateX(-70%);
  }

  100% {
    transform: translateX(70%);
  }
}

/* The sheet is squeezed and twisted as it crumples, then smoothed flat again. */
@keyframes paperCrunch {
  0% {
    transform: none;
  }

  28% {
    transform: scale(0.955) rotate(-0.6deg) skewX(0.8deg);
  }

  52% {
    transform: scale(0.985) rotate(0.3deg);
  }

  100% {
    transform: none;
  }
}

@keyframes paperCreases {
  0% {
    opacity: 0;
  }

  25% {
    opacity: 0.85;
  }

  100% {
    opacity: 0;
  }
}

@keyframes cursorFade {
  to {
    opacity: 0;
    transform: translate(-50%, -50%) rotate(var(--cursor-rotate, 0deg)) scale(0.25);
  }
}

/* A firefly left behind by the cursor: it drifts off, blinking twice before it goes out. */
@keyframes cursorPawFade {
  0%,
  40% {
    opacity: 0.9;
  }

  100% {
    opacity: 0;
  }
}

@keyframes cursorTerminalBlock {
  0%,
  30% {
    opacity: 1;
  }

  31%,
  100% {
    opacity: 0;
  }
}

@keyframes cursorTerminalMark {
  0%,
  30% {
    opacity: 0;
  }

  31% {
    opacity: 0.9;
  }

  100% {
    opacity: 0;
  }
}

@keyframes cursorFirefly {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%);
  }

  15%,
  45% {
    opacity: 1;
  }

  30%,
  60% {
    opacity: 0.25;
  }

  75% {
    opacity: 0.9;
  }

  100% {
    opacity: 0;
    transform: translate(calc(-50% + var(--firefly-x, 0px)), calc(-50% + var(--firefly-y, -20px)));
  }
}

@keyframes cursorPlasmaFade {
  to {
    opacity: 0;
    transform: translate(-50%, -50%) rotate(var(--cursor-rotate, 0deg)) scale(0.15);
    filter: blur(8px) saturate(1.6);
  }
}

@keyframes cursorRingFade {
  to {
    opacity: 0;
    transform: translate(-50%, -50%) rotate(var(--cursor-rotate, 0deg)) scale(1.9);
  }
}

@keyframes cursorConstellationFade {
  to {
    opacity: 0;
    transform: translate(-50%, -50%) rotate(var(--cursor-rotate, 0deg)) scale(1.55);
  }
}

@keyframes cursorScannerFade {
  to {
    opacity: 0;
    transform: translate(-50%, -50%) rotate(var(--cursor-rotate, 0deg)) scaleX(2.1) scaleY(0.15);
  }
}

@keyframes cursorOrbitFade {
  to {
    opacity: 0;
    transform: translate(-50%, -50%) rotate(calc(var(--cursor-rotate, 0deg) + 160deg)) scale(0.35);
  }
}

@keyframes cursorSnowFade {
  to {
    opacity: 0;
    transform: translate(-50%, calc(-50% + 24px)) rotate(calc(var(--cursor-rotate, 0deg) + 70deg)) scale(0.5);
  }
}

@keyframes cursorPulseFade {
  0% {
    opacity: 0.82;
    transform: translate(-50%, -50%) rotate(var(--cursor-rotate, 0deg)) scale(0.55);
  }

  72% {
    opacity: 0.34;
    transform: translate(-50%, -50%) rotate(var(--cursor-rotate, 0deg)) scale(1.45);
  }

  100% {
    opacity: 0;
    transform: translate(-50%, -50%) rotate(var(--cursor-rotate, 0deg)) scale(1.85);
  }
}

@keyframes heroScan {
  0%,
  68% {
    transform: translateX(-110%);
  }

  100% {
    transform: translateX(110%);
  }
}

@keyframes panelFloat {
  0%,
  100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(-8px);
  }
}

@keyframes orbitSpin {
  from {
    transform: rotateX(68deg) rotateZ(-16deg);
  }

  to {
    transform: rotateX(68deg) rotateZ(344deg);
  }
}

@keyframes chipFloat {
  0%,
  100% {
    transform: translate3d(0, 0, 24px);
  }

  50% {
    transform: translate3d(0, -10px, 34px);
  }
}

@media (max-width: 920px) {
  .nav-links {
    display: none;
  }

  .nav-toggle {
    display: inline-grid;
    place-items: center;
  }

  .hero {
    min-height: auto;
  }

  .hero-grid,
  .case-hero-grid,
  .grid.two,
  .grid.three,
  .stats-grid,
  .proof-lanes,
  .skills-grid,
  .services-grid,
  .case-metrics,
  .case-grid,
  .case-flow,
  .cv-paper-head,
  .cv-highlight-strip,
  .cv-paper-grid,
  .cv-project-grid,
  .cv-downloads,
  .admin-layout,
  .todo-layout,
  .site-footer-inner {
    grid-template-columns: 1fr;
  }

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

  .work-card > img,
  .work-media,
  .work-placeholder {
    min-height: 190px;
  }

  .cv-sidebar {
    border-right: 0;
    border-bottom: 1px solid rgba(17, 23, 34, 0.18);
  }

  .timeline-controls {
    justify-content: flex-start;
  }
}

@media (max-width: 620px) {
  .section-inner,
  .site-footer-inner,
  .mobile-nav {
    width: min(var(--max), calc(100% - 20px));
  }

  .hero,
  .case-hero,
  .section,
  .page-hero {
    padding-block: 46px;
  }

  .proof-grid,
  .proof-lanes,
  .case-metrics,
  .case-flow,
  .timeline-item {
    grid-template-columns: 1fr;
  }

  .hero h1,
  .case-hero h1,
  .page-hero h1 {
    font-size: clamp(2.3rem, 14vw, 4rem);
  }

  .work-card > img,
  .work-media,
  .work-placeholder {
    min-height: 170px;
  }

  .cv-paper-head,
  .cv-sidebar,
  .cv-main {
    padding: 22px;
  }

  .cv-role-head {
    display: grid;
  }
}

/* A hyphenated word that must not break at its hyphen. A line ending "open-" with "source"
   on the next reads as a mistake, and a browser breaks there by default. */
.keep-whole {
  white-space: nowrap;
}
