/* ============================================================
   Reset, document, layout primitives, chrome (header/footer)
   ============================================================ */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 1rem);
  /* The page stops dead at its top and bottom instead of stretching
     past them and springing back. Vertical only: sideways overscroll
     is the browser's back and forward swipe. */
  overscroll-behavior-y: none;
}

body {
  min-height: 100dvh;
  /* Column layout so the footer is pinned to the bottom of the viewport
     when the page is short. That leaves no strip of sky below it, so the
     sun rises up out of the footer instead of flashing in a gap first. */
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.6;
  font-feature-settings: "cv11", "ss01";
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
  overscroll-behavior-y: none;
}

/* While the slider moves (theme.js sets data-theming), colour changes
   land at once instead of easing: easing restarted every frame is what
   made a drag stutter. Motion keeps its transitions: panels opening,
   cards sliding, photos and sky frames crossfading. */
html[data-theming] * {
  transition-property: transform, opacity, height, visibility !important;
}

/* Once the live sky covers the whole window the page colour behind it
   can never show, so it stops following the slider; otherwise every
   frame of a drag would repaint the full window underneath the sky. */
html[data-sky-live] body { background: #000; }

#app { flex: 1 0 auto; }   /* grow to push the footer down */

img, svg, video, canvas { display: block; max-width: 100%; }
img { height: auto; background: var(--surface-2); }
input, button, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
ul, ol { list-style: none; padding: 0; }

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

:where(h1, h2, h3, h4) {
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 1.08;
  text-wrap: balance;
}
h1 { font-size: var(--t-h1); }
h2 { font-size: var(--t-h2); }
h3 { font-size: var(--t-h3); letter-spacing: -0.02em; line-height: 1.25; }
p  { text-wrap: pretty; }

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

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}
:focus:not(:focus-visible) { outline: none; }

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

.skip-link {
  position: fixed; top: var(--s-3); left: var(--s-3);
  z-index: 200; padding: var(--s-3) var(--s-4);
  background: var(--accent); color: var(--accent-ink);
  border-radius: var(--r-md); font-weight: 500; font-size: var(--t-sm);
  transform: translateY(-160%);
  transition: transform var(--dur) var(--ease-out);
}
.skip-link:focus-visible { transform: translateY(0); }

/* ---- Layout primitives ----------------------------------- */
.shell { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter); }
.section { padding-block: var(--section-y); }
.section--tight { padding-block: calc(var(--section-y) * 0.6); }
.section + .section { border-top: 1px solid var(--line); }
.lead { font-size: var(--t-lead); color: var(--text-2); max-width: var(--measure); line-height: 1.55; }
.body-2 { color: var(--text-2); max-width: var(--measure); }

.mono {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
}

/* ---- Sky (fixed, behind content, decorative) -------------- */
/* Three layers, back to front. A plain gradient (night blue-black,
   crossfading to a day blue on --day) for the instant before
   anything else has loaded. Still frames of the sky
   (assets/js/sky-poster.js) while the live sky starts, or for good
   where it cannot run. The live WebGL sky on the canvas
   (assets/js/sky.js), which fades in over both. */
.sky {
  position: fixed;
  inset: 0;
  z-index: -1;                 /* above the page background, below content */
  pointer-events: none;
  overflow: hidden;
  background: #000;            /* the page opens on black; the sky fades up from it */
}
.sky::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, #c9dcef 0%, #8db7e3 35%, #4f8fd6 100%);
  opacity: var(--day, 0);
}

.sky__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  transition: opacity 700ms var(--ease-out);
}
.sky[data-sky] .sky__canvas { opacity: 1; }
/* "live" once the canvas has faded in over them: the gradients are
   hidden for good, so a drag does not repaint them behind it. */
.sky[data-sky="live"] { background: none; }
.sky[data-sky="live"]::before { display: none; }

/* Its own stacking context: the frames stack among themselves by
   z-index (sky-poster.js), and the wrapper as a whole stays under
   the canvas. Without it the frames would sit over the canvas and
   hide the live sky until they were removed. */
.sky__poster {
  position: absolute;
  inset: 0;
  isolation: isolate;
}
.sky__frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 100%;   /* keep the horizon when cropped */
  opacity: 0;
  transition: opacity 600ms linear;   /* FADE_MS in sky-poster.js */
}
.sky__frame.is-on { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .sky__frame { transition: none; }
}

/* ---- Grain (fixed, never on a scrolling container) -------- */
.grain {
  position: fixed; inset: 0;
  z-index: var(--z-grain);
  pointer-events: none;
  opacity: var(--grain-opacity);
  background-image: var(--grain-url);
  mix-blend-mode: overlay;
  /* Its own layer: the slider changes its opacity, which a layer takes
     for free, where otherwise the whole window would be redrawn. */
  will-change: opacity;
}

/* ---- Header ---------------------------------------------- */
/* Glass, like the footer: the sky shows through, softened. */
.siteheader {
  position: sticky; top: 0;
  z-index: var(--z-header);
  background: var(--glass-chrome);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur) var(--ease-out), background-color var(--dur) var(--ease-out);
}
.siteheader[data-scrolled="true"] { border-bottom-color: var(--line); }

.siteheader__inner {
  height: var(--header-h);
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex; align-items: center; gap: var(--s-5);
}

.brand { display: inline-flex; align-items: center; gap: var(--s-3); flex-shrink: 0; }
.brand__mark { display: grid; place-items: center; transition: transform var(--dur) var(--ease-out); }
.brand:hover .brand__mark { transform: rotate(-6deg) scale(1.05); }
/* The logo art sits on its own black ground, shown as a small rounded
   tile that reads on both the light and dark header. */
.brand__logo {
  width: 36px; height: 36px;
  border-radius: 9px;
  object-fit: cover;
  display: block;
  box-shadow: 0 0 0 1px var(--line), 0 2px 8px rgb(0 0 0 / 0.35);
}
.brand__text { display: grid; gap: 1px; min-width: 0; }

/* Neon wordmark. Core letterform plus three halo rings. Both the
   core colour and the halo are driven by the theme engine, so the
   glow shifts from white at night to the accent by day instead of
   bleaching out against a light page. */
.brand__word {
  font-weight: 600;
  letter-spacing: -0.01em;
  font-size: var(--t-sm);
  color: var(--neon-core);
  line-height: 1.2;
}
.neon {
  text-shadow:
    0 0 4px  rgb(var(--neon-glow) / calc(var(--neon-strength) * 0.90)),
    0 0 12px rgb(var(--neon-glow) / calc(var(--neon-strength) * 0.55)),
    0 0 32px rgb(var(--neon-glow) / calc(var(--neon-strength) * 0.30));
}

/* A slow breath rather than a flicker. Flicker on a wordmark you
   read on every page gets old fast. */
@media (prefers-reduced-motion: no-preference) {
  .neon { animation: neon-breathe 5.5s var(--ease-in-out) infinite; }
  @keyframes neon-breathe {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.88; }
  }
}

.brand__tagline {
  font-size: var(--t-micro);
  letter-spacing: 0.02em;
  color: var(--text-3);
  line-height: 1.3;
  white-space: nowrap;
}
@media (max-width: 700px) { .brand__tagline { display: none; } }
@media (max-width: 420px) { .brand__word { font-size: var(--t-xs); } }

/* The nav is always visible. There is no burger. On narrow screens
   it wraps to its own row under the brand rather than collapsing. */
.nav { display: flex; flex-wrap: wrap; justify-content: center; margin-inline: auto; gap: var(--s-1); }

@media (max-width: 859px) {
  .siteheader__inner {
    flex-wrap: wrap;
    height: auto;
    min-height: var(--header-h);
    padding-block: var(--s-2);
    row-gap: var(--s-1);
  }
  .nav { order: 3; width: 100%; margin: 0; }
}

.nav a {
  position: relative;
  padding: var(--s-2) var(--s-3);
  font-size: var(--t-sm);
  color: var(--text-2);
  border-radius: var(--r-md);
  transition: color var(--dur-fast) var(--ease-out);
}
.nav a:hover { color: var(--text); }
.nav a::after {
  content: ""; position: absolute;
  left: var(--s-3); right: var(--s-3); bottom: 2px; height: 1.5px;
  background: var(--accent); border-radius: var(--r-full);
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur) var(--ease-out);
}
.nav a[aria-current="page"] { color: var(--text); }
.nav a[aria-current="page"]::after { transform: scaleX(1); }

/* Always pushed to the far right, so the brightness slider sits in the
   top-right corner of the page. */
.siteheader__actions { display: flex; align-items: center; gap: var(--s-2); margin-left: auto; }

.iconbtn {
  width: 38px; height: 38px;
  display: grid; place-items: center;
  border-radius: var(--r-md);
  color: var(--text-2);
  font-size: 1.15rem;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.iconbtn:hover { background: var(--surface-2); color: var(--text); }
.iconbtn:active { transform: scale(0.94); }

/* Route progress bar: honest feedback while a view resolves */
.siteheader__progress { position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; overflow: hidden; }
.siteheader__progress > span {
  display: block; height: 100%; width: 100%;
  background: var(--accent);
  transform: scaleX(0); transform-origin: left;
  transition: transform 240ms var(--ease-out), opacity 200ms 160ms linear;
  opacity: 0;
}
.siteheader__progress > span[data-state="loading"] { transform: scaleX(0.7); opacity: 1; transition-delay: 0s; }
.siteheader__progress > span[data-state="done"] { transform: scaleX(1); opacity: 0; }

/* ---- Footer ---------------------------------------------- */
/* Kept compact: the landing page is a single screen, so every line
   the footer takes is sky it covers. The LinkedIn button sits beside
   the statement wherever the two fit on one row. */
.sitefooter {
  border-top: 1px solid var(--line);
  background: var(--glass-chrome);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}
.sitefooter__inner {
  max-width: var(--maxw); margin-inline: auto;
  padding: var(--s-5) var(--gutter);
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s-4) var(--s-5);
}
.sitefooter__statement { font-size: var(--t-lead); font-weight: 500; letter-spacing: -0.015em; line-height: 1.3; margin-bottom: var(--s-1); }
/* On glass: the stronger accent ink (see pages.css). */
.sitefooter .maillink {
  font-size: var(--t-body);
  color: var(--accent-text-glass);
  border-bottom-color: color-mix(in srgb, var(--accent-text-glass) 35%, transparent);
}
.sitefooter .maillink:hover { border-bottom-color: var(--accent-text-glass); }

.maillink {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-size: var(--t-lead); color: var(--accent-text);
  border-bottom: 1px solid color-mix(in srgb, var(--accent-text) 35%, transparent);
  padding-bottom: 2px;
  transition: border-color var(--dur-fast) var(--ease-out);
}
.maillink i { transition: transform var(--dur) var(--ease-out); }
.maillink:hover { border-bottom-color: var(--accent-text); }
.maillink:hover i { transform: translate(3px, -3px); }

.sitefooter__social { display: flex; }
.sociallink {
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  font-size: var(--t-sm);
  color: var(--text-2);
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}
.sociallink > i:first-child { font-size: 1.125rem; }
.sociallink__out { font-size: 0.95rem; opacity: 0.6; transition: transform var(--dur) var(--ease-out); }
.sociallink:hover { color: var(--text); border-color: var(--line-strong); background: var(--surface); }
.sociallink:hover .sociallink__out { transform: translate(3px, -3px); }

.sitefooter__base {
  max-width: var(--maxw); margin-inline: auto;
  padding: var(--s-3) var(--gutter);
  border-top: 1px solid var(--line);
  display: flex; justify-content: space-between; gap: var(--s-4); flex-wrap: wrap;
  font-size: var(--t-xs); color: var(--text-2);
}

/* ============================================================
   Route transitions
   Native View Transitions where supported, scripted fallback
   everywhere else. Both collapse to instant under reduced motion.
   ============================================================ */

#app { display: block; }

::view-transition-old(page),
::view-transition-new(page) { animation-duration: 380ms; animation-timing-function: var(--ease-out); }
::view-transition-old(page) { animation-name: vt-out; }
::view-transition-new(page) { animation-name: vt-in; }

@keyframes vt-out { to   { opacity: 0; transform: translateY(-10px); } }
@keyframes vt-in  { from { opacity: 0; transform: translateY(14px); } }

/* Fallback for browsers without View Transitions */
#app[data-transition="out"] {
  opacity: 0; transform: translateY(-10px);
  transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out);
}
#app[data-transition="in"] {
  animation: vt-in 380ms var(--ease-out) both;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  ::view-transition-old(page), ::view-transition-new(page) { animation: none; }
  #app[data-transition="out"], #app[data-transition="in"] { opacity: 1; transform: none; animation: none; transition: none; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
