/* ============================================================================
   Ilai Levavi — portfolio
   Dark viewport palette. Two accents with strictly separated jobs:
   cyan is interactive, orange marks an achievement. Nothing else is coloured.
   ========================================================================= */

@font-face {
  font-family: 'Archivo';
  src: url('assets/fonts/archivo-var.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-stretch: 62.5% 125%;
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: url('assets/fonts/jetbrains-mono-var.woff2') format('woff2-variations');
  font-weight: 400 600;
  font-display: swap;
}

:root {
  --ground: #08090b;
  --panel: #0f1116;
  --panel-2: #161a21;
  --line: rgba(255, 255, 255, 0.09);
  --line-strong: rgba(255, 255, 255, 0.18);
  --ink: #eceef1;
  --dim: #878e99;    /* 6.1:1 on --ground */
  --dimmer: #767d88; /* 4.7:1 — the floor for the small mono labels */
  --accent: #4fe3d0;   /* categorical labels only — never a click target */
  --flare: #ff6b2c;    /* the award badge, and nothing else */

  /* Interactive colour, carried over from version 2. Anything clickable is
     blue; cyan stays on labels so the two never compete. */
  --blue: #2997ff;
  --blue-solid: #0071e3;
  --blue-hover: #0077ed;
  --ease-apple: cubic-bezier(0.28, 0.11, 0.32, 1);

  --sans: 'Archivo', 'Segoe UI', system-ui, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, 'Cascadia Mono', Menlo, monospace;

  --gutter: clamp(1.25rem, 5vw, 5.5rem);
  --container: 96rem;
  /* Every full-width block uses this instead of its own max-width, so the
     nav, rail, headings, projects, about and footer all share one left edge. */
  --pad-x: max(var(--gutter), calc((100% - var(--container)) / 2));
  --measure: 46ch;
  --bar-h: 56px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

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

html {
  scroll-behavior: smooth;
  /* Keep anchored sections clear of both sticky bars. */
  scroll-padding-top: calc(var(--bar-h) + 52px);
}

/* No overflow-x rule anywhere, on purpose. Nothing overflows horizontally, and
   the usual `overflow-x: hidden` on <body> is not free: it turns body into a
   scroll container, which is a common source of sticky-positioning bugs.
   (`clip` on <html> is worse still — it propagates to the viewport and stops
   the page scrolling at all.) If something ever does overflow, clip the
   offending element rather than reaching for the root. */

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-variation-settings: 'wdth' 100;
  font-size: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

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

a { color: inherit; }

.mono {
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.sr {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  padding: 0.75rem 1.25rem;
  background: var(--accent);
  color: #04211d;
  font-weight: 600;
  text-decoration: none;
}
.skip:focus { left: 0; }

:where(a, button, [tabindex]):focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ───────────────────────────  top bar  ─────────────────────────────────── */

.topbar {
  position: sticky;
  top: 0;
  z-index: 60;
  height: var(--bar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-inline: var(--pad-x);
  background: color-mix(in srgb, var(--ground) 82%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
}

.topbar__name {
  font-weight: 700;
  font-size: 0.9rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  font-variation-settings: 'wdth' 112;
}

.topbar__nav {
  display: flex;
  gap: clamp(1rem, 3vw, 2.25rem);
}

.topbar__nav a {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim);
  text-decoration: none;
  transition: color 0.2s;
}
.topbar__nav a:hover,
.topbar__nav a[aria-current='true'] { color: var(--ink); }

/* ─────────────────────────────  hero  ──────────────────────────────────── */

.hero {
  position: relative;
  min-height: calc(100svh - var(--bar-h));
  display: flex;
  align-items: center;
  padding: clamp(3rem, 10vh, 7rem) var(--pad-x) 6rem;
  overflow: hidden;
  isolation: isolate;
}

.hero__plates { position: absolute; inset: 0; z-index: -2; }

.hero__plate {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  transform: scale(1.06);
  transition: opacity 2.2s linear;
}
.hero__plate.is-on { opacity: 0.42; }

/* Two washes: a vertical fade so the type always sits on near-black, and a
   radial vignette that pulls the eye to the centre-left where the name lands. */
.hero::before,
.hero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
.hero::before {
  background:
    linear-gradient(100deg, var(--ground) 16%, color-mix(in srgb, var(--ground) 40%, transparent) 55%, transparent 100%),
    linear-gradient(to top, var(--ground) 1%, transparent 38%);
}
.hero::after {
  background: radial-gradient(130% 100% at 18% 45%, transparent 32%, rgba(4, 5, 7, 0.6) 100%);
}

.hero__inner { position: relative; max-width: 62rem; }


.hero__title {
  margin: 0;
  font-size: clamp(3.4rem, 15vw, 12.5rem);
  line-height: 0.84;
  font-weight: 800;
  letter-spacing: -0.025em;
  text-transform: uppercase;
}

.hero__word {
  display: block;
  font-variation-settings: 'wdth' 125;
}

.hero__lede {
  max-width: 34rem;
  margin: clamp(1.5rem, 4vh, 2.5rem) 0 0;
  font-size: clamp(1.0625rem, 0.98rem + 0.42vw, 1.3125rem);
  line-height: 1.55;
  color: #c8ccd3;
}

.hero__stack {
  display: flex;
  flex-wrap: wrap;
  gap: 0 1.5rem;
  list-style: none;
  margin: 1.75rem 0 0;
  padding: 0;
  color: var(--dimmer);
}
.hero__stack li { position: relative; }
.hero__stack li + li::before {
  content: '·';
  position: absolute;
  left: -0.9rem;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: clamp(2rem, 5vh, 3rem);
}

/* Buttons, carried over from version 2: pill shape, sentence case, one
   filled blue primary and hairline secondaries. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 1.375rem;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 980px;
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 400;
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--ink);
  text-decoration: none;
  transition: background 0.25s var(--ease-apple), border-color 0.25s var(--ease-apple),
              color 0.25s var(--ease-apple);
}
.btn:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.45);
}

.btn--primary {
  background: var(--blue-solid);
  border-color: var(--blue-solid);
  color: #fff;
}
.btn--primary:hover {
  background: var(--blue-hover);
  border-color: var(--blue-hover);
  color: #fff;
}

.hero__meta {
  position: absolute;
  left: var(--pad-x);
  right: var(--pad-x);
  bottom: 1.75rem;
  display: flex;
  justify-content: space-between;
  color: var(--dimmer);
}

/* ────────────────────────  section furniture  ──────────────────────────── */

.section-title {
  margin: 0;
  font-size: clamp(1.9rem, 1.2rem + 3vw, 3.6rem);
  line-height: 1;
  font-weight: 800;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  font-variation-settings: 'wdth' 118;
}

.work__head {
  padding: clamp(4rem, 12vh, 8rem) var(--pad-x) clamp(1.5rem, 4vh, 2.5rem);
  border-top: 1px solid var(--line);
}

.section-note {
  max-width: var(--measure);
  margin: 1.25rem 0 0;
  color: var(--dim);
}

/* ────────────  the timeline, carried over from version 2  ───────────────
   Frosted bar, soft dots, the active project named above it. Runs newest on
   the left to oldest on the right, matching the order of the sections below. */

.rail {
  position: sticky;
  top: var(--bar-h);
  z-index: 50;
  background: rgba(0, 0, 0, 0.72);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.rail__inner {
  position: relative;
  max-width: 920px;
  margin: 0 auto;
  padding: 0.9rem var(--gutter) 0.8rem;
}

/* Inset so the dots at 0% and 100% sit fully inside the bar. */
.rail__track {
  position: relative;
  height: 12px;
  margin: 0 10px;
}

.rail__line {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 1px;
  transform: translateY(-50%);
  background: rgba(255, 255, 255, 0.16);
}

/* One dot per project, positioned from its real start date. */
.rail__dot {
  position: absolute;
  top: 50%;
  left: var(--at);
  width: 28px;
  height: 28px;
  margin: -14px 0 0 -14px;
  padding: 0;
  border: 0;
  background: none;
  border-radius: 50%;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.rail__dot::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 7px;
  height: 7px;
  margin: -3.5px 0 0 -3.5px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.32);
  transition: width 0.35s var(--ease-apple), height 0.35s var(--ease-apple),
              margin 0.35s var(--ease-apple), background 0.35s var(--ease-apple),
              box-shadow 0.35s var(--ease-apple);
}
.rail__dot:hover::after { background: rgba(255, 255, 255, 0.75); }
.rail__dot.is-on::after {
  width: 11px;
  height: 11px;
  margin: -5.5px 0 0 -5.5px;
  background: #fff;
  box-shadow: 0 0 0 5px rgba(255, 255, 255, 0.12);
}

.rail__years {
  position: relative;
  height: 1.1rem;
  margin: 0.4rem 10px 0;
}
.rail__years span {
  position: absolute;
  left: var(--at);
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0;
  color: var(--dimmer);
  transform: translateX(-2px);
}

/* ───────────────────────────  projects  ────────────────────────────────── */

.project {
  position: relative;
  padding: clamp(4.5rem, 12vh, 9rem) var(--pad-x);
  border-bottom: 1px solid var(--line);
  isolation: isolate;
  overflow: hidden;
}

/* The old site's section photographs, kept as the faint backdrop they were —
   pushed far enough down that the renders stay the brightest thing on screen. */
.project__bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  background-size: cover;
  background-position: center;
  opacity: 0.13;
  filter: saturate(0.55);
}
.project::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(to bottom, var(--ground) 0%, transparent 30%, transparent 70%, var(--ground) 100%);
  pointer-events: none;
}

.project__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}

/* Mirror the layout, and mirror the column weights with it so the media panel
   keeps the same width on both sides. */
.project--flip .project__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); }
.project--flip .project__media { order: 2; }

.project__media { min-width: 0; }

.project__body { min-width: 0; max-width: 40rem; }

/* Metadata line: the sans face at a readable size, in the same grey as the
   body copy. It labels the project, so it stays quiet — the cyan mono treatment
   it replaced read as a link. */
.project__kicker {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 0.5rem;
  margin: 0 0 1rem;
  font-family: var(--sans);
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: -0.008em;
  text-transform: none;
  color: var(--dim);
  max-width: none;
}
.project__kind { color: #b9bec7; }
.project__date { color: var(--dim); font-weight: 400; }
.project__date::before { content: '· '; color: var(--dimmer); }

.project__title {
  margin: 0 0 1.25rem;
  font-size: clamp(1.6rem, 1.1rem + 2vw, 2.75rem);
  line-height: 1.05;
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variation-settings: 'wdth' 110;
}

/* The reading measure applies to prose only. Metadata lines carry their own
   class and are matched separately, so nothing here needs !important. */
.project__body p {
  margin: 0 0 1rem;
  color: #c3c8d0;
}
.project__body p:not([class]),
.project__body .project__aside {
  max-width: var(--measure);
}

.project__body .project__aside {
  padding-left: 1rem;
  border-left: 1px solid var(--line-strong);
  color: var(--dim);
  font-size: 0.9375rem;
}

.project__body .project__tools {
  margin: 1.5rem 0 0;
  color: var(--dimmer);
  line-height: 1.9;
  text-transform: none;
  letter-spacing: 0.02em;
}

.project__body .project__links { margin: 1.5rem 0 0; }

/* Links, carried over from version 2: plain blue with a chevron, no underline
   until hover. */
.link-out {
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 400;
  letter-spacing: -0.012em;
  text-transform: none;
  color: var(--blue);
  text-decoration: none;
}
.link-out::after { content: ' ›'; }
.link-out:hover { text-decoration: underline; }

/* The only orange on the page. */
.project__body .award {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  margin: 0 0 1.25rem;
  padding: 0.4rem 0.9rem 0.4rem 0.4rem;
  border: 1px solid color-mix(in srgb, var(--flare) 35%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--flare) 8%, transparent);
  color: var(--ink);
  font-size: 0.8125rem;
}
.award__rank {
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  background: var(--flare);
  color: #26150c;
  font-weight: 600;
  font-size: 0.65rem;
}

/* ────────────────────  viewport (video facade)  ────────────────────────── */

.viewport {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--panel);
  border: 1px solid var(--line-strong);
  overflow: hidden;
}

.viewport__poster {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.6s var(--ease), filter 0.4s;
  filter: saturate(0.9) contrast(1.02);
}
.viewport:hover .viewport__poster { transform: scale(1.03); filter: none; }

.viewport__play {
  position: absolute;
  inset: 0;
  width: 100%;
  border: 0;
  padding: 0;
  /* Scrims top and bottom so the HUD labels stay legible over bright frames. */
  background: linear-gradient(
    to bottom,
    rgba(4, 5, 7, 0.5) 0%,
    rgba(4, 5, 7, 0.04) 24%,
    rgba(4, 5, 7, 0.04) 62%,
    rgba(4, 5, 7, 0.6) 100%
  );
  cursor: pointer;
  display: grid;
  place-items: center;
}

.viewport__glyph {
  width: 68px;
  height: 68px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: color-mix(in srgb, var(--ground) 55%, transparent);
  backdrop-filter: blur(6px);
  display: grid;
  place-items: center;
  transition: background 0.25s, border-color 0.25s, transform 0.25s var(--ease);
}
.viewport__glyph::before {
  content: '';
  width: 0; height: 0;
  margin-left: 4px;
  border-left: 15px solid var(--ink);
  border-top: 9px solid transparent;
  border-bottom: 9px solid transparent;
  transition: border-left-color 0.25s;
}
.viewport__play:hover .viewport__glyph {
  background: var(--accent);
  border-color: var(--accent);
  transform: scale(1.08);
}
.viewport__play:hover .viewport__glyph::before { border-left-color: #04211d; }

/* HUD labels + corner brackets: the vernacular of the engine viewport these
   frames were rendered in. */
.viewport__hud {
  position: absolute;
  z-index: 2;
  font-size: 0.625rem;
  color: rgba(255, 255, 255, 0.72);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
  pointer-events: none;
  text-transform: none;
  letter-spacing: 0.08em;
}
.viewport__hud--tl { top: 0.85rem; left: 0.95rem; }
.viewport__hud--br { bottom: 0.85rem; right: 0.95rem; }

.viewport::before,
.viewport::after {
  content: '';
  position: absolute;
  z-index: 2;
  width: 18px;
  height: 18px;
  pointer-events: none;
  opacity: 0.5;
}
.viewport::before {
  top: 8px; left: 8px;
  border-top: 1px solid var(--accent);
  border-left: 1px solid var(--accent);
}
.viewport::after {
  bottom: 8px; right: 8px;
  border-bottom: 1px solid var(--accent);
  border-right: 1px solid var(--accent);
}

/* The Town Jumper trailer is a vertical mobile capture — giving it a 9:16 frame
   crops YouTube's pillarboxed thumbnail back to the real picture, and the embed
   fills the frame instead of sitting in bars. */
.viewport--portrait { aspect-ratio: 9 / 16; }
/* A hair of overscale hides the soft seam where YouTube's blurred pillarbox
   meets the real frame. */
.viewport--portrait .viewport__poster { transform: scale(1.05); }
.viewport--portrait:hover .viewport__poster { transform: scale(1.08); }

/* A video can ride inside a strip as its first card — The Town Jumper's trailer
   does, so one swipe runs the trailer and all seven screenshots instead of the
   video occupying a block of its own above them. It matches the shots' height
   and takes its width from its own aspect ratio. */
.strip__scroller > .viewport {
  flex: 0 0 auto;
  height: clamp(210px, 26vw, 320px);
  width: auto;
  scroll-snap-align: start;
}
.strip__scroller > .viewport--portrait { aspect-ratio: 9 / 16; }

.viewport.is-live { aspect-ratio: 16 / 9; }
.viewport--portrait.is-live { aspect-ratio: 9 / 16; }
.viewport.is-live::before,
.viewport.is-live::after { display: none; }
.viewport iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* ──────────────────────────  image strips  ─────────────────────────────── */

.strip { position: relative; margin-top: 1rem; }
.project__media > .strip:first-child { margin-top: 0; }

/* Fade the trailing edge only while there is more to scroll to, so the mask
   itself is the affordance. main.js toggles .has-more. */
.strip.has-more .strip__scroller,
.strip.has-more .shots-grid {
  mask-image: linear-gradient(to right, #000 82%, transparent 100%);
  -webkit-mask-image: linear-gradient(to right, #000 82%, transparent 100%);
}

/* A grid strip only scrolls below 640px, so its count is hidden until then —
   above that everything is already on screen and the label would be noise. */
.strip:has(.shots-grid) .strip__count { display: none; }

.strip__count {
  position: absolute;
  right: 0;
  bottom: -1.4rem;
  margin: 0;
  color: var(--dimmer);
  font-size: 0.625rem;
}

.strip__scroller {
  display: flex;
  gap: 0.75rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
  padding-bottom: 0.6rem;
}
.strip__scroller::-webkit-scrollbar { height: 6px; }
.strip__scroller::-webkit-scrollbar-track { background: transparent; }
.strip__scroller::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: 999px;
}

.shot {
  flex: 0 0 auto;
  padding: 0;
  border: 1px solid var(--line);
  background: var(--panel);
  cursor: zoom-in;
  scroll-snap-align: start;
  overflow: hidden;
  transition: border-color 0.25s;
}
.shot:hover { border-color: var(--accent); }

.shot img {
  height: clamp(150px, 21vw, 240px);
  width: auto;
  object-fit: cover;
  transition: transform 0.5s var(--ease);
}
.shot:hover img { transform: scale(1.05); }

.strip--portrait .shot img { height: clamp(210px, 26vw, 320px); }

/* Landscape sets read far better as a grid than as a cramped scroller — every
   render is visible at once instead of two at a time. */
.shots-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 0.65rem;
}
.shots-grid .shot { width: 100%; }
.shots-grid .shot img {
  width: 100%;
  height: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* ───────────────────────────  lightbox  ────────────────────────────────── */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 1rem;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgba(4, 5, 7, 0.94);
  backdrop-filter: blur(8px);
}
.lightbox[hidden] { display: none; }

.lightbox__img {
  grid-column: 2;
  max-width: 100%;
  max-height: 88vh;
  margin: 0 auto;
  object-fit: contain;
  border: 1px solid var(--line);
}

.lightbox__close {
  position: absolute;
  top: clamp(0.75rem, 2vw, 1.5rem);
  right: clamp(0.75rem, 2vw, 1.5rem);
  width: 44px; height: 44px;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  background: none;
  color: var(--ink);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.2s, color 0.2s;
}
.lightbox__close:hover { border-color: var(--accent); color: var(--accent); }

.lightbox__nav {
  width: 48px; height: 48px;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  background: none;
  color: var(--ink);
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.2s, color 0.2s;
}
.lightbox__nav:hover { border-color: var(--accent); color: var(--accent); }
.lightbox__nav--prev { grid-column: 1; }
.lightbox__nav--next { grid-column: 3; }

.lightbox__count {
  position: absolute;
  bottom: clamp(0.75rem, 2vw, 1.5rem);
  left: 50%;
  transform: translateX(-50%);
  margin: 0;
  color: var(--dim);
}

/* ────────────────────────────  about  ──────────────────────────────────── */

#about {
  position: relative;
  padding: clamp(4.5rem, 12vh, 9rem) var(--pad-x);
  border-bottom: 1px solid var(--line);
  isolation: isolate;
  overflow: hidden;
}

/* The old hero illustration, kept as ambient haze rather than a focal image. */
.about__wash {
  position: absolute;
  inset: auto 0 0;
  height: 70%;
  z-index: -1;
  background: url('assets/img/hero-art.jpg') center / cover no-repeat;
  opacity: 0.1;
  mix-blend-mode: luminosity;
  mask-image: linear-gradient(to top, #000, transparent);
  -webkit-mask-image: linear-gradient(to top, #000, transparent);
  pointer-events: none;
}

.about__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.55fr) minmax(0, 1.45fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: center;
}

.about__figure {
  margin: 0;
  border: 1px solid var(--line);
  overflow: hidden;
  background: var(--panel);
}
.about__figure img {
  width: 100%;
  /* height:auto is required — the img height attribute is a presentational
     hint, and an explicit height wins over aspect-ratio. */
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: center 30%;
  filter: saturate(0.85) contrast(1.05);
}

.about__body p {
  margin: 0 0 1rem;
  color: #c3c8d0;
  max-width: var(--measure);
}

.about__body .about__lead {
  font-size: clamp(1.25rem, 1rem + 1.1vw, 1.75rem);
  line-height: 1.35;
  color: var(--ink);
  font-weight: 500;
  margin: 1.75rem 0 1.25rem;
  max-width: 24ch;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
}
.chips li {
  padding: 0.35rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--dim);
  font-size: 0.6875rem;
  text-transform: none;
  letter-spacing: 0.04em;
}

/* ───────────────────────────  contact  ─────────────────────────────────── */

#contact {
  padding: clamp(5rem, 16vh, 11rem) var(--pad-x);
}

.contact__inner { max-width: 76rem; }

.contact__title {
  margin: 0;
  font-size: clamp(2.4rem, 1rem + 8vw, 8rem);
  line-height: 0.92;
  font-weight: 800;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  font-variation-settings: 'wdth' 120;
}

.contact__note {
  max-width: var(--measure);
  margin: 1.75rem 0 0;
  color: var(--dim);
}

.contact__links {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-top: clamp(2.5rem, 7vh, 4rem);
}

/* Cards, not underlined links. Squarer than the pill .btn on purpose, so the
   two never read as the same control. Hover moves to --blue, never --accent:
   cyan is a label colour on this page and must not land on a click target. */
.contact__link {
  --edge: var(--line);
  display: inline-grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.95rem;
  min-width: min(20rem, 100%);
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--edge);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.02);
  color: var(--ink);
  text-decoration: none;
  transition: border-color 0.3s var(--ease-apple), background 0.3s var(--ease-apple),
              transform 0.3s var(--ease-apple);
}
.contact__link:hover,
.contact__link:focus-visible {
  --edge: color-mix(in srgb, var(--blue) 50%, transparent);
  background: color-mix(in srgb, var(--blue) 8%, transparent);
  transform: translateY(-2px);
}

.contact__icon {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.04);
  color: var(--dim);
  transition: color 0.3s var(--ease-apple), background 0.3s var(--ease-apple),
              border-color 0.3s var(--ease-apple);
}
.contact__icon svg { width: 20px; height: 20px; }
.contact__link:hover .contact__icon,
.contact__link:focus-visible .contact__icon {
  color: #fff;
  background: var(--blue-solid);
  border-color: var(--blue-solid);
}

.contact__text { display: grid; gap: 0.2rem; min-width: 0; }

.contact__label {
  color: var(--dimmer);
  font-size: 0.62rem;
  transition: color 0.3s var(--ease-apple);
}
.contact__link:hover .contact__label,
.contact__link:focus-visible .contact__label { color: var(--blue); }

.contact__value {
  font-size: clamp(0.9375rem, 0.88rem + 0.3vw, 1.0625rem);
  font-weight: 500;
  letter-spacing: -0.01em;
  overflow-wrap: anywhere;
}

.contact__arrow {
  display: grid;
  place-items: center;
  width: 18px;
  color: var(--dimmer);
  transition: color 0.3s var(--ease-apple), transform 0.3s var(--ease-apple);
}
.contact__arrow svg { width: 18px; height: 18px; }
.contact__link:hover .contact__arrow,
.contact__link:focus-visible .contact__arrow {
  color: var(--blue);
  transform: translateX(3px);
}

/* ────────────────────────────  footer  ─────────────────────────────────── */

.foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 2rem var(--pad-x);
  border-top: 1px solid var(--line);
  color: var(--dimmer);
  font-size: 0.65rem;
  text-transform: none;
}


/* ───────────────────────  logo chips + previously-at  ──────────────────
   All three marks are supplied for dark backgrounds (white or white-and-teal
   artwork), so they sit on a subtle dark tile rather than a white one. The
   exception is ByondXR, whose navy wordmark is recoloured to white in
   assets/img/companies/byondxr.svg — the untouched original is kept alongside
   it as byondxr-original.svg. */

/* Flex, not `grid` + `place-items: center`. Under grid centering Chrome treats
   the grid area as an indefinite height, so `height: 100%` on the image below
   silently fell back to `auto` — the image then took its height from its own
   aspect ratio at `width: 100%` and burst out of the tile, and `overflow:
   hidden` sliced it. Harmless at 156px wide (desktop), fatal on mobile where
   .chip--lg goes full width: a 340px-wide tile made every logo ~400px tall
   inside a 68px box. In flex the container's height is definite, so the
   percentage resolves and object-fit does its job. */
.chip {
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 0.5rem 0.85rem;
  height: 50px;
  overflow: hidden;
}
/* Explicit box + contain, so a tall stacked lockup (Eyeviation) can never
   overflow the tile the way max-width/max-height alone allowed. */
.chip img {
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  object-fit: contain;
}

.chip--lg {
  height: 68px;
  width: 100%;
  border-radius: 14px;
  padding: 0.85rem 1.1rem;
}

.prev { margin-top: clamp(2.25rem, 6vh, 3.25rem); }

.prev__label {
  margin: 0 0 0.85rem;
  color: var(--dimmer);
  font-size: 0.65rem;
}

.prev__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.prev__list .chip {
  width: 136px;
  opacity: 0.82;
  transition: opacity 0.25s var(--ease-apple), transform 0.25s var(--ease-apple);
}
.prev__list li:hover .chip { opacity: 1; transform: translateY(-2px); }

/* ─────────────────────────────  experience  ───────────────────────────── */

.roles {
  list-style: none;
  margin: 0;
  padding: 0 var(--pad-x) clamp(1rem, 4vh, 2.5rem);
}

.role {
  display: grid;
  grid-template-columns: 156px minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  padding: clamp(2rem, 5vh, 3rem) 0;
  border-top: 1px solid var(--line);
}
.role:first-child { border-top: 0; }

.role__mark { display: flex; justify-content: flex-start; }

.role__body { max-width: 54rem; }

.role__when {
  margin: 0 0 0.5rem;
  color: var(--dimmer);
  font-size: 0.68rem;
}

.role__title {
  margin: 0 0 0.6rem;
  font-size: clamp(1.25rem, 1rem + 1vw, 1.75rem);
  line-height: 1.15;
  font-weight: 700;
  letter-spacing: -0.015em;
  font-variation-settings: 'wdth' 108;
}

/* Company name plus a one-line description of what they actually do — the
   reader may not know ByondXR or Eyeviation. */
.role__at {
  margin: 0 0 1rem;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--dim);
}
.role__at strong {
  color: var(--ink);
  font-weight: 600;
}
.role__at strong::after {
  content: ' — ';
  color: var(--dimmer);
  font-weight: 400;
}

.role__body p {
  margin: 0 0 0.9rem;
  color: #c3c8d0;
  max-width: 68ch;
}
.role__body p:last-child { margin-bottom: 0; }

.role__note {
  color: var(--dimmer) !important;
  font-size: 0.875rem;
}

/* ───────────────────────────────  skills  ─────────────────────────────── */

.skills {
  margin: 2rem 0 0;
  padding: 0;
  display: grid;
  gap: 1rem;
}

.skills__group {
  display: grid;
  grid-template-columns: minmax(0, 11rem) minmax(0, 1fr);
  gap: 0.35rem 1.25rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
}

.skills dt {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dimmer);
  padding-top: 0.15rem;
}

.skills dd {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.6;
  color: #c3c8d0;
}

@media (max-width: 720px) {
  .role { grid-template-columns: 1fr; gap: 1.25rem; }
  /* The logo column becomes a full-width row here. Left as `width: 100%` the
     tile stretches the whole screen with a small contained logo floating in the
     middle of it, so keep it the same badge size it is on desktop. */
  .chip--lg { width: 156px; }
  .skills__group { grid-template-columns: 1fr; gap: 0.25rem; }
}


/* ─────────────────  self-hosted video + 2026 layouts  ─────────────────── */

/* Three vertical social films side by side. */
.media-trio {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.85rem;
}
.media-trio .viewport--portrait {
  width: 100%;
  height: auto;
}

/* An inline <video> replaces the poster in place, same as the embeds do. */
.viewport video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  background: #000;
  object-fit: contain;
}

/* Marks the role that is still running, in place of a company logo. */
.chip--now {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  width: auto;
  padding: 0 0.95rem;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink);
}
.chip--now .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #3ddc84;
  box-shadow: 0 0 0 4px rgba(61, 220, 132, 0.16);
}

@media (max-width: 700px) {
  .media-trio { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ───────────────────────  reveal on scroll  ────────────────────────────── */

.reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}
.reveal.is-in { opacity: 1; transform: none; }

/* ─────────────────────────  responsive  ────────────────────────────────── */

@media (max-width: 1000px) {
  .project__grid,
  .about__grid { grid-template-columns: 1fr; }
  /* `.project--flip .project__grid` is (0,2,0) and beat the (0,1,0) rule above,
     so the five mirrored projects — FlashSync, Catching Waves, Startup Gallery,
     Ping Pong, Virtual Mikveh — never collapsed. On a 390px phone they were
     still laid out as two columns: a 135px media column holding a 135×76 video,
     next to a 168px ribbon of text. It also put Virtual Mikveh's `.shots-grid`
     (min track 230px) into a 135px column, so the renders spilled sideways
     underneath the text. This one line is the fix for all of that. */
  .project--flip .project__grid { grid-template-columns: 1fr; }
  /* Stacked, the text leads and the media follows — on every project, mirrored
     or not. Opening with a poster made each project read as an image with a
     caption somewhere below it; opening with the title says what you are
     looking at before you look at it. Selector is (0,2,0) on purpose, to beat
     `.project--flip .project__media`. */
  .project__grid .project__body { order: 1; }
  .project__grid .project__media { order: 2; }
  .project__body { max-width: none; }
  .about__figure { max-width: 22rem; }
}

@media (max-width: 640px) {
  :root { --bar-h: 52px; }

  /* ── Vertical rhythm ──────────────────────────────────────────────────
     The desktop `12vh` paddings are generous on a wide screen and absurd on a
     phone: ten projects at 101px top and bottom was ~2,000px of empty page,
     and the whole document came to 18,400px — 22 phone screens. Everything in
     this group is spacing only; nothing here changes what is on the page. */
  .project { padding: clamp(2.5rem, 7vh, 3.5rem) var(--pad-x); }
  .work__head { padding: clamp(2.5rem, 7vh, 3.5rem) var(--pad-x) 1.25rem; }
  #about { padding: clamp(2.5rem, 7vh, 3.5rem) var(--pad-x); }
  #contact { padding: clamp(3rem, 9vh, 4.5rem) var(--pad-x); }
  .role { padding: clamp(1.5rem, 4vh, 2rem) 0; }
  .hero { padding: clamp(2rem, 8vh, 5rem) var(--pad-x) 3.5rem; }
  .section-note { margin-top: 0.85rem; }
  .about__figure { max-width: 16rem; }

  /* ── Every gallery is a swipe strip ───────────────────────────────────
     `.shots-grid` stacked Flip Flop's ten renders into 1,987px of scrolling
     as a single column, and a two-up contact sheet made them thumbnails. One
     card at a time, swiped, is the shape that fits a phone: the render is
     nearly full width, the next one peeks in from the right to say there are
     more, and the whole set costs one screen instead of five. */
  .shots-grid {
    display: flex;
    gap: 0.6rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: thin;
    scrollbar-color: var(--line-strong) transparent;
    padding-bottom: 0.5rem;
  }
  .shots-grid .shot {
    flex: 0 0 85%;
    scroll-snap-align: start;
  }
  .shots-grid .shot img { width: 100%; height: auto; }
  .strip:has(.shots-grid) .strip__count { display: block; }

  /* The trailer card and the screenshots in one strip, sized so the trailer
     leads and roughly two screenshots follow it into view. */
  .strip--portrait .shot img { height: clamp(200px, 60vw, 250px); }
  .strip__scroller > .viewport { height: clamp(200px, 60vw, 250px); }

  /* The three Kite Lab films, smaller — two fit the screen now instead of one
     and a half. */
  .media-trio {
    display: flex;
    gap: 0.6rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: thin;
    scrollbar-color: var(--line-strong) transparent;
    padding-bottom: 0.5rem;
  }
  .media-trio .viewport--portrait {
    flex: 0 0 48%;
    scroll-snap-align: start;
  }

  /* ── Where one project ends ───────────────────────────────────────────
     A 9%-alpha hairline is legible across a wide desktop column and invisible
     on a phone, where nothing else marks the boundary. Sharpen it, and give
     the next title real air above it. */
  .project { border-bottom: 1px solid rgba(255, 255, 255, 0.3); }
  .project + .project { padding-top: clamp(3rem, 8vh, 4rem); }

  /* The separators are absolutely positioned 0.9rem to the left of each item,
     which is invisible until the list wraps — then the dot for the first item
     on line two hangs out into the page gutter on its own. Below 640px the
     line always wraps, so the dot moves inside the item it follows and ends
     the line instead of orphaning itself. */
  .hero__stack { gap: 0.15rem 0.55rem; }
  .hero__stack li + li::before { content: none; }
  .hero__stack li:not(:last-child)::after {
    content: ' ·';
    color: var(--line-strong);
  }

  .hero__meta { display: none; }
  .hero__title { line-height: 0.9; }
  .hero__word { font-variation-settings: 'wdth' 112; }
  .lightbox { grid-template-columns: 1fr; }
  .lightbox__nav {
    position: absolute;
    bottom: 3.5rem;
  }
  .lightbox__nav--prev { left: 1rem; }
  .lightbox__nav--next { right: 1rem; }
  .lightbox__img { grid-column: 1; }
  .foot { flex-direction: column; }
}

/* Smallest phones: the name plus four nav links stop fitting on one line, so
   the name drops out — the hero says it immediately below anyway. */
@media (max-width: 460px) {
  .topbar__name { font-size: 0.78rem; letter-spacing: 0.1em; }
  .topbar__nav { gap: 0.85rem; }
  .topbar__nav a { font-size: 0.64rem; letter-spacing: 0.04em; }
}

@media (max-width: 380px) {
  .topbar { justify-content: center; }
  .topbar__name { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
  .reveal { opacity: 1; transform: none; }
  .hero__plate.is-on { opacity: 0.42; }
}
