/* ============================================================
   Tokens
   ============================================================ */
:root {
  color-scheme: light;
  --bg: #f5f5f5;
  --ink: #050505;
  --ink-60: rgba(5, 5, 5, 0.6);
  --ink-10: rgba(5, 5, 5, 0.1);
  --chip: #e5e5e5;
  --surface: #fff;        /* calendar frame, cookie notice */
  --band: #050505;        /* the dark About and footer bands */
  --band-ink: #f5f5f5;
  --live: #47c554;

  --gutter: clamp(16px, 4.2vw, 64px);
  --track: -0.06em;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --header-h: 60px;
}

/* Dark theme: only when the visitor picks it with the header toggle (data-theme on <html>,
   see the inline script in <head>). Every visit starts light, whatever the system setting. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #1e1e1e;
  --ink: #ececec;
  --ink-60: rgba(236, 236, 236, 0.62);
  --ink-10: rgba(236, 236, 236, 0.12);
  --chip: #2c2c2c;
  --surface: #262626;
  --band: #282828;
  --band-ink: #ececec;
}

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

html { scroll-behavior: smooth; scrollbar-gutter: stable; -webkit-text-size-adjust: 100%; }
/* Lenis smooth scrolling */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-stopped { overflow: clip; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: "Manrope", system-ui, sans-serif;
  font-weight: 600;
  font-size: 16px;
  line-height: 1.4;
  letter-spacing: var(--track);
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}

/* Film grain over everything */
body::after {
  content: "";
  position: fixed;
  inset: -50%;
  z-index: 100;
  pointer-events: none;
  opacity: 0.07;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 1.4 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }
h1, h2, h3, h4, p, ul, dl, dd, figure { margin: 0; padding: 0; }
h1, h2, h3, h4 { font-weight: 600; font-size: inherit; }
ul { list-style: none; }
button { font: inherit; letter-spacing: inherit; color: inherit; background: none; border: 0; cursor: pointer; padding: 0; }

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

.eyebrow { font-size: 12px; color: var(--ink-60); margin-bottom: 12px; }
.muted { color: var(--ink-60); }

.display-md { font-size: clamp(40px, 4.4vw, 64px); line-height: 1.2; }
.display-xl { font-size: clamp(64px, 11vw, 168px); line-height: 1; letter-spacing: -0.05em; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 44px; padding: 0 20px;
  border: 1px solid transparent;
  font-size: 14px;
  white-space: nowrap;
  transition: transform 0.4s var(--ease), background-color 0.3s, color 0.3s, border-color 0.3s;
}
.btn-dark { background: var(--ink); color: var(--bg); }
.btn-line { border-color: var(--ink-10); }
.btn-line:hover { border-color: var(--ink); }
.btn .material-symbols-outlined { transition: transform 0.45s var(--ease); }
@media (hover: hover) {
  .btn-dark:hover { transform: scale(1.04); }
  .btn:hover .material-symbols-outlined[data-dir="out"] { transform: translate(2px, -2px); }
  .btn:hover .material-symbols-outlined[data-dir="down"] { transform: translateY(2px); }
}
:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }

.status { display: inline-flex; align-items: center; gap: 8px; font-size: 14px; }
.dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--live);
  box-shadow: 0 0 0 0 rgba(71, 197, 84, 0.5);
  animation: ping 2.2s infinite;
}
@keyframes ping {
  0% { box-shadow: 0 0 0 0 rgba(71, 197, 84, 0.5); }
  70%, 100% { box-shadow: 0 0 0 7px rgba(71, 197, 84, 0); }
}
/* 10 PM to 6 AM in Manila (class set in JS): the dot hides and three z's float up after the text in turn. */
.zzz { display: none; position: relative; width: 16px; height: 16px; font-style: normal; color: var(--ink-60); }
.status.is-asleep .dot { display: none; }
.status.is-asleep .zzz { display: inline-block; }
.zzz b { position: absolute; font-weight: 700; line-height: 1; animation: zzz 2.4s ease-out infinite both; }
.zzz b:nth-child(1) { left: 0; bottom: 0; font-size: 7px; }
.zzz b:nth-child(2) { left: 5px; bottom: 3px; font-size: 9px; animation-delay: 0.4s; }
.zzz b:nth-child(3) { left: 10px; bottom: 6px; font-size: 11px; animation-delay: 0.8s; }
@keyframes zzz {
  0% { opacity: 0; transform: translate(-2px, 4px) scale(0.6); }
  30% { opacity: 1; transform: none; }
  75%, 100% { opacity: 0; transform: translate(3px, -5px); }
}

.clock { display: inline-flex; align-items: baseline; gap: 8px; font-size: 14px; white-space: nowrap; }
.clock-time { color: var(--ink-60); font-variant-numeric: tabular-nums; }

/* Material Symbols: shared defaults */
.material-symbols-outlined {
  font-variation-settings: "FILL" 0, "wght" 500, "GRAD" 0, "opsz" 20;
  user-select: none;
}
.btn .material-symbols-outlined { font-size: 18px; }

/* ============================================================
   Header
   ============================================================ */
.site-header {
  position: fixed; inset: 0 0 auto 0; z-index: 50;
  height: var(--header-h);
  padding: 0 var(--gutter);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 24px;
  background: var(--bg);
  animation: header-in 1s 0.55s var(--ease) both;
}
@keyframes header-in { from { opacity: 0; translate: 0 -10px; } }
.brand { font-size: 18px; justify-self: start; }
.header-center { display: flex; align-items: center; gap: 28px; }
.header-actions { justify-self: end; display: flex; align-items: center; gap: 20px; }
.theme-btn { display: grid; place-items: center; width: 32px; height: 32px; margin-right: -6px; }
.theme-btn .material-symbols-outlined { font-size: 20px; }
/* Show the icon for the theme you'd switch to */
.theme-btn .icon-light, :root.is-dark .theme-btn .icon-dark { display: none; }
:root.is-dark .theme-btn .icon-light { display: inline; }
/* The incoming icon fades in as the themes swap */
.theme-btn .material-symbols-outlined { animation: theme-icon-in 0.6s ease-out; }
@keyframes theme-icon-in { from { opacity: 0; rotate: -30deg; } }
/* Theme switch: JS adds .theme-fading for a moment so every colour eases to the new theme */
:root.theme-fading,
:root.theme-fading *,
:root.theme-fading *::before,
:root.theme-fading *::after {
  transition: background-color 0.6s ease, color 0.6s ease, border-color 0.6s ease, fill 0.6s ease, box-shadow 0.6s ease !important;
}

.menu-btn { display: inline-flex; align-items: center; gap: 10px; font-size: 14px; }
.menu-icon { font-size: 20px; }

/* ============================================================
   Hero
   ============================================================ */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: var(--header-h) 0 clamp(40px, 6vh, 72px);
  overflow: hidden;
}

.hero-marquee {
  position: absolute; left: 0; right: 0; top: 50%;
  transform: translateY(-58%);
  font-size: clamp(88px, 17vw, 280px);
  line-height: 1;
  letter-spacing: -0.06em;
  white-space: nowrap;
  pointer-events: none;
  animation: marquee-in 1.6s 0.2s var(--ease) both;
}
@keyframes marquee-in { from { opacity: 0; translate: 0 8%; } }
/* JS drives the hero loop (speeds up with scroll velocity) */
.hero .marquee-track.is-js { animation: none; }

.marquee-track {
  display: inline-flex;
  animation: marquee 28s linear infinite;
  will-change: transform;
}
.marquee-track > * { flex: none; }
@keyframes marquee { to { transform: translateX(-50%); } }
/* Separator between repeats of the name: a drawn bar rather than a typed dash */
.marquee-sep {
  display: inline-block; vertical-align: middle;
  width: 0.5em; height: 0.06em; margin: 0 0.3em;
  background: currentColor;
}

.hero-portrait {
  position: absolute; left: 50%; top: 50%;
  width: clamp(180px, 22vw, 340px);
  aspect-ratio: 4 / 5;
  translate: -50% -58%;
  overflow: hidden;
  background: var(--chip);
  z-index: 1;
  /* Solid from the first frame (no fade, so the name never shows through it); it only scales in */
  animation: portrait-zoom 1.4s 0.1s var(--ease) both;
}
.hero-portrait img { width: 100%; height: 100%; object-fit: cover; }
/* Scrolling out of the hero: the frame scrolls with the page while the photo inside it
   drifts down (parallax), and the intro fades (--hero-p 0 → 1, set in JS). The photo is
   scaled up 14% so the drift (capped at 7%) never reveals an edge. */
.hero-portrait img { scale: 1.14; translate: 0 min(calc(var(--hero-p, 0) * 10%), 7%); }
.hero-foot { opacity: calc(1 - var(--hero-p, 0) * 1.8); }
@keyframes portrait-zoom { from { scale: 1.3; } to { scale: 1; } }

.hero-foot {
  position: relative; z-index: 2;
  display: flex; justify-content: center;
  padding: 0 var(--gutter);
}

.hero-intro {
  max-width: 440px;
  font-size: 18px;
  text-wrap: balance;
  color: var(--ink-60);
  text-align: center;
}

/* ============================================================
   Works
   ============================================================ */
.works { padding: clamp(80px, 12vw, 160px) var(--gutter) clamp(100px, 12vw, 160px); }

.section-head {
  display: flex; justify-content: space-between; align-items: center;
  margin-bottom: 40px;
}
.section-head sup {
  font-size: 12px; vertical-align: top; position: relative; top: -0.4em; margin-left: 4px;
  letter-spacing: -0.02em;
}

.work-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 16px;
}
.work {
  position: relative;
  aspect-ratio: 4 / 3.1;
  overflow: hidden;
  background: #111;
  color: var(--bg);
  isolation: isolate;
}
.work img {
  width: 100%; height: 100%; object-fit: cover;
  transform: scale(1.1);
  translate: 0 calc(var(--py, 0) * 4%);
  transition: transform 1.1s var(--ease), filter 0.8s var(--ease);
}
/* Entrance: the image settles from a closer crop as the card fades in */
.work.reveal img { transform: scale(1.28); transition: transform 1.6s var(--ease), filter 0.8s var(--ease); }
.work.reveal.is-in img { transform: scale(1.1); }
.work::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0) 45%);
}
/* Hover: zoom in, darken and soften the image so the title stands out */
.work.reveal:hover img, .work.reveal:focus-visible img { transform: scale(1.16); filter: brightness(0.55) blur(3px); transition: transform 1.1s var(--ease), filter 0.8s var(--ease); }
.work-meta {
  position: absolute; left: clamp(16px, 2.2vw, 28px); bottom: clamp(16px, 2.2vw, 28px); z-index: 2;
}
.work-meta h3 {
  font-size: clamp(26px, 2.8vw, 40px); line-height: 1.2; font-weight: 600;
  color: rgba(245, 245, 245, 0.82);
  transform-origin: left bottom;
  transition: transform 0.7s var(--ease), color 0.6s var(--ease);
}
.work:hover .work-meta h3, .work:focus-visible .work-meta h3 { color: #fff; transform: translateX(6px) scale(1.12); }

/* ============================================================
   About
   ============================================================ */
.about {
  /* Dark band in both themes: local token overrides recolour everything inside */
  --ink-60: rgba(245, 245, 245, 0.6);
  --ink-10: rgba(245, 245, 245, 0.14);
  --chip: color-mix(in srgb, var(--band-ink) 10%, var(--band));
  background: var(--band);
  color: var(--band-ink);
  padding: clamp(80px, 10vw, 140px) var(--gutter) clamp(80px, 10vw, 140px);
}
.about .btn-dark { background: var(--band-ink); color: var(--band); }
.about-grid {
  display: grid;
  grid-template-columns: 1.25fr 1fr;
  gap: clamp(32px, 6vw, 96px);
}
.about-copy p:not(.eyebrow) { font-size: clamp(18px, 1.6vw, 22px); line-height: 1.4; }
.about-copy p + p { margin-top: 16px; }

.about-side { display: flex; flex-direction: column; gap: 28px; }
.stats { display: grid; grid-template-columns: 1fr 1fr; gap: 28px 24px; }
.stats div { display: flex; flex-direction: column-reverse; gap: 6px; }
.stats dt { font-size: 14px; color: var(--ink-60); }
.stats dd { font-size: 22px; font-variant-numeric: tabular-nums; }
.about-side .btn { align-self: flex-start; }

/* ============================================================
   Experience (accordion)
   ============================================================ */
.xp-block { margin-top: clamp(112px, 14vw, 200px); }

/* Skills carousel: two full-bleed rows drifting in opposite directions */
.skills { margin-top: clamp(64px, 8vw, 112px); }
.skills-row {
  display: flex;
  margin: 0 calc(-1 * var(--gutter));
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
}
.skills-row + .skills-row { margin-top: 12px; }
.skills-track {
  flex: none;
  display: flex;
  gap: 12px;
  padding: 0 12px 0 0;
  margin: 0;
  list-style: none;
  animation: skills-scroll 48s linear infinite;
}
.skills-row-rev .skills-track { animation-direction: reverse; animation-duration: 56s; }
.skills-row:hover .skills-track { animation-play-state: paused; }
.skills-track li {
  padding: 14px 26px;
  border: 1px solid rgba(245, 245, 245, 0.18);
  font-size: clamp(18px, 1.7vw, 26px);
  white-space: nowrap;
  transition: background-color 0.3s, color 0.3s, border-color 0.3s;
}
.skills-track li:hover { background: var(--band-ink); color: var(--band); border-color: var(--band-ink); }
@keyframes skills-scroll { to { transform: translateX(-100%); } }
.xp-block > .eyebrow { margin-bottom: 16px; }

.xp-list { scroll-margin-top: calc(var(--header-h) + 24px); list-style: none; margin: 0; padding: 0; border-bottom: 1px solid var(--ink-10); }
.xp { border-top: 1px solid var(--ink-10); }
.xp-h { font: inherit; letter-spacing: inherit; }

.xp-head,
.xp-inner {
  display: grid;
  grid-template-columns: var(--xp-cols, minmax(48px, 1fr) 1.6fr 32px);
  gap: 24px;
}
.xp-head {
  width: 100%;
  align-items: center;
  padding: clamp(20px, 2.2vw, 30px) 0;
  text-align: left;
}
.xp-num { font-size: 16px; }
.xp-heading { display: flex; flex-direction: column; gap: 6px; }
.xp-heading { transition: transform 0.6s var(--ease); }
/* Opened details fade in one after another (--xd), and fade straight out on close */
.xp-inner > * { opacity: 0; transition: opacity 0.3s var(--ease), transform 0.6s var(--ease); }
.xp.is-open .xp-inner > * { opacity: 1; transition: opacity 0.6s var(--ease) var(--xd, 0.1s), transform 0.6s var(--ease); }
.xp.is-open .xp-inner > :nth-child(2) { --xd: 0.16s; }
.xp.is-open .xp-inner > :nth-child(3) { --xd: 0.22s; }
.xp.is-open .xp-inner > :nth-child(4) { --xd: 0.28s; }
.xp.is-open .xp-inner > :nth-child(n + 5) { --xd: 0.34s; }
.xp-title { font-size: clamp(26px, 3.2vw, 48px); line-height: 1.1; }
.xp-meta { font-size: 14px; color: var(--ink-60); letter-spacing: -0.02em; word-spacing: 0.1em; }
.xp-arrow {
  justify-self: end;
  font-size: 24px;
  transition: transform 0.5s var(--ease);
}
/* Focus: the open or hovered row stays full strength, the rest fade back */
.xp-head, .xp-inner { transition: opacity 0.4s var(--ease); }
.xp-list:has(.xp.is-open) .xp:not(.is-open) :is(.xp-head, .xp-inner) { opacity: 0.35; }
@media (hover: hover) {
  .xp-list:has(.xp:hover) .xp:not(:hover) :is(.xp-head, .xp-inner) { opacity: 0.35; }
  .xp-list:has(.xp:hover) .xp:hover :is(.xp-head, .xp-inner) { opacity: 1; }
}
/* Hovering anywhere on a row nudges its title, meta and open description together */
@media (hover: hover) {
  .xp:hover .xp-heading, .xp:hover .xp-inner > * { transform: translateX(8px); }
}
.xp.is-open .xp-arrow { transform: rotate(180deg); }

/* Smooth height: animate grid row from 0fr to 1fr */
.xp-body {
  display: grid;
  grid-template-rows: 0fr;
  /* visibility keeps links in closed rows out of the tab order */
  visibility: hidden;
  transition: grid-template-rows 0.6s var(--ease), visibility 0s 0.6s;
}
.xp.is-open .xp-body { grid-template-rows: 1fr; visibility: visible; transition: grid-template-rows 0.6s var(--ease); }
.xp.is-open .xp-body { cursor: pointer; }
.xp-body > .xp-inner { min-height: 0; overflow: hidden; row-gap: 0; }
.xp-inner > * { grid-column: 2; }
.xp-inner > :last-child { margin-bottom: clamp(24px, 2.6vw, 36px); }
.xp-inner p { max-width: 62ch; font-size: 17px; line-height: 1.5; }
.xp-inner ul { list-style: none; margin: 14px 0 0; padding: 0; max-width: 62ch; }
.xp-inner li {
  position: relative;
  padding-left: 22px;
  color: var(--ink-60);
  line-height: 1.5;
}
.xp-inner li + li { margin-top: 8px; }
.xp-inner li::before { content: ""; position: absolute; left: 0; top: 0.75em; width: 10px; height: 1px; background: currentColor; }
.xp-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.xp-link { justify-self: start; margin-top: 24px; }

.xp-more,
.xp-head,
.xp-inner { --xp-cols: minmax(48px, 1fr) 1.6fr 32px; }
.xp-more {
  display: grid;
  grid-template-columns: var(--xp-cols);
  gap: 24px;
  padding-top: clamp(48px, 5vw, 72px);
}
.xp-more > .btn { grid-column: 1 / -1; justify-self: center; }

.xp-tags span {
  padding: 6px 12px;
  background: var(--chip);
  font-size: 13px;
}

/* ============================================================
   How I work
   ============================================================ */
.process { padding: clamp(100px, 12vw, 160px) var(--gutter) clamp(20px, 3vw, 40px); }
/* Desktop: the section pins for a full screen while ~2 more screens of scrolling
   move the progress through steps 01 → 05 (driven in JS) */
@media (min-width: 961px) {
  .process { height: 300vh; padding: 0 var(--gutter); }
  .process-pin {
    position: sticky; top: 0;
    height: 100vh; height: 100svh;
    display: flex; flex-direction: column; justify-content: center;
    padding-top: var(--header-h);
  }
  .process-pin .section-head { margin-bottom: clamp(48px, 6vh, 80px); }
}
.process-lead { font-size: 15px; }
.process-list {
  --p: 0;
  position: relative;
  list-style: none; margin: 0; padding: 0;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: clamp(16px, 2vw, 32px);
}
/* Scroll progress: a faint track between the first and last markers (positions set in JS),
   with an ink fill that grows as you scroll. Horizontal on desktop, vertical when stacked. */
.process-list::before,
.process-list::after {
  content: ""; position: absolute; pointer-events: none;
  top: 16px; left: var(--t0, 0px);
  width: calc(var(--t1, 0px) - var(--t0, 0px)); height: 1px;
  background: var(--ink-10);
}
.process-list::after { background: var(--ink); transform: scaleX(var(--p)); transform-origin: left; }
.step { position: relative; display: flex; flex-direction: column; gap: 14px; }
.step-num {
  position: relative; z-index: 1;
  display: grid; place-items: center;
  width: 32px; height: 32px; margin-bottom: 10px;
  border: 1px solid var(--ink-10); border-radius: 50%;
  background: var(--bg);
  font-size: 12px; color: var(--ink-60); font-variant-numeric: tabular-nums;
  transition: background-color 0.5s var(--ease), border-color 0.5s var(--ease), color 0.5s var(--ease);
}
.step-title { margin: 0; font-size: clamp(26px, 2.4vw, 36px); line-height: 1.1; }
.step-copy { margin: 0; font-size: 15px; line-height: 1.5; color: var(--ink-60); max-width: 32ch; }
.step-title, .step-copy { transition: opacity 0.6s var(--ease); }
/* Steps light up as the progress line reaches them (only once JS is running) */
.process-list.is-live .step:not(.is-active) :is(.step-title, .step-copy) { opacity: 0.28; }
.step.is-active .step-num { background: var(--ink); border-color: var(--ink); color: var(--bg); }

/* ============================================================
   Book a call (Cal.com)
   ============================================================ */
.book { padding: clamp(80px, 10vw, 140px) var(--gutter) clamp(80px, 9vw, 130px); }
.book-inner { position: relative; z-index: 1; max-width: 1080px; margin: 0 auto; }

/* Giant faded "Book a call": the calendar frame slides over the bottom of the letters */
.wordmark {
  /* Sized so the text spans the calendar frame; JS fine-tunes it to the exact width */
  --wordmark-size: calc(min(1080px, 100vw - 2 * var(--gutter)) / 4.3);
  margin: 0 0 calc(-0.3 * var(--wordmark-size));
  text-align: center;
  white-space: nowrap;
  font-size: var(--wordmark-size);
  line-height: 1;
  letter-spacing: -0.07em;
  user-select: none;
}
.wordmark span {
  display: inline-block;
  /* Room for the last letter to paint, without shifting the centered text */
  padding-right: 0.07em;
  margin-right: -0.07em;
  background: linear-gradient(to bottom, color-mix(in srgb, var(--ink) 16%, transparent), color-mix(in srgb, var(--ink) 2%, transparent) 75%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.book-frame {
  padding: 8px;
  background: var(--surface);
  border: 1px solid var(--ink-10);
  box-shadow: 0 30px 60px -30px rgba(5, 5, 5, 0.25);
}
.book-cal { display: none; width: 100%; min-height: 560px; overflow: auto; }
.book.has-cal .book-cal { display: block; }
.book.has-cal .book-fallback { display: none; }
/* "Scroll for times" tag over the bottom of the calendar (small screens only, see 960px query) */
.book-hint {
  display: none;
  position: absolute; left: 50%; bottom: 20px; z-index: 2;
  translate: -50% 0;
  align-items: center; gap: 6px;
  padding: 8px 14px 8px 16px;
  background: var(--ink); color: var(--bg);
  font-size: 13px;
  pointer-events: none;
  box-shadow: 0 10px 24px -10px rgba(5, 5, 5, 0.45);
  transition: opacity 0.4s var(--ease), translate 0.4s var(--ease);
}
.book-hint .material-symbols-outlined { font-size: 16px; animation: hint-bob 1.6s var(--ease) infinite; }
@keyframes hint-bob { 0%, 100% { transform: translateY(-2px); } 50% { transform: translateY(3px); } }

/* Entrance: "Book a call" lifts up first, then the calendar rises over it */
.book-frame, .book-mark span {
  opacity: 0;
  transition: opacity 1s var(--ease), transform 1.4s var(--ease);
}
.book-frame { transform: translateY(96px); }
.book-mark span { transform: translateY(45%); }
.book.is-in .book-frame, .book.is-in .book-mark span { opacity: 1; transform: none; }
.book.is-in .book-frame { transition-delay: 0.45s; }
.book-fallback {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
  min-height: 360px; padding: 40px 24px;
  border: 1px solid var(--ink-10);
  text-align: center;
}
.book-fallback-title { font-size: clamp(22px, 2vw, 28px); }
.book-fallback .muted { max-width: 400px; margin-bottom: 12px; }
.book-fallback-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; }
.book-alt { margin-top: 24px; text-align: center; font-size: 14px; }
.book-alt a { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }

/* ============================================================
   Footer (black)
   ============================================================ */

.site-footer {
  --ink-60: rgba(245, 245, 245, 0.55);
  --ink-10: rgba(245, 245, 245, 0.12);
  position: relative;
  z-index: 1;
  background: var(--band);
  color: var(--band-ink);
  padding: clamp(56px, 6vw, 88px) var(--gutter) 0;
  font-size: 14px;
}
.footer-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
  padding-bottom: 40px;
}
.footer-about { display: flex; flex-direction: column; gap: 12px; }
.footer-name { font-size: 18px; }
.footer-about .muted { font-size: 15px; }
.footer-about .socials { margin-top: 12px; }

.footer-nav { display: flex; flex-direction: column; }
.footer-nav a, .menu-links a {
  font-size: clamp(28px, 2.6vw, 38px); line-height: 1.2;
  transition: opacity 0.3s, transform 0.5s var(--ease);
}
.footer-nav:hover a, .menu-links:hover a { opacity: 0.35; }
.footer-nav a:hover, .menu-links a:hover { opacity: 1; transform: translateX(6px); }

.socials { display: flex; gap: 12px; }
.socials a { width: 22px; height: 22px; transition: opacity 0.3s; }
.socials a:hover { opacity: 0.5; }
.socials svg { width: 100%; height: 100%; }

.footer-bar {
  display: flex; justify-content: space-between; gap: 16px;
  padding: 20px 0 28px;
  border-top: 1px solid var(--ink-10);
  font-size: 13px;
  color: var(--ink-60);
}
.footer-legal { display: flex; flex-wrap: wrap; gap: 6px 20px; }
.footer-legal :is(a, button) { font-size: inherit; color: inherit; transition: color 0.3s; }
.footer-legal :is(a, button):hover { color: var(--band-ink); }

/* Back to top: a round button fixed to the lower right. It fades in once the hero is scrolled
   past, and JS adds .on-band while it sits over the About section or the footer so it flips colour. */
.to-top {
  position: fixed; right: var(--gutter); bottom: var(--gutter); z-index: 55;
  display: grid; place-items: center;
  width: 48px; height: 48px; border-radius: 50%;
  background: var(--ink); color: var(--bg);
  opacity: 0; translate: 0 12px; visibility: hidden;
  transition: opacity 0.5s var(--ease), translate 0.6s var(--ease), visibility 0s 0.6s, background-color 0.3s, color 0.3s, scale 0.4s var(--ease);
}
.to-top.is-in { opacity: 1; translate: none; visibility: visible; transition-delay: 0s; }
.to-top.on-band { background: var(--band-ink); color: var(--band); }
.to-top .material-symbols-outlined { font-size: 20px; transition: transform 0.4s var(--ease); }
@media (hover: hover) {
  .to-top:hover { scale: 1.06; }
  .to-top:hover .material-symbols-outlined { transform: translateY(-2px); }
}

/* ============================================================
   Menu drawer
   ============================================================ */
.menu {
  position: fixed; inset: 0; z-index: 60;
  visibility: hidden;
  transition: visibility 0s 0.8s;
}
.menu-open .menu { visibility: visible; transition: none; }

.menu-backdrop {
  position: absolute; inset: 0;
  background: rgba(20, 20, 20, 0.55);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  opacity: 0;
  transition: opacity 0.6s var(--ease);
}
.menu-open .menu-backdrop { opacity: 1; }

.menu-stage {
  position: absolute; inset: 0 40% 0 0;
  display: grid; place-items: center;
  pointer-events: none;
  overflow: hidden;
}
.menu-marquee {
  position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%);
  color: var(--bg);
  font-size: clamp(40px, 4.4vw, 64px); line-height: 1.2;
  white-space: nowrap;
  opacity: 0;
  /* Closing: fade out with no delay, in step with the panel and backdrop */
  transition: opacity 0.6s var(--ease);
}
.menu-portrait {
  position: relative; z-index: 1;
  width: clamp(120px, 14vw, 200px); height: auto; aspect-ratio: 4 / 5; object-fit: cover;
  opacity: 0;
  transition: opacity 0.6s var(--ease);
}
/* Opening keeps the staggered entrance */
.menu-open .menu-marquee { opacity: 1; transition: opacity 0.6s 0.2s; }
.menu-open .menu-portrait { opacity: 1; transition: opacity 0.4s 0.1s ease-out; }

.menu-panel {
  position: absolute; top: 0; right: 0; bottom: 0;
  width: min(40%, 560px);
  min-width: 320px;
  background: var(--bg);
  display: flex; flex-direction: column;
  padding-top: var(--header-h);
  transform: translateX(100%);
  transition: transform 0.8s var(--ease);
}
.menu-open .menu-panel { transform: none; }
.menu-block { padding: 28px clamp(16px, 2.4vw, 32px); }
.menu-block + .menu-block { border-top: 1px solid var(--ink-10); }
.menu-block:first-of-type { flex: 1; }
.menu-links { display: flex; flex-direction: column; align-items: flex-start; gap: clamp(10px, 1.4vh, 18px); }
.menu-links a { font-size: clamp(40px, 3.8vw, 56px); line-height: 1.1; }
.menu-open .menu-links a { animation: menu-link-in 0.9s var(--ease) backwards; animation-delay: calc(0.22s + var(--i, 0) * 0.06s); }
@keyframes menu-link-in { from { opacity: 0; translate: 0 18px; } }
.menu-email { font-size: clamp(16px, 4.4vw, 20px); overflow-wrap: anywhere; }
.menu-foot { padding-block: 20px; }

.menu-open { overflow: hidden; }
.menu-close {
  position: absolute; top: 0; right: var(--gutter);
  height: var(--header-h);
  display: inline-flex; align-items: center; gap: 10px;
  font-size: 14px;
}
.menu-close .material-symbols-outlined { font-size: 20px; }

/* ============================================================
   Cookie notice
   ============================================================ */
.consent {
  --inset: clamp(12px, 1.4vw, 20px);  /* tucked closer to the corner than the page gutter */
  position: fixed; right: var(--inset); bottom: var(--inset); z-index: 90;
  width: min(420px, calc(100% - 2 * var(--inset)));
  padding: 20px;
  background: var(--surface);
  border: 1px solid var(--ink-10);
  box-shadow: 0 24px 48px -24px rgba(5, 5, 5, 0.35);
  font-size: 14px; line-height: 1.5;
  opacity: 0; translate: 0 16px;
  transition: opacity 0.5s var(--ease), translate 0.6s var(--ease);
}
.consent[hidden] { display: none; }
.consent.is-in { opacity: 1; translate: none; }
.consent-title { font-size: 16px; margin-bottom: 6px; }
.consent p:not(.consent-title) { color: var(--ink-60); }
.consent a { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
.consent-actions { display: flex; gap: 8px; margin-top: 16px; }
.consent-actions .btn { flex: 1; }
/* The notice shares the lower-right corner with the back-to-top button, so the button steps aside */
:root.consent-open .to-top { opacity: 0; visibility: hidden; pointer-events: none; }

/* ============================================================
   Legal pages and 404
   ============================================================ */
.page-header { grid-template-columns: 1fr auto; }
.back-link { display: inline-flex; align-items: center; gap: 6px; font-size: 14px; }
.back-link .material-symbols-outlined { font-size: 18px; transition: transform 0.45s var(--ease); }
@media (hover: hover) { .back-link:hover .material-symbols-outlined { transform: translateX(-3px); } }
.legal {
  max-width: 760px; margin: 0 auto;
  padding: calc(var(--header-h) + clamp(56px, 8vw, 112px)) var(--gutter) clamp(80px, 10vw, 140px);
  font-weight: 500; letter-spacing: -0.02em;
}
.legal h1 { font-size: clamp(40px, 5.6vw, 72px); line-height: 1.05; letter-spacing: -0.06em; font-weight: 600; }
.legal-updated { margin: 16px 0 48px; color: var(--ink-60); font-size: 14px; }
.legal h2 { margin: 48px 0 12px; font-size: clamp(22px, 2vw, 28px); line-height: 1.2; letter-spacing: -0.04em; }
.legal p, .legal li { font-size: 17px; line-height: 1.6; color: var(--ink-60); }
.legal p + p { margin-top: 12px; }
.legal strong { color: var(--ink); font-weight: 600; }
.legal ul { margin: 12px 0 0; padding: 0; }
.legal li { position: relative; padding-left: 22px; }
.legal li + li { margin-top: 8px; }
.legal li::before { content: ""; position: absolute; left: 0; top: 0.8em; width: 10px; height: 1px; background: currentColor; }
.legal :is(a, .text-link) { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
.legal .text-link { font: inherit; letter-spacing: inherit; cursor: pointer; }

.not-found {
  min-height: 100svh;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px;
  padding: calc(var(--header-h) + 40px) var(--gutter) 80px;
  text-align: center;
}
.not-found .wordmark { --wordmark-size: clamp(120px, 28vw, 360px); margin: 0; }
.not-found h1 { font-size: clamp(32px, 4vw, 56px); line-height: 1.1; }
.not-found p { max-width: 420px; color: var(--ink-60); }
.not-found-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 8px; }

/* ============================================================
   Reveal on scroll
   ============================================================ */
.reveal {
  opacity: 0;
  transform: translateY(32px);
  filter: blur(8px);
  transition: opacity 1s var(--ease), transform 1.2s var(--ease), filter 1s var(--ease);
  transition-delay: var(--d, 0s);
}
.reveal.is-in { opacity: 1; transform: none; filter: none; }

/* ============================================================
   Stacking cards
   ============================================================ */
/* Only where the colour changes: a .card pins once its bottom edge meets the bottom of
   the viewport (--stick-top set in JS) and stays put while the next section slides up
   over it. Same-colour neighbours just scroll. */
.stack { position: relative; }
.card {
  position: sticky;
  top: var(--stick-top, 0px);
  isolation: isolate;
  background: var(--bg);
}
.card.about { background: var(--band); }
/* A pinned card stays stuck (hidden) until the end of main, so every other section has
   to be positioned with a solid background to paint over it */
.stack > :not(.card) {
  position: relative; background: var(--bg);
  /* Covers the hairline seam at fractional section boundaries */
  box-shadow: 0 -1px 0 var(--bg);
}
/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 960px) {
  /* Cal.com stacks the calendar and every time slot on small screens, so cap the
     iframe's height (overriding Cal's inline height) and let Cal scroll inside its own
     frame. Scrolling the iframe itself (not a wrapper) lets the browser hand the scroll
     back to the page at either end, so visitors can carry on to the footer */
  .book-frame { position: relative; }
  /* The iframe is one scrollbar wider than the frame (--sbw, measured in JS) and the extra
     is clipped, so Cal's own scrollbar doesn't eat into the layout width and squeeze it
     into scrolling sideways. Clipping (not scrolling) keeps the scroll hand-off working. */
  .book.has-cal .book-cal { min-height: 0; overflow: hidden; }
  .book.has-cal .book-cal iframe {
    height: min(720px, 78svh) !important;
    width: calc(100% + var(--sbw, 0px)) !important;
    max-width: none;
    flex: none; /* Cal's wrapper is a flex row and would shrink it back */
  }
  .book.has-cal .book-frame::after {
    content: ""; position: absolute; left: 1px; right: 1px; bottom: 1px; height: 96px;
    background: linear-gradient(to top, var(--surface) 20%, transparent);
    pointer-events: none;
    transition: opacity 0.4s var(--ease);
  }
  .book.has-cal .book-hint { display: inline-flex; }
  /* Fade and hint go away once the visitor gets into the calendar, or if it fits */
  .book-frame:is(.hint-done, .no-scroll)::after { opacity: 0; }
  .book-frame:is(.hint-done, .no-scroll) .book-hint { opacity: 0; translate: -50% 8px; }
  /* Portrait gets relatively bigger as the screen narrows */
  .hero-portrait { width: min(44vw, 340px); }
  .site-header { grid-template-columns: 1fr auto; }
  .header-center { display: none; }
  .about-grid { grid-template-columns: 1fr; }
  .about-side .btn { align-self: center; }
  .menu-stage { display: none; }
  .menu-panel { width: 100%; min-width: 0; }
}

@media (max-width: 960px) {
  .process-list { grid-template-columns: 1fr; gap: 0; }
  .process-list::before,
  .process-list::after { top: var(--t0, 0px); left: 15.5px; width: 1px; height: calc(var(--t1, 0px) - var(--t0, 0px)); }
  .process-list::after { transform: scaleY(var(--p)); transform-origin: top; }
  .step { display: grid; grid-template-columns: 32px 1fr; column-gap: 24px; row-gap: 10px; padding-bottom: 48px; }
  .step:last-child { padding-bottom: 0; }
  .step-num { grid-row: span 2; margin: 0; }
  .step-title { padding-top: 2px; }
  .step-copy { max-width: 46ch; }
}

@media (max-width: 640px) {
  /* Menu becomes a full-screen dropdown: the panel unrolls from the top, then its content fades in */
  .menu-panel {
    height: 100svh;
    overflow-y: auto; overscroll-behavior: contain;
    transform: none;
    clip-path: inset(0 0 100% 0);
    transition: clip-path 0.8s var(--ease);
  }
  .menu-open .menu-panel { clip-path: inset(0); }
  .menu-block { opacity: 0; transition: opacity 0.4s var(--ease); }
  .menu-open .menu-block { opacity: 1; transition: opacity 0.5s 0.2s var(--ease); }
  .menu-links a { font-size: 36px; }
  /* The header stays on top so the dropdown hangs from it; its Menu button becomes Close */
  .site-header { z-index: 70; }
  .menu-close { display: none; }
  .step { column-gap: 16px; padding-bottom: 40px; }
  .xp-head, .xp-inner, .xp-more { --xp-cols: 32px 1fr 24px; gap: 12px; }
  .xp-num { font-size: 14px; }
  .hero-intro { font-size: 16px; }
  /* Compact hero: portrait (64vw wide, 4:5) sits under the header, the name runs
     through its middle, and the intro follows right after instead of at the screen bottom */
  .hero { --portrait-mid: calc(var(--header-h) + 40px + 40vw); min-height: 0; padding: calc(var(--header-h) + 80px + 80vw) 0 56px; }
  .hero-marquee { top: var(--portrait-mid); transform: translateY(-50%); }
  .hero-portrait { top: var(--portrait-mid); width: 64vw; translate: -50% -50%; }
  .section-head { flex-direction: column; align-items: flex-start; gap: 16px; }
  .work-grid { grid-template-columns: 1fr; }
  .footer-grid { grid-template-columns: 1fr; }
  .footer-bar { flex-direction: column; }
  /* The calendar runs edge to edge so Cal gets the full screen width */
  .book { padding-left: 0; padding-right: 0; }
  .book-mark, .book-alt { padding: 0 var(--gutter); }
  .book-frame { padding: 0; border-left: 0; border-right: 0; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.001s !important; animation-iteration-count: 1 !important; transition-duration: 0.001s !important; }
  .marquee-track { animation: none; }
  .zzz b { animation: none; }
  .skills-row { flex-wrap: wrap; margin: 0; -webkit-mask-image: none; mask-image: none; }
  .skills-track { flex: 1 1 auto; flex-wrap: wrap; animation: none; }
  .skills-track[aria-hidden="true"] { display: none; }
  .reveal, .book-frame, .book-mark span { opacity: 1; transform: none; filter: none; }
  .xp-inner > * { opacity: 1; }
  html { scroll-behavior: auto; }
}
